掘金 后端 ( ) • 2022-01-26 15:41

「这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战

C++ 引用(这篇博客是我现有知识只能这样写)

语法层和底层分开看基本没什么问题,但是一旦两个交互看就会感觉有悖论,这个有人说编译器优化,强行把细节的优化没了,反正现在凌晨3点了我头发掉没了也没悟到引发悖论的细节。用一个物理来说有点波粒二象性,有波的特性也有粒子的特性。语法层的的确确看到是别名和本体公用空间,但是底层你会发现他也是引用变量,那就是他有个双字四字节的小空间(和指针变量所占一样),我是这样理解的比如我们电脑系统不是装在硬盘里面的吗,我们开机是直接进入系统的,但实际上系统前面还有一点点的内存是存放系统信息的好像是ESP我也记不清了 ,那个东西要是没了,你系统就是废掉了,但是感觉举的例子不怎么恰当。我问过很多人,没有人说明白这个小悖论。。。烦啊。C++现在刚起步就要我头发

引用            语法层

引用概念

引用是也已存在的变量取一个别名,编译器不会为引用变量开辟内存空间(这是语法层面)它和它 引用的变量共用同一块内存空间

image-20211206203633555

类型& 引用变量名(对象名) = 引用实体

引用类型必须和引用实体是同种类型的

引用特性

  1. 引用在定义时必须初始化
  2. 一个变量可以有多个引用
  3. 引用一旦引用一个实体,再不能引用其他实体
image-20211206211028288

引用做参数

传值

image-20211206231602873

//传值
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;
}

传址

image-20211206233026964

//传址
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;
}

传引用

image-20211206234230844

//传引用
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;
}
image-20211207000454304

传值、传引用效率比较

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低

image-20211207154228580

#include
using namespace std;
#include 
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;
}

引用做返回值

传值返回 所有的传值返回都会生成一个拷贝

image-20211208075406647

临时变量存在哪 1.c比较小的(4字节 8字节),是寄存器充当临时变量 2.c比较大的,临时变量是放在上一层栈中的,比如这里的Add函数的栈帧

传引用返回

错误用法

image-20211207124631634

那么如何用呢

注意:如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。

值和引用的作为返回值类型的性能比较

image-20211207173215487

#include
using namespace std;
#include 
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;
}

传值和指针在作为传参以及返回值类型上效率相差很大。

  1. 引用传参和返回值 有些场景下可以明显提高性能 大对象+深拷贝对象
  2. 引用传参和返回值 输出型参数和输出型返回值 人话就是函数的形参改变可以改变函数外面的实参,有些场景下,引用返回,可以改变返回对象

修改返回对象

用引用的返回可读可写

image-20211207212645153

#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;
}
image-20211207213851417

常引用

image-20211207182559488

image-20211207234808535

通过上面我们可以发现const引用大小通吃

const type& 可以接收各种类型的变量

这就引入了新的问题 const引用参数的好处

image-20211208002223513

左值右值

我们经常有一种误解,即在等号左边的叫做左值(L-value),右边的叫做右值(R-value)。这个说法其实是不准确的, 首先左值右值是针对表达式来说的。C++中左值被定义为Location Value,即可以取地址的值,或者说是指表达式结束后仍然存在的值。相反右值就是临时的值了


不要想着越权,越权的人是我最讨厌的人

image-20211207185336905

我们假设x是个很大的对象或者是后面的深度拷贝的部分 那么我们就使用引用参数,这样是可以减少拷贝,来提高效率 假如x我们不需要改变它,我们就用const引用,来提高代码的安全性

image-20211207190827538


引用             低层

我们通过vs19观察别名和本体的确是在同一个地址上,所以我们说了语法上别名是没有开辟空间的,但是实际上底层实现是有空间的,因为引用是按照指针的方式来实现的。

玩底层带你看汇编这是我最喜欢看的东西

image-20211208015114991

linux下面正宗汇编看不懂

image-20211208015655043

引用和指针的不同点:

  1. 引用在定义时必须初始化,指针没有要求
  2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
  3. 没有NULL引用,但有NULL指针
  4. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
  5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  6. 有多级指针,但是没有多级引用
  7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  8. 引用比指针使用起来相对更安全