C++ 引用(这篇博客是我现有知识只能这样写)
语法层和底层分开看基本没什么问题,但是一旦两个交互看就会感觉有悖论,这个有人说编译器优化,强行把细节的优化没了,反正现在凌晨3点了我头发掉没了也没悟到引发悖论的细节。用一个物理来说有点波粒二象性,有波的特性也有粒子的特性。语法层的的确确看到是别名和本体公用空间,但是底层你会发现他也是引用变量,那就是他有个双字四字节的小空间(和指针变量所占一样),我是这样理解的比如我们电脑系统不是装在硬盘里面的吗,我们开机是直接进入系统的,但实际上系统前面还有一点点的内存是存放系统信息的好像是ESP我也记不清了 ,那个东西要是没了,你系统就是废掉了,但是感觉举的例子不怎么恰当。我问过很多人,没有人说明白这个小悖论。。。烦啊。C++现在刚起步就要我头发
引用 语法层
引用概念
引用是也已存在的变量取一个别名,编译器不会为引用变量开辟内存空间(这是语法层面)它和它
引用的变量共用同一块内存空间
类型& 引用变量名(对象名) = 引用实体
引用类型必须和引用实体是同种类型的
引用特性
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
引用做参数
传值
//传值 void Swap(int x, int y) { int tmp = x; x = y; y = tmp; } int main() { int a = 0, b = 10; cout << a << endl << b << endl << endl; Swap(a, b); cout << a << endl << b << endl << endl; return 0; }
传址
//传址 void Swap(int* x, int* y) { int tmp = *x; *x = *y; *y = tmp; } int main() { int a = 0, b = 10; cout << a << endl << b << endl << endl; Swap(&a, &b); cout << a << endl << b << endl << endl; return 0; }
传引用
//传引用 void Swap(int& rx, int& ry) { int tmp = rx; rx = ry; rx = tmp; } int main() { int a = 0, b = 10; cout << a << endl << b << endl << endl; Swap(a, b); cout << a << endl << b << endl << endl; return 0; }
传值、传引用效率比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低
#include<iostream> using namespace std; #include <time.h> 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; } int main() { TestRefAndValue(); return 0; }
引用做返回值
传值返回 所有的传值返回都会生成一个拷贝
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-49HdFMks-1638901936693)(C:\Users\zzy\AppData\Roaming\Typora\typora-user-images\image-20211207110234488.png)]
临时变量存在哪
1.c比较小的(4字节 8字节),是寄存器充当临时变量
2.c比较大的,临时变量是放在上一层栈中的,比如这里的Add函数的栈帧
传引用返回
错误用法
那么如何用呢
注意:如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
值和引用的作为返回值类型的性能比较
#include<iostream> using namespace std; #include <time.h> struct A { int a[10000]; }; A a; // 值返回 A TestFunc1() { return a; } // 引用返回 A& TestFunc2() { return a; } void TestReturnByRefOrValue() { // 以值作为函数的返回值类型 size_t begin1 = clock(); for (size_t i = 0; i < 100000; ++i) TestFunc1(); size_t end1 = clock(); // 以引用作为函数的返回值类型 size_t begin2 = clock(); for (size_t i = 0; i < 100000; ++i) TestFunc2(); size_t end2 = clock(); // 计算两个函数运算完成之后的时间 cout << "TestFunc1 time:" << end1 - begin1 << endl; cout << "TestFunc2 time:" << end2 - begin2 << endl; } int main() { TestReturnByRefOrValue(); return 0; }
传值和指针在作为传参以及返回值类型上效率相差很大。
- 引用传参和返回值 有些场景下可以明显提高性能 大对象+深拷贝对象
- 引用传参和返回值 输出型参数和输出型返回值 人话就是函数的形参改变可以改变函数外面的实参,有些场景下,引用返回,可以改变返回对象
修改返回对象
用引用的返回可读可写
#define N 10 int& fun(const int& i) { static int a[N]; return a[i]; } int main() { int i = 0; for (i = 0; i < N; i++) { fun(i) = 100 + i; } for (i = 0; i < N; i++) { cout << fun(i) << " "; } return 0; }
常引用
通过上面我们可以发现const引用大小通吃
const type& 可以接收各种类型的变量
这就引入了新的问题 const引用参数的好处
左值右值
我们经常有一种误解,即在等号左边的叫做左值(L-value),右边的叫做右值(R-value)。这个说法其实是不准确的, 首先左值右值是针对表达式来说的。C++中左值被定义为Location Value,即可以取地址的值,或者说是指表达式结束后仍然存在的值。相反右值就是临时的值了
不要想着越权,越权的人是我最讨厌的人
我们假设x是个很大的对象或者是后面的深度拷贝的部分
那么我们就使用引用参数,这样是可以减少拷贝,来提高效率
假如x我们不需要改变它,我们就用const引用,来提高代码的安全性
引用 低层
我们通过vs19观察别名和本体的确是在同一个地址上,所以我们说了语法上别名是没有开辟空间的,但是实际上底层实现是有空间的,因为引用是按照指针的方式来实现的。
玩底层带你看汇编这是我最喜欢看的东西
linux下面正宗汇编看不懂
引用和指针的不同点:
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
- 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
- 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全