前言
在C++入门上我们简单的介绍了c++这门语言,了解了命名空间、缺省函数等C++特性这节课我们将继续深入了解C++的引用、内联函数、auto等特性。
1 引用
引用是一个很好的操作工具,他就类似给一个空间(变量)取别名,可以使两个不同的名字指向同一块空间,换句话说,引用是指针改进版,在后续学习中,有 80% 的场景都会使用引用而非指针。
比如李逵,在家称为"铁牛",江湖上人称"黑旋风"
int a = 10;
int* pa = &a; //指针
int& ra = a; //引用
上面代码段中的 ra 与 a 都表示同一块空间,而 *pa 和 a 也表示同一块空间;可以简单把引用理解为一个智能版指针,会自动解引用,使用起来更方便
引用的底层任然是指针
1.1 引用的特性 | 规则
- 引用必须初始化,当一个引用变量被创建时,必须存在其所代表的变量
- 一个变量可以存在多个引用,就像 土豆 可以有多个别名
- 当引用初始化后,无法再代表其他变量,每个引用一生只为一人
- 不存在多级引用,当 引用b 代表 引用a 时,实际上就是在代表 引用a 所代表的变量 a
char a = 'A';
char b = 'B';
//1、引用必须初始化
char& ra = a; //正确
char& rx; //错误,必须初始化
//2、一个变量可以有多个引用
char& ra = a;
char& rra = a;
char& rrra = a; //没有问题,一个变量允许存在多个引用
//3、引用无法改变指向
char& ra = a;
char& ra = b; //错误,引用一旦确立后,就无法再改变其指向
ra = b; //这个没问题,实际结果为 a = 'B' 即将 b 的内容赋值给 a
//4、不存在多级引用
char& ra = a;
char&& b = ra; //非法,不存在多级引用
char& b = ra; //合法,实际结果为 char& b = a;
注意: 引用不能像指针那样随意使用,引用也不存在指针多级指向的功能
比如上图这种情况对于引用来说是不存在的
1.2 常引用
对于指针和普通引用来说具有修改原数组的能力,指向/引用 常量时会有权限问题。
众所周知,对于程序来说,存在几个区域:栈、堆、静态区等等,我们使用的常量位于数据段或代码段中,常量具有可读不可修改的特性,当我们使用普通指针或引用指向常量数据时,会引发错误
解决方案也很简单,在指针或引用前加入常量修饰符(权限平移)
1.3 引用的应用场景
- 作参数
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
- 作返回值
int& Count()
{
static int n = 0;
n++;
// ...
return n;
}
当引用做返回值时,接收到的变量就是函数返回时的本体,比如全局数组 arr ,此时对返回值做出修改,就是在改变数组 arr
引用返回很强大,但也不能随便使用,引用返回一般用于生命周期较长的变量,即函数结束后不被销毁的变量,如果使用生命周期短的变量作为引用返回值,那么结果是未定义的
int& func(int n)
{
int val = n;
return val; //结果未定义
}
//val是函数 func 中的局部变量,当函数结束后,变量就被销毁了
//此时可能得到正确的结果(编译器未清理),也可能得到错误的结果(编译器已清理)
//因此说结果是未定义的
//可以看到下图中相同语句出现两种结果
注意:引用在初次学习中可能感觉其没有什么价值但是在你深入了解后可能回放下偏见,比如:
- 在传参和引用返回时,只需要给原来取一个别名,这样这样可以避免要改实参的值传指针麻烦。
- 提高效率,在传参的过程中可能需要传一个对像,有些对象可能很大,反复传参效率极低,这样通过引用就可以传一个别名,避免一个对象的反复复制。
1.4 小结
引用是比较重要的特性,需要小结一下:
- 引用在概念上是给变量取别名,而指针是新开一块空间指向变量
- 引用必须初始化,指针可以不初始化
- 引用无法改变指向,指针可以
- 不存在空引用,但存在空指针
- 引用大小为所代表变量的大小,而指针大小为 4/8 字节
- 引用+1等价于变量+1,指针+1则表示指向位置向后偏移一个单位
- 引用不需要解引用,指针使用前需要解引用
- 引用使用起来更安全、更方便
- 以后涉及需要改变原变量值时,优先考虑使用引用,特殊场景下,仍然需要使用指针
引用与指针互不冲突,两者都有自己适用的领域,比如在实现链表时,必须使用指针,因为引用无法改变指向,而链表需要频繁的进行改链操作
2. 内联函数
内联函数主要用来替换宏函数,应为宏函数存在许多坑,例如:
- 在很多场景下使用复杂
- 宏不能进行调试,宏是直接进行替换的
- 没有内置安全检查
- 尽量用const和enum替换宏定义变量
- 使用内联函数inline替换宏函数
- 总之宏很危险尽量少用
内敛函数实际上就是在函数的实现前加上inline修饰,此时函数会被编译器标记成为内联函数
//此时的 Add 函数就是一个内联函数
inline int Add(int x, int y)
{
return x + y;
}
内联函数的特点:
- 在Debug模式下,函数不会进行替换,可以进行调试
- 在Realse模式下,函数会像宏一样进行展开,提高运行效率
- 内联函数弥补了宏函数的不足,同时吸收了宏函数的优点
内联函数可以全面替换宏但是使用时也有很多需要注意的地方 - 频繁使用内联函数,编译出来的可执行程序会更大,因为代码会变多,但运行速度更快
- 调用内联函数时,是否展开取决于编译器,如果内联函数展开后会影响性能,那么编译器有权不展开内联函数
- 内联函数适用于代码行数较少,且被频繁调用的小函数
- 内联函数不建议声明和定义分开,因为内联函数不进入符号表,因此可能产生链接错误,推荐在声明时就顺便将函数定义,头文件展开时,将内联函数一起包含
3. auto关键字
这个是 C++11 中的新特性,auto 关键字能直接识别目标变量类型,然后自动转换为相应类型
int a = 10;
int* b = &a;
auto aa = a; //此时 aa 为 int
auto bb = b; //此时 bb 为 int*
除了自动识别外,我们还可以指定转化类型
int a = 10;
auto* pa = a; //指定 pa 为 int*
auto& ra = a; //指定 pa 为 int&
注意:== auto 不能用于数组,auto 也不能当作参数类型==
4. 范围for
这个c++中的新特性,范围for会自动循环拷贝,自动判断范围、自动结束等特点,用起来非常方便
int main()
{
int arr[] = { 1,2,3,4,5,6,7 };
for (auto x : arr)
{
cout << x << " ";
}
return 0;
}
范围for配合auto自动识别类型,写出来的循环配合引用可以轻而易举的给数组赋值
int main()
{
int arr[] = { 1,2,3,4,5,6,7 };
for (auto& x : arr)
{
x *= 2;
cout << x << " ";
}
return 0;
}
5. nullptr指针空值
这是c++11中新增的补丁,应为在设计c++时,指针空值NULL出了点问题,NULL可能被编译器直接识别为0而非void*
#include<iostream>
using namespace std;
void func(int)
{
cout << "参数为整型 0" << endl;
}
void func(void*)
{
cout << "参数为指针 void*" << endl;
}
int main()
{
func(0);
func(NULL);
return 0;
}
可以看到NULL并没有被识别为空指针,因此退出了nulltpr这个补丁,专门为指针滞空
注意:
- nulltpr是作为关键字加入的并不需要头文件
- 在后续学习中,为了确保程序的健壮性,建议指针置空使用 nullptr 而非 NULL
总结
以上就是关于 C++ 入门基础的全部内容了,学习了 C++ 中的各种新特性,如 引用、内联、auto等等C++ 的修行之路才刚刚开始,我们已充满信心!
如果你觉得本文写的还不错的话,期待留下一个小小的赞👍,你的支持是我分享的最大动力!
如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正
本文部分引用于北海的这篇文章
链接:https://blog.csdn.net/weixin_61437787/article/details/128848466