持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第13天,点击查看活动详情
前言
引用是C++的一个重难点,本文就来分享一波笔者对于引用的学习经验和心得,应该算是初级内容(是我太菜),更加深入的内容待笔者修炼有成再续。
笔者水平有限,难免存在纰漏,欢迎指正交流。
引用
引用概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
这里定义一个变量int a = 10
,那么a是什么?a是变量吗?不是的,变量是具有特定标识符的一定大小的内存空间,而a只是变量的标识符(也就是名称),我们要直接访问一个变量只需要用它的标识符即可。
那再定义一个引用,实际上引用也是一种类型,但是并不会新开辟空间来存储,引用只是标识符罢了,用来标识对应引用的变量。
我们看看上图,定义引用ra引用变量a,这样一来ra和a都是这块空间的标识符,都代表了这块空间,所以对ra和a的操作其实就是对这块空间的操作。
总的来说引用就是同一个变量的别名。
引用特性
- 引用类型必须和引用实体是同种类型的
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
例子:
int main()
{
int a = 10;
int& ra = a;
int& rra = a;
printf("%p %p %p\n", &a, &ra, &rra);//同一变量的三个标识符
return 0;
}
JAVA中引用完全替代了指针,并且可以改变引用实体,而C++中引用没有替代指针,并且不能改变引用实体。对于C++,一方面是它要兼容C,另一方面是C++中引用并不是为了替代指针而创作出来的,并不能解决所有问题,该使用指针的地方还是得用指针。同时,C++中指针和引用的作用既有重叠部分,又有不重叠部分。
使用场景
作参数
下面两段代码作用一样,都是交换两变量的值,在函数里面能直接修改到函数外的实参内容,只不过指针是通过解引用间接访问实参的,而引用则可以直接访问实参。
void Swap(int* left, int* right)
{
int temp = *left;
*left = *right;
*right = *temp;
}
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
作返回值
引用还可以作为返回值
int Count()
{
static int n = 0;
n++;
return n;
}
int& Count()
{
static int n = 0;
n++;
return n;
}
以下的内容需要对函数栈帧内容有一定的理解,不太熟悉的可以点这里读一读笔者之前写的函数栈帧的文章:
[深入浅出C语言]深入函数栈帧 - 掘金 (juejin.cn)
先来点前置准备:
内存空间销毁意味着什么?
空间还在吗?——在,只是使用权已经不是我们的了,我们存储的数据不被保护了。
我们还能访问吗?——能,只是我们读写的数据都是不确定的了。
非引用版本
n是静态局部变量,存储在静态区,不会随着Count函数栈帧的销毁而销毁,Count函数这里返回的是n的值,而且是通过一个临时变量作为媒介返回的,因为把n的值返回到上一层函数中的时候Count函数栈帧已经销毁了,只能通过一个媒介事先拷贝一份值传递到上一层函数中去。
如果n没有static的修饰而作为局部变量,此时又会是怎样的情况呢?其实没什么差别,只是变量n的存储位置不同导致生命周期不同了,依旧是要开辟一个临时变量来拷贝传递返回值。
引用版本
如图,ret其实也只是拿到了n值的拷贝,n在静态区,Count函数栈帧销毁对n没有影响,但是如果n没有static的修饰而作为局部变量的情况下n就会随栈帧销毁而销毁,此时n对应的空间我们没有使用权了,再用别名访问里面的值就不确定了。
用引用接收引用
如果我们改成这样结果会怎样?
int& Count()
{
int n = 0;
n++;
return n;
}
void Func()
{
int x = 100;
}
int main()
{
int& ret = Count();
cout << ret << endl;
cout << ret << endl;
Func();
cout << ret << endl;
return 0;
}
Count函数返回的本来就是n的别名,如果只是用int变量接收的话就是接收拷贝值,但如果是用int&引用来接收的话就是给别名起了个别名,这样一来ret就是n的别名了。
第一次使用cout打印值发现和原来一样,第二次打印时就是个随机值了,第三次打印仍是随机值,这是因为Count函数栈帧被销毁后虽然原来的空间和值还在,但是不受保护了,有可能被覆盖,特别是再调用别的函数会创建新的栈帧。
那我们把n、x和ret的地址打印出来看看。
int& Count()
{
int n = 0;
n++;
cout << &n << endl;
return n;
}
void Func()
{
int x = 100;
cout << &x << endl;
}
int main()
{
int& ret = Count();
cout << ret << endl;
cout << ret << endl;
Func();
cout << ret << endl;
cout << &ret << endl;
return 0;
}
发现地址都相同,说明对应的是同一块空间。
结论
函数返回时,如果出了函数作用域返回数据对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回而非引用返回,因为此时引用返回的结果是未定义的。
如下是正确用法:
int& Count()
{
static int n = 0;
n++;
return n;
}
int Count()
{
int n = 0;
n++;
return n;
}
以上就是本文全部内容,感谢观看,你的支持就是对我最大的鼓励~