文章目录
- Ⅰ 命名空间
- 1. 命名空间域的产生
- 2. 命名空间域的定义
- 3. 命名空间域的使用
- Ⅱ 缺省参数
- 1. 缺省的概念
- 2. 缺省的分类
- 3. 声明和定义不能同时存在缺省参数
- Ⅲ 函数重载
- 1. 函数重载概念
- 2. 编译器如何实现函数重载
- Ⅳ 引用
- 1. 引用的概念
- 2. 引用的特性
- 3. 引用的使用场景
- 4. 引用和指针的区别
- Ⅴ 内联函数
- 1. 内联函数的概念
- 2. 内联函数的特性
Ⅰ 命名空间
1. 命名空间域的产生
- 在开发过程中,常用的名字也就那么多。在同一个域内,如果自己定义的变量和别人已定义好后的变量冲突了,此时就必须更改名字才行。
int a = 10; // 张三在一个区域内定义的一个变量 a
// 若干行代码后......
int a = 5; // 李四在同一个区域内定义的同名变量,此时两个人定义的变量就产生了冲突
- 上述情况就被叫做命名冲突,而且如果你想更改自己的变量名就又不知道可能会和谁的同域变量名冲突,毕竟取过名的都知道好用的名字大概率给人取走了。
- 此时就需要使用 namespace 关键字 来划分一块属于自己的命名空间域,在这块区域内就可以对标识符的名称进行本地化,以避免命名冲突或名字污染。
2. 命名空间域的定义
1. 命名空间的定义语法
namespace 该命名空间的名字
{
// 在这里可以自己定义命名空间的成员
// 命名空间的成员包括 变量、函数、自定义类型
}
2. 命名空间的定义示例
namespace T1 // T1 是我自己取的这个空间域的名字
{
int a = 10; // 可以定义变量
int b = 5;
int Add(int x, int y) // 可以定义函数
{
return x + y;
}
struct Node // 可以自定义类型
{
int data;
struct Node* next;
};
}
3. 命名空间域的使用
如果想使用上面示例的命名空间域 T1 内的成员的话有三种方法:
1. 命名空间名 + 域作用限定符 + 命名空间内的成员
- 域作用限定符 :: 使用 :: 这个域作用限定符能够指定到某个区域内寻找该变量。
int main()
{
printf("%d\n", T1::a); // 指定使用 T1 这块区域内的 a 变量
struct T1::Node head;
}
- 注意:如果某个变量在全局和局部都有定义,一般来说会优先使用局部变量,也可以使用域作用限定符指定使用全局变量。
int k = 10; // 全局变量 k
int main()
{
int k = 5; // 局部变量 k
printf("%d\n", k); // 5 优先使用局部的变量
printf("%d\n", ::k);// 10 域作用限定符左边什么都没有表示指定使用全局变量
}
2. 使用 using 将命名空间中的某个成员引入
- 语法:using 空间域名::空间域的成员名,这样之后使用该成员都是到指定的命名空间域,但是其他的成员还是要用第一种方法才能使用。
using T1::a; // 之后的代码 a 都是指定使用 T1 这块空间的
int main()
{
printf("%d %d\n",a, T1::b);
}
3. 使用 using namespace 命名空间名 引入
- 还是优先使用局部区域的变量,如果该变量不在局部区域,则去命名空间域内搜索该变量。
- 该方法能够让命名空间域内的成员像普通变量一样直接使用,不需要加上域作用限定符。
using namespace T1; // 可直接使用 T1 这块空间内的成员
int main()
{
printf("%d %d\n",a ,b);
printf("%d\n", Add(a, b));
}
Ⅱ 缺省参数
1. 缺省的概念
- 缺省参数是声明或者定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参,则采用该形参的缺省值,否则使用指定的实参。
2. 缺省的分类
- 缺省参数分为全缺省和半缺省两类。
1. 全缺省参数
- 函数的所有形参都带有一个默认的缺省值。
2. 半缺省参数
- 半缺省参数指的是部分缺省,而不是刚好就缺一半。
- 半缺省参数只能从右往左依次给出,且不能间隔着给。
3. 声明和定义不能同时存在缺省参数
- 如果一个函数的声明和定义同时存在缺省参数,且声明和定义提供的缺省参数恰好值不同,此时编译器无法判断该使用谁的缺省值。
- 所以,缺省参数必须只放在声明。
Ⅲ 函数重载
1. 函数重载概念
- 在同一作用域中声明几个功能类似的同名函数,这些函数的形参列表 (形参的个数、类型、类型顺序) 不同,用来对不同的数据类型实现相同的功能。
- 函数重载与函数的返回值无关,只与函数的形参列表有关。
1. 参数类型不同
2. 参数个数不同
3. 参数类型顺序不同
- 本质还是类型不同
2. 编译器如何实现函数重载
- 在 C 语言中,是利用函数名去找函数的地址,如果有同名函数,就没法区分。
- 在 C++ 中,使用函数名修饰规则,在函数名中引入参数类型 (各个编译器自己实现)。
- 现在主要讲解在 Linux 下的 g++ 编译器是如何实现在函数名中引入参数类型。
Linux 下 g++ 编译器的函数名修饰规则
- _Z + 函数名长度+ 函数名 + 参数类型1 + 参数类型 2 + 参数类型 n。
void Print(char a, int b);
// 上述函数使用函数名修饰规则就可以变成 _Z + 5 + Print + c + i → _Z5Printci。
// 使用修饰后的函数名,编译器就可以找到使用者具体是要调用哪个函数
- 如果形参是指针则将指针类型修饰成 p + 该指针指向的数据类型。
void Func(int* a); // int* 就可以修饰成 pi,整个函数可以修饰成 _Z4Funcpi
void Func(char* b); // char* 可以修饰成 pc,整个函数可以修饰成 _Z4Funcpc
Ⅳ 引用
1. 引用的概念
语法:数据类型& 别名 = 原变量名
作用:给变量取别名,对这个别名进行得操作能够影响到原变量的内容。
示例
注意:引用类型必须和引用实体是同种类型的
2. 引用的特性
1. 引用在定义时必须初始化
- 也就是得有原名才能取别名。
int& ra; // × 如果没有原名,那取别名还有什么意义
2. 一个变量可以有多个引用
- 即一个变量可以取多个别名,这些别名指的都是该变量。
int a = 10;
int& ra = a;
int& rra = ra; // ra 和 rra 都是 a 的别名,对这两个别名进行操作都会影响到原变量 a
3. 引用定义后,不能再改变指向
- 鲁迅已经是周树人的别名了,它就不能再是其他任何人的别名。
int a = 10;
int b = 20;
int& ra = a; // √
ra = b; // × ra 已经是 a 得别名,不能再做其他人得外号了
3. 引用的使用场景
1. 做函数的形参
- 在 C 语言中,函数形参如果想要改变实参,那么只能使用址传递的方式。实参需要传地址,而形参需要用指针接收。
- 在 C++ 中就不用那么麻烦了,将形参定义为引用类型,形参是实参的别名,能直接修改实参的值。
- 而如果想要传一级指针变量的地址,那么在 C 语言中就需要使用二级指针来接收,引用就只需要使用对一级指针的引用即可。
2. 做函数的返回值
- 局部变量不能用引用返回。
int& func()
{
int a = 10; // a 是局部变量,出了该函数就会被销毁
return a; // 如果返回 a 的别名,就相当于野引用
}
int main()
{
int& ret = func(); // ret 是 a 的别名的别名,ret 成了野引用
}
- 静态变量 / 全局变量 / 堆上变量 可以用引用返回。这几种类型的变量出了函数的作用域也不会被销毁。
// 返回静态变量的别名
int& func1()
{
static int a = 10;
return a;
}
// 返回全局变量的别名
int b = 20;
int& func2()
{
return b;
}
// 返回堆上变量的别名
int& func3(int*& arr)
{
// ...
return arr[0];
}
int main()
{
func1();
func2();
int* arr = (int*)malloc(4 * sizeof(int));
func3(arr);
return 0;
}
4. 引用和指针的区别
1. 从语法的角度看
- 引用是别名,不用开空间。指针是地址,需要开空间存地址。
- 引用必须初始化,指针可以选择是否初始化,所以指针更容易出现野指针。
- 引用不能改变指向,指针可以。
- 没有空引用,引用更安全,不容易出现野引用。有空指针,容易出现野指针。
- 在 sizeof 中引用结果为引用类型的大小,指针始终为 4 / 8 字节。
- 有多级指针,但没有多级引用。
- 访问实体方式不同,指针需要显式解引用,引用是编译器自己处理。
2. 从底层的角度看
- 从汇编层面上看,没有引用,都是指针。引用编译后也会转换成指针。
Ⅴ 内联函数
- 在调用函数的时候是会开辟栈帧的,如果某一个函数非常频繁的被调用,那么栈空间的压力就会非常大。
- 此时就出现了内联关键字 inline,将栈帧的压力转移。
1. 内联函数的概念
- 在编译的时候,编译器会在调用内联函数的地方将内联函数的实现逻辑展开。
- 没有函数调用建立栈帧的开销,内联函数能够有效提升程序运行的效率。
2. 内联函数的特性
内联函数的优缺点
inline 内联函数是一种以空间换时间的方式,在编译阶段,会用函数体替换函数调用。
- 缺点:可能会使编译好的可执行程序变大。假设一个内联函数有 50 行,有 1万 个调用该函数的地方,那么这个文件就会多出 50 万行文本的大小。
- 优点:不用每次调用都开辟栈帧,可以提高程序的运行效率。
何时使用内联函数
- 函数体内容小于等于 75 行时可以选择使用内联函数。
不建议声明和定义分离
- 如果函数声明有 inline 而函数定义没有,就可能导致链接错误。
- 因为inline被展开,就没有函数地址了,链接就会找不到。