C++学习(1)——命名空间、函数重载、缺省参数、引用、内联函数

系列文章目录

C++学习传送门


文章目录

  • 系列文章目录
  • 一、命名空间
    • 1.定义:
    • 2.使用方法
  • 二、缺省参数
    • 1.缺省参数的定义
    • 2.缺省函数的分类
      • (1)全缺省
      • (2)半缺省
      • (3)注意
  • 三、函数重载
    • 1.函数重载的含义
    • 2.函数重载的原理——名字修饰
  • 四、引用
    • 1.引用的特性
    • 2.引用的用途
      • (1)引用作为返回值
      • (2)引用作为参数
    • 3.引用与指针的区别
    • 4.const引用补充
      • (1)强制类型转换
      • (2)传值调用
      • (3)函数返回值
  • 五、内联函数
    • 1.宏函数的缺点:
    • 2.内联函数的注意事项:


一、命名空间

在编写项目时,我们会包含很多头文件,这会造成出现大量的变量、函数和类等,而这些类、函数、变量的名称都存在与全局作用域中会产生命名冲突和命名污染,也不便于我们进行自定义类和函数。

而namespace的作用就是:定义一个新的作用域,将一些类、函数、变量等写在其中。可以实现对标识符名称的本地化,防止命名冲突和污染。

1.定义:

写法如下:

namespace scope
{
	int i;
	int add(int a,int b)
	{
		return a + b;
	}
	strcut Node
	{
		struct Node* next;
		int data;
	}
}

命名空间也可以嵌套定义

namespace N1
{
	int a;
	int b;
	int Add(int left, int right)
	 {
	     return left + right;
	 }
	namespace N2
	 {
	     int c;
	     int d;
	     int Sub(int left, int right)
	     {
	         return left - right;
	     }
	 }
}

同一个项目中,在不同的文件里可以定义多个相同名字的命名空间,链接器最终会将它们链接成同一个命名空间

2.使用方法

以下以使用命名空间std为例:

第一,可以在每次使用std内的成员时,加上命名空间的名字std 和 作用域限定符::

int main()
{
	int a=10;
	std::cout<<a<<std::endl;
	return 0;
}

第二,可以通过using使用所要访问的成员

using std::cout;

int main()
{
	int a=10;
	cout<<a<<std::endl;
	return 0;
}

第三,通过using直接使用整个命名空间

using namesapce std;
int main()
{
	int a=10;
	cout<<a<<endl;
	return 0;
}

二、缺省参数

1.缺省参数的定义

缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实
参则采用该形参的缺省值,否则使用指定的实参。
在函数定义与声明分离时,编译器要求只能在声明时指明缺省值,而定义时不可以。

void Func(int a = 0)
{
 	cout<<a<<endl;
}
int main()
{
	 Func();     // 没有传参时,使用参数的默认值
	 Func(10);   // 传参时,使用指定的实参
	return 0;
}

2.缺省函数的分类

(1)全缺省

void Func(int a = 10, int b = 20, int c = 30)
 {
     cout<<"a = "<<a<<endl;
     cout<<"b = "<<b<<endl;
     cout<<"c = "<<c<<endl;
 }

(2)半缺省


void Func(int a, int b = 10, int c = 20)
 {
     cout<<"a = "<<a<<endl;
     cout<<"b = "<<b<<endl;
     cout<<"c = "<<c<<endl;
 }

(3)注意

1. 半缺省参数必须从右往左依次来给出,不能间隔着给
2. 缺省参数不能在函数声明和定义中同时出现(一般规定在声明中出现)
3. 缺省值必须是常量或者全局变量
4. C语言不支持(编译器不支持)

三、函数重载

1.函数重载的含义

函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这
些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型
不同的问题。
注意三种区分依据:参数个数、参数类型、参数类型的排列顺序
不能通过返回值类型定义函数重载

#include
using namespace std;
// 1、参数类型不同
int Add(int left, int right)
{
	cout << "int Add(int left, int right)" << endl;
 	return left + right;
}
double Add(double left, double right)
{
 	cout << "double Add(double left, double right)" << endl;
 	return left + right;
}
// 2、参数个数不同
void f()
{
 	cout << "f()" << endl;
}
void f(int a)
{
 	cout << "f(int a)" << endl;
}
// 3、参数类型顺序不同
void f(int a, char b)
{
 	cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
 	cout << "f(char b, int a)" << endl;
}
int main()
{
 	Add(10, 20);
 	Add(10.1, 20.2);
 	f();
	 f(10);
	 f(10, 'a');
	 f('a', 10);
	 return 0;
}

2.函数重载的原理——名字修饰

c++中,当程序进行链接处理时,函数名字将会被修饰成【_Z+函数名字长度+函数名+类型首字母】。

如: int add(int a ,int b); 会被修饰为Z3addii
void test(char* arr,int i); 会被修饰为Z4testPci 大写P代表指针

由此也可知道返回值类型不同无法构成函数重载,因为只有返回值不同的函数在名字修饰后仍然相同。
不过如果两个函数通过参数类型、参数个数、参数类型顺序不同已经构成重载,此时这两个函数即使返回值类型不同也可以构成重载
简而言之:重载函数返回值类型可以不同,但不能只通过返回值类型不同来构成重载函数

全缺省函数和无参数函数重载时会产生二义性
如下代码中,编译器将无法决定调用哪一个Test函数。

void Test(int x = 0, int y = 0)
{
	cout << x + y << endl;
}

void Test()
{
	cout << "empty" << endl;
}
int main()
{
	Test();
	return 0;
}

四、引用

引用不是创建一个新变量,而是为变量取别名。
引用变量和实体变量共用一块内存空间。

int main()
{
	int a = 10;
	int& b = a;
	cout << a << ' ' << b << endl;
	cout << &a << ' ' << &b << endl;
	return 0;
}

C++学习(1)——命名空间、函数重载、缺省参数、引用、内联函数_第1张图片

1.引用的特性

(1)引用变量在定义时必须初始化,且引用不会为空

//不对引用初始化会报错
	int& a;
//引用的类型要与实体数据类型保持一致
//初始化
	int b=10;
	int& ra=b;

(2)引用一旦引用一个实体变量,就不可以再引用其他实体变量

(3)一个实体变量可以有多个引用

int main()
{
	int a = 10;
	int& ra = a;
	int& rra = a;
	cout << a << ' ' << ra <<' ' <<rra<< endl;
	cout << &a << ' ' << &ra <<' '<<&rra<< endl;
	return 0;
}

(4)引用变量相较实体变量的权限不可放大

int main()
{
	//权限不变
	int a = 10;
	int& ra = a;
	//权限放大(错误)
	const int b = 20;
	int& rb = b;
	//权限缩小
	int c = 30;
	const int& rc = c;
	//对常量也要保证权限不放大
	const int& r = 10;
	return 0;
}

2.引用的用途

(1)引用作为返回值

引用作为返回值的前提:引用的实体在函数调用结束后不会随着栈帧销毁。

	int& test()
	{
		static int a=10;
		return a;	
	}

如果我们对出了函数作用域就会销毁的对象使用引用返回,就会产生类似与c语言中使用野指针访问已释放的动态空间的结果:动态空间释放后,指向动态空间的指针未被赋值为NULL,而我们又再次使用该指针访问所指向的空间,将会得到随机数。因为在动态空间释放后,这片内存虽然未消失,但不再受保护,可能已被挪用,也可能保持不变。
例如:

	int& Add(int a, int b)
	{
		int c = a + b;
		return c;
	}
	int main()
	{
		int& ret = Add(1, 2);
		Add(3, 4);
		cout << "Add(1, 2) is :" << ret << endl;
		return 0;
	}

上述代码的最终结果是7
原因便是在第一次调用add函数时,系统在栈区开辟了空间作为函数栈帧,在函数栈帧内分配空间创建了变量c,然后c被赋值为3,c返回给引用接收。最后函数栈帧销毁,变量c也被销毁,ret所访问的空间不再受保护。
第二次调用add函数,系统在同样的栈区创建栈帧,然后在同样的位置再次创建变量c,c被赋值为7,返回c。这次没有引用接收返回值。但ret所访问的空间仍是c当初所在的空间。所以最后打印7。
此次所打印值是7,而不是随机值,完全属于碰巧现象,两次调用所创建的栈帧和局部变量位置重合。但我们绝不能对这种访问抱有侥幸心理。对于在栈桢销毁时,同时销毁的局部变量绝不能进行引用返回!

引用作为返回值的优势
第一,减少拷贝,提高效率。
第二,可以修改返回值。
内存空间的销毁意味着什么?
第一,内存仍然存在,只是该内存的所有不再是我们,我们所存入的数据不再受保护,可能会被系统覆盖。
第二,我们仍然能够访问该内存,只是读取的数据随机。

(2)引用作为参数

	void Swap(int& left, int& right)
	{
	   int temp = left;
	   left = right;
	   right = temp;
	}

引用作为参数的优势
第一,减少拷贝,提高效率。
第二,作为输出型参数,可以在函数内改变外部变量。

3.引用与指针的区别

  1. 引用在概念上是一个变量的别名,而指针存储一个变量地址。
  2. 引用在定义时必须初始化,指针没有要求
  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
    一个同类型实体
  4. 没有NULL引用,但有NULL指针(nullptr)
  5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
    位平台下占4个字节)
  6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  7. 有多级指针,但是没有多级引用
  8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  9. 引用比指针使用起来相对更安全

4.const引用补充

在进行强制类型转换、传值调用、函数返回值时会创建临时变量,而临时变量具有常性,我们可以粗浅地理解为临时变量被const修饰。
因此当我们使用引用执行会产生临时变量的操作时,应当使用const 修饰引用,保持权限的一致。

(1)强制类型转换

当我们对double类型变量进行int类型引用,编译器会因为类型不一致而报错

	double a=3.13;
	int& b=a

而当我们对变量a进行强制类型转换后,编译器仍会报错

	double a=3.14;
	int& b=(int)a;

原因就是在进行强制类型转换时,编译器并不是直接将a转换再赋值给b,而是隐性地拷贝a生成了一个临时变量,然后对临时变量进行转换在赋值给b。完成工作的临时变量便随即被释放。
而错误的原因便在于:临时变量具有常性。引用绝不可以放大权限,应当使用const修饰引用!
我们使用const修饰后,就编译成功。

	double a=3.14;
	const int& b=(int)a;

(2)传值调用

在我们学习c语言时,就已经知道所谓的传值调用就是将实参的一份拷贝赋值给形参。而在拷贝过程中就会产生临时变量,而为了满足临时变量的常性,我们常常需要使用常引用作为形参

(3)函数返回值

函数结束前的返回值也是通过生成临时变量实现的。当我们使用引用接收返回值时应注意const的使用

五、内联函数

被inline修饰的函数在编译时就会直接展开,所以在被调用时会直接执行而不创建栈帧。
这是一种以空间换时间的做法,省去栈帧的创建与销毁的过程,可节省时间,提高效率。
其实在C语言中的宏函数也可实现类似的作用,而c++中补充内联函数的原因在于宏函数的三个劣势。

1.宏函数的缺点:

1.宏函数在预处理阶段会直接进行替换,无法进行调试工作
2.宏函数没有类型安全检测
3.宏函数极易写错

2.内联函数的注意事项:

第一,内联函数只是向编译器的一个请求,编译器可自行决定是否执行内敛操作;
第二,递归函数,或函数体归于大(一般超过十行)的函数不适合使用内联,编译器也不会执行内联。因为对较大的函数执行内联,会因为展开函数而造成代码膨胀,而代码膨胀的后果就是可执行文件过大。
第三,在定义内联函数时不支持声明与定义的分离。因为在多文件编程中,一旦对声明与定义分离的函数加上inline进行内敛操作,编译器就不会在汇编阶段形成内联函数对于的符号表。这将导致多个目标文件中的内函数在进行链接时找不到对应的定义,最终产生链接错误。
这对于函数体过大的函数同样适用。即使我们错误的对不该使用inline的函数进行内联,并且编译器没有通过这个请求。但在汇编时,编译器还是会因为函数前的inline而不在生存其对应的符号表。
因此内联函数绝不可以声明与定义分离
第四,的不过版本中编译器无法实现内联操作。因为内联执行的函数展开与debug版本保存编译信息的目的相悖。

你可能感兴趣的:(C++语言学习,c++,学习,算法)