当前位置:首页 » 《随便一记》 » 正文

【详解C++中的引用】

18 人参与  2023年05月04日 19:13  分类 : 《随便一记》  评论

点击全文阅读


文章目录

一、什么是引用二、引用规则三、引用特性四、使用场景1.做函数参数2.做返回值五、常引用 ps:为什么类型转换会产生临时变量? 六、引用和指针的区别总结


一、什么是引用

引用就是给一个变量取别名。

注意:这个引用不会新开辟一块空间,而是和原来的变量公用一块空间。

举个例子:李逵,在家称为"铁牛",江湖上人称"黑旋风"。
在这里插入图片描述

二、引用规则

引用规则:引用实体类型+&+引用别名 = 引用实体。

比如下面:

int main(){int a = 10;//引用int& ba = a;ba = 20;printf("%d ", a);return 0;}

上面代码为例:

引用对象类型是int + & + 引用别名(ba) = 引用对象(a)

C++中的 “&”符号跟类型在一起是不在是取地址,而是”引用“。

现在ba这个就是a的别名,和a是同一块内存空间,改变了ba的内容,就等于改变了a的内容。

同时,一个变量可以有多个引用。

相当于一个人可以有多个别名一样。

就像是:假如你在家被叫做小红,在外面被叫燕燕。你的妈妈叫小红吃饭,然后小红去吃饭了,那燕燕是不是也吃了,你是不是也吃了呢?

三、引用特性

1.引用类型必须是和引用实体是同一类型。

比如:
在这里插入图片描述
报错的原因是:引用对象的类型和引用实体的类型不一致。

引用的对象a是int类型,而给它取别名却是double类型,这是不允许的。

2.引用在定义时必须初始化

比如:

在这里插入图片描述
这也是不允许的。

3. 引用一旦引用一个实体,就不能引用其他实体。

比如:

int main(){int a = 10;//引用int& pa = a;int x = 20;pa = x;return 0;}

这段代码,有错误吗?

没有错误,但是pa = x这行代码,并不是引用更改,因为前面已经提过:

引用一旦引用一个实体,就不能引用其他实体。

所以这行代码的意思是:将x 的值赋值给pa。

此时a的值是20了。

四、使用场景

1.做函数参数

引用可以做函数的参数,作用相当于指针,可以改变变量本身。

比如:

void Swap(int& a, int& b){int tmp = a;a = b;b = tmp;}int main(){int a = 10;int b = 20;printf("交换前:a = %d ,b = %d\n", a, b);Swap(a, b);printf("交换后:a = %d ,b = %d\n", a, b);return 0;}

传参传一个实参过去,但是接收时使用引用,此时该引用就是形参的别名,形参的别名改变会改变形参本身。

这个是使用引用一个好的地方。

做返回值的作用:
1.做输出型参数,节省空间。
我们传形参时,用引用接收,也不再需要使用地址,不再需要开辟指针空间来接收,直接改变引用即可改变形参。

2. 提高效率,当传过来的形参是大对象/深拷贝对象时,能够极大地提高效率。

2.做返回值

先看不用引用做返回值,用普通的返回值:

案例1,错误代码int test(){int n = 10;n++;return n;}int main(){int ret = test();printf("%d\n", ret);return 0;}

这样的返回值是我们常见的返回值。
这里需要提一点:
test函数并不是直接返回n的。

因为test函数在调用结束后会销毁它所在的栈帧,连同n会一起销毁,所以编译器会先保存n的值到一个寄存器中,再销毁栈帧,然后返回寄存器的值给ret。

过程如下:
在这里插入图片描述
所以就出现了一个问题。

当我们用上面的代码,返回的是n的引用(别名)时,这就不安全了。因为返回的是n的引用,不会创建临时空间给n,而是直接返回n。 但是返回之后n所在的函数栈帧会被销毁,所以连同n一起销毁了,但是此时ret是n这块已经不属于自己的空间的拷贝,所以ret是违法的。

打印出来的ret,可能是随机值,也可能是n原来的值,但如果是n原来的值,这只是侥幸,因为n原来的空间暂时没有被使用。但如果n这块空间被其他函数使用了,此时ret就有可能是随机值。

所以在上面的例子中,不能使用引用来返回。

再看下面的例子:

案例2 ,正确代码int& test(){static int n = 10;n++;return n;}int main(){int ret = test();printf("%d\n", ret);return 0;}

注意,n是被static修饰过的变量。此时可以用引用进行返回了,因为函数test的销毁不会销毁n,n是在静态区开辟的空间,而函数是在栈区开辟的空间,两者互不影响。

返回n的引用是绝对安全的。

再看下面:

案例3,错误代码int& test(){int n = 10;n++;return n;}int main(){int& ret = test();printf("%d\n", ret);return 0;}

此时与案例二相似,但是n不是被static修饰过的,而且ret也是引用,相当于返回n的引用后,再用引用接收n的引用,此时ret也还是n的别名,而n是在栈区开辟的空间,销毁后,此时n的空间不再属于自己,打印ret,相当于打印不属于自己的n,这是违法的行为。

当这段栈空间被其他东西用之后,n的值可能是随机值了。
在这里插入图片描述

再看下面的案例:

案例4,正确代码int& test(){static int n = 10;n++;return n;}int main(){int& ret = test();printf("%d\n", ret);return 0;}

此时n 是被static修饰过的,所以栈帧空间的销毁不会影响n,打印ret(ret是n的引用),会正确打印出来。

总结:
1.使用引用做返回值时,引用返回不会开辟临时空间保存返回值
2.而不管改变量是在栈区还是在静态区,不用引用都会开辟临时空间保存返回值。
但是使用引用必须保证返回值是绝对的安全。

五、常引用

1.在引用的过程中,权限不能放大,只能缩小或平移。
1.在引用的过程中,权限不能放大,只能缩小或平移。
1.在引用的过程中,权限不能放大,只能缩小或平移。

案例1:
错误示例

int main(){//权限不能放大,不正确const int a = 0; // 常变量,不可修改int& b = a; // 起了一个别名,必须也是常引用return 0;}

a是一个被const修饰后的常变量,不可修改。
而b是a的引用,此时b并没有被const修饰,意味着b可以修改,但是a已经不能被修改了,b是a的别名同样不可修改,这是规定。

所以权限不能放大

案例2:
正确实例

int main(){const int a = 0; // 常变量,不可修改//权限的平移const int& b = a;return 0;}

变量a 和引用b都被const修饰了,它们的权限是一样的,所以权限可以平移。

案例3:
正确案例:

int main(){//权限可以缩小int g = 0;const int& h = g;return 0;}

g是一个变量,h是g的引用,但是h被const修饰后,意味着h不能修改。
所以这是一个权限的缩小。

2.只要发生类型转换,都会产生临时变量。
而临时变量不可修改,具有常性。

2.只要发生类型转换,都会产生临时变量。
而临时变量不可修改,具有常性。

2.只要发生类型转换,都会产生临时变量。
而临时变量不可修改,具有常性。

类型转换包括:隐式类型转换,截断,整型提升等。

比如:

int main(){double d = 1.1;int a = d;return 0;}

这里会发生隐式类型转换。d是double类型拷贝给a ,会在中间生成一个int类型的临时变量,然后把d放入该临时变量中,然后该临时变量再拷贝给a。所以把d拷贝给a是不会改变d本身的。
在这里插入图片描述
再看下面的代码:

错误示例int main(){double d = 1.1;int& a = d;return 0;}

为什么a不能作为d的引用?
本质上还是权限放大的问题。
d是double类型的变量,a是int类型的引用,中间创建一个int类型的临时拷贝,而临时拷贝具有常性,给了a之后,a是引用,不具有常性,就相当于权限的放大。

正确示范int main(){double d = 1.1;const int& a = d;return 0;}

加了const修饰后,引用a就具有了常性,相当于权限的平移了。

ps:为什么类型转换会产生临时变量?

看下面的例子:

int main(){int a = 1;double b = 1.1;if (b > a){cout << "hehe" << endl;}return 0;}

此时会打印hehe,因为表达式左右两边如果类型不同,会发生整型提升或截断。

这里b和a相比,a会发生整型提升到double类型再与b比较。

而整型提升的过程会生成一个临时变量,这个临时变量就是a提升后的结果(并不是a本身提升)。

所以比较前后a和b的值都不会发生改变。

六、引用和指针的区别

从汇编的角度看,引用的底层也是用指针实现的。
在这里插入图片描述

但是在语法层面,引用不开空间,指针会开辟一块空间。

总结

引用和指针的不同点:

引用概念上定义一个变量的别名,指针存储一个变量地址。
int main(){int a = 10;//指针存储a的地址int* pa = &a;//b是a的引用(别名)int& b = a;return 0;}
引用在定义时必须初始化,指针没有要求。
int main(){int a = 1;//引用必须初始化int& b = a;//指针可不初始化int* p;return 0;}
引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。
int main(){int a = 10;int b = 20;int& d = a;//指针可以改变指向的对象int* p = &a;p = &b;//但是引用不可改变指向的对象//这个并不是改变引用d的实体对象,而是把b拷贝给dd = b;return 0;}
没有NULL引用,但有NULL指针。
int main(){//可以存在空指针int* p = NULL;//不存在空引用,引用必须初始化一个实体//错误代码int& b;return 0;}
在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)。
int main(){double a = 10;double* p = &a;cout << sizeof(p) << endl;double& b = a;cout << sizeof(b) << endl;return 0;}

在这里插入图片描述

引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小。
在这里插入图片描述

有多级指针,但是没有多级引用。

访问实体方式不同,指针需要显式解引用,引用编译器自己处理。

引用比指针使用起来相对更安全。


点击全文阅读


本文链接:http://zhangshiyu.com/post/60801.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1