掘金 后端 ( ) • 2024-03-05 17:38

引用是什么?

引用&就是取别名. 比如齐天大圣是孙悟空的别名,弼马温也是孙悟空的别名.齐天大圣被困在五行山,那么孙悟空有没有被困呢?

#include<iostream>
#include<assert.h>
using namespace std;
int main()
{
int a = 10;
int& pa = a;  //pa是a的别名
pa = 20;

cout << &a << endl;
cout << &pa << endl;

cout << a << endl;
cout << pa << endl;

return 0;
}

image.png 我们发现,引用的别名pa和a的地址,值都是一样的.(齐天大圣就是孙悟空)

引用的使用规范

俗话说:行车不规范,亲人两行泪.引用的使用也是必须要遵守规范的.

引用必须初始化

image.png

一个变量可以有多个别名

这个跟我们人一样,可以有多个别名.还可以给别名起别名.比如再给孙悟空的别名齐天大圣起一个别名:猴子.

#include<iostream>
#include<assert.h>
using namespace std;
int main()
{
int a = 10;
int& b = a; //b是a的别名
int& c = b; //c是b的别名,同时也是a的别名
return 0;
}

引用不能改变指向

这个就好像齐天大圣已经成为孙悟空的别名,不能再被其他人使用. 这个规范是引用和指针之间最大的不同.也是引用无法替代指针的最大原因

#include<iostream>
#include<assert.h>
using namespace std;
int main()
{
int a = 10;
int b = 20;

int& c = b;//c是b的别名
c = a;  //这个只是把a的值10赋值给c

cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
return 0;
}

image.png 我们可以看到c的地址没有发生改变. 在这里我们也可以看到祖师爷设计的初衷.如果没有这个语法 c = a这个语句就会有歧义. 到底将a的值赋值给c,还是将c改为a的别名.

引用的使用场景

引用做参数

void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
int main()
{
int x = 19;
int y = 99;
Swap(&x, &y);

cout << "x = " << x << endl;
cout << "y = " << y << endl;
        
        Swap(x, y);

cout << "x = " << x << endl;
cout << "y = " << y << endl;
return 0;
}

image.png 在我们学引用之前,当我们需要交换两个变量的值时要把变量的地址传过去,在Swap函数里面还要不断地解引用,多麻烦. 现在只需要传x和y就行.

引用做返回值

class Stack
{
private:
int* _a;
int _size;
int _capacity;
public:
void Init(int n = 4)
{
int* tmp = (int*)malloc(sizeof(int) * n);
//assert(tmp != nullptr);
_a = tmp;
_size = 0;
_capacity = n;
}
void Pushback(int x)
{

//...扩容
_a[_size++] = x;
}
int& Get(int pos)
{
assert(pos >= 0);
assert(pos < _size);
return _a[pos];
}

};
int main()
{
Stack st;
st.Init();
st.Pushback(1);
st.Pushback(2);
st.Pushback(3);
st.Pushback(4);

for (int i = 0; i < 4; i++)
{
cout << st.Get(i) << " ";
/*cout << st[i] << " ";*/
}
cout << endl;

for (int i = 0; i < 4; i++)
{
if (st.Get(i) % 2 == 0)
{
st.Get(i) *= 2; // 修改栈里面的值
}
}

for (int i = 0; i < 4; i++)
{
cout << st.Get(i) << " ";
}
cout << endl;
return 0;
}

在栈的基本实现里,如果我们要修改一个数或者得到一个数.我们正常是要写两个函数.但是用了引用做返回值后,我们可以只写一个函数来实现两个功能

野引用的出现

int& Add(int a, int b)
{
int c = a + b;
return c; //c出了Add这个函数就会销毁
}
int main()
{
int& ret = Add(1, 2); //ret是已销毁空间里面c的别名
cout << "Add(1, 2) is :" << ret << endl;
return 0;
}

我们知道,局部变量c在栈区开辟,出了作用域Add就销毁.ret所接收的是一个已销毁空间的值.编译器在销毁的同时很有可能会清理空间.我们访问的是一片未知的区域.

举例: 我们入住酒店(调用函数,开辟空间),离开酒店(销毁空间),打扫酒店房间(清理空间).但是我们在离开时不小心把行李(变量)遗落在房间里面,我们的行李很有可能被清理走了.当我们再次进入房间(再次访问这个空间),我们会发现自己的行李不见了

image.png