内容:
本文由浅入深,要好好看呦~
1.前言
在C++中,引用的作用主要包括以下几个方面:
- 作为函数的参数:当函数需要修改其参数的值时,可以将引用作为参数传递给函数。这样,函数对引用的操作实际上是对原始变量的操作,而不是对原始变量的副本进行操作。这种方式可以避免数据的拷贝,提高效率。
- 简化指针操作:通过引用,可以更简洁地操作指针。例如,可以将一个数组的引用传递给函数,使得函数可以直接访问数组元素,而不需要使用指针来解引用。
- 作为函数的返回值:可以将引用作为函数的返回值。当函数返回一个临时对象时,可以通过引用返回该临时对象,避免了返回临时对象所占用的存储空间被重复使用的情况。
- 为变量起别名:引用可以为变量起一个别名,即引用的名字是目标变量的另一个名字。通过引用,可以更方便地访问和修改变量的值。
- 处理复杂的数据类型:对于复杂的数据类型,如类或结构体,可以通过引用访问其成员变量或成员函数,使得代码更简洁易读。
总的来说,C++中的引用是一种非常有用的工具,它可以提高代码的效率和可读性,简化指针操作,处理复杂的数据类型等
2.初识引用
所谓引用,其实并不神秘,他变量的别称。
例如一个叫张三的人,在家里的时候父母会叫他阿三,阿三和张三其实都是他,只不过名字不同而已。而在c++中给变量齐别称的语法是:
类型名& 别称 = 某个对象;
让我们来看看一段具体的代码:
int main()
{
int a = 1;
int& b = a;
cout << b << endl;//输出1
b++;
cout << a << endl;//输出2
return 0;
}
引用和指针是类似的,在这段代码中,b是a的引用,我们调用b其实就是调用a,让b++其实就是再让a++,所以说b其实就是类似于一个指向a的指针,但是要比指针调用方便。
让我们看一个实际的例子:
在c的写法中,用一个函数交换两个值是这么写的:
void Sweap(int* a, int* b)
{
int tem = *a;
*a = *b;
*b = tem;
}
int main()
{
int x = 1, y = 3;
Sweap(&x, &y);
return 0;
}
但是学习了c++以后我们可以这么写:
void Sweap1(int& a, int& b)
{
int tem = a;
a = b;
b = tem;
}
int main()
{
int x = 1, y = 3;
Sweap1(x, y);
}
在这个函数中,a 和b是分别是x和y的别称,实际上调用的就是x和y。
和c的代码相比,它的调用就更方便一些。而祖师爷在设计引用的初衷之一就是对指针使用较复杂的场景进行替换。
3.引用的性质
引用具有以下性质:
- 一个变量可以有多个引用
- 引用必须初始化
- 引用不可改变指向
1)一个变量可以有多个引用
变量的引用数量理论上是无限的,多个名字的存在不冲突,它既可以叫x也可以去叫y,名字数量完全取决于你的主观意愿。
int main()
{
int a;
int& c = a, d = a, e = a;
}
2)引用必须初始化
定义普通变量时,我们可以先定义它的名字而不给他赋值,但是在引用的定义的时候必须要有指向,否则就会报错!
int main()
{
int a;
int& c; //错误用法
c = a;
}
3)引用指向不可改变
引用的初始化是唯一的,它初始化以后只能是指向这个量,这也是为什么引用不能完全代替指针(例如数据结构的链表),这样设计是为了避免以下情况:
int main()
{
int a = 1, b = 3;
int& c = b;
c = a;
}
在这种情况下第三个语句可能会产生两个意思:
- 将c的指向改变成a。
- 将a的值赋值给c也就是b。
而这就会产生歧义,因此在设计的时候祖师爷就规定了:引用的指向不能改变。所以上述代码的意思就是第二种情况。
4.引用的作用
1)引用方便,性价比高
让我们编写一个函数来检验指针和引用的效率
struct A {
int a[10000]; };
void TestFunc1(A a) {
}
void TestFunc2(A& a) {
}
void TestRefAndValue()
{
A a;
// 以值作为函数参数
size_t begin1 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc1(a);
size_t end1 = clock();
// 以引用作为函数参数
size_t begin2 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc2(a);
size_t end2 = clock();
// 分别计算两个函数运行结束后的时间
cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
结果如下:
我们可以发现引用的效率比指针快了很多倍,因此引用的性价比要更高一些。
2)用做返回值,便于修改
让我们先看一个错误用例:
int& ret()
{
int re = 0;
return re;
}
int main()
{
int& c = ret();
return 0;
}
这个代码乍一看可能觉得没有问题,但是仔细想想就会明白,在函数里re只是一个临时变量,出了作用域就会销毁,但是用于接收的引用c却指向了已经销毁的re。
在一些编译器里面它并不会立刻体现出错误来,但是当你再调用其他函数时,被销毁的空间可能会被重新利用,从而改变了正确的值。
正确的引用方法是,返回的值应是像一个全局变量或者malloc的一个这种不会轻易销毁的值。
5.指针和引用的深层比较
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
但在底层实现上它是占用空间的。
在底层逻辑上,引用是用指针来实现的,让我们来看看他们的反汇编代码的执行逻辑:
经过对比,我们可以发现这两个的逻辑是一模一样的,所以尽管两者在使用上不同,但引用和指针其实是一家人。
其他不同的如下:
- 引用概念上定义一个变量的别名,指针存储一个变量地址。
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何 一个同类型实体
- 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32 位平台下占4个字节)
- 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
纸上得来终觉浅,绝知此事要躬行。
感谢各位的观看,点个关注再走吧ლ(°◕‵ƹ′◕ლ)