一、引用
1.1 引用是什么
想必大家都读过四大名著之一的《水浒传》,小说中每个英雄除了有自己本身的名字以外,根据人物外表性格还有许多绰号。比如:宋江又叫:及时雨、呼保义、孝义黑三郎、宋公明、宋押司。李逵又叫铁牛、黑旋风。C++引入了引用这个概念,引用不是新定义一个变量,而是给已存在变量取了一个别名。在语法层面:编译器不会为引用变量开辟内存空间,它和引用的变量共用同一块内存空间。
1.2 引用的用法
用法:引用对象类型& 别名 = 引用对象;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <iostream>
using namespace std;
int main() { int x = 10; int& alias_x = x; cout << "&x = " << &x << endl; cout << "&alias_x = " << &alias_x << endl; alias_x = 20; cout << "x= " << x << endl; cout << "alias_x= " << alias_x << endl; return 0; }
|
输出:
1 2 3 4
| &x = 009BFBBC &alias_x = 009BFBBC x= 20 alias_x= 20
|
我们可以看到x与alias_x的内存地址相同,修改x就是修改alias_x,修改alias_x就是修改x
1.3 引用的特性
1.3.1 引用在定义时必须初始化
引用语法层面是某个对象的别名,所以在定义时必须初始化,不能引用一个未知的对象
1 2 3 4
| int x = 10; int& alias_x; int& alias_x = nullptr; int& alias_x = x;
|
1.3.2 一个对象可以有多个引用
水浒传中好汉可以有多个绰号,一个对象可以有多个引用
1 2 3 4
| int x = 10; int& alias_x1 = x; int& alias_x2 = x; int& alias_x3 = x;
|
1.3.3 一旦引用一个对象,再不能改为引用其他对象
引用是一个非常专一的“人”,一旦引用了某个对象,就不能在改为引用其他对象了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <iostream>
using namespace std;
int main() { int x = 10; int y = 20; int& alias_x = x; cout << "&x = " << &x << endl; cout << "&y = " << &y << endl; cout << "&alias_x = " << &alias_x << endl; cout << "alias = " << alias_x << endl; alias_x = y; cout << "&x = " << &x << endl; cout << "&y = " << &y << endl; cout << "&alias_x = " << &alias_x << endl; cout << "alias = " << alias_x << endl; return 0; }
|
输出
1 2 3 4 5 6 7 8
| &x = 0058FA08 &y = 0058F9FC &alias_x = 0058FA08 alias = 10 &x = 0058FA08 &y = 0058F9FC &alias_x = 0058FA08 alias = 20
|
可以发现alias_x地址并未发生改变,只是alias_x值变为20
1.3.4 引用类型必须和引用对象的类型相同

1.4 引用的权限
1 2 3 4 5 6 7 8 9 10 11 12
| #include <iostream>
int main() { const int x = 10; const int& alias_x = x;
int y = 10; const int& alias_y = y; }
|
1.5 引用的本质
引用的本质就是指针,引用一个对象其实就是存储这个对象的内存地址,当要使用时编译器自动对指针解引用访问对应对象,所以使用引用会开辟内存空间存放对象地址,32位系统下占4字节,64位系统下占8字节

二、引用的应用场景
2.1 引用做函数形参
2.1.1 形参可以改变实参
当函数内形参的改变实参也改变时,可以用引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| void swap1(int x,int y) { int tmp = x; x = y; y = tmp; }
void swap2(int* px, int* py) { int tmp = *px; *px = *py; *py = tmp; }
void swap3(int& x, int& y) { int tmp = x; x = y; y = tmp; }
|
swap1中函数内x与y交换,函数外的实参并没有发生改变,因为形参只是实参一份临时拷贝
swap2中形参px、py存放的是实参的地址,对形参修改会改变外面的实参
swap3中使用了引用,引用的本质就是指针,等同于swap2
2.1.2 提高效率
当函数的形参变量类型很大时,直接传值调用,形参是实参的临时拷贝,效率很低。可以使用引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #define MAXSIZE 10000
typedef int ElemType;
typedef struct SeqList { ElemType data[MAXSIZE]; int length; }SeqList;
void Print1(SeqList s) { for (int i = 0; i < s.length; i++) { printf("%d ", s.data[i]); } }
void Print2(SeqList& s) { for (int i = 0; i < s.length; i++) { printf("%d ", s.data[i]); } }
|
调用函数Print1时,需要将SeqList类型的实参拷贝给形参,而SeqList类型大小为40004字节,开销很大,效率很低
调用函数Print2时,形参本质是指针
2.2 引用做函数返回值
当函数的返回值类型很大时,直接传值返回,实际返回的是返回值的临时拷贝,效率很低。可以使用引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #define MAXSIZE 10000
typedef int ElemType;
typedef struct SeqList { ElemType data[MAXSIZE]; int length; }SeqList;
SeqList s;
SeqList Test1() { return s; }
SeqList& Test2(SeqList& s) { return s; }
|
函数Test1返回时,先将返回值拷贝给临时变量,再将临时变量返回,而SeqList类型大小为40004字节,开销很大,效率很低
函数Test2返回时,返回引用,引用本质是指针,只需要将指针拷贝给临时变量,再将临时变量返回
三、引用的陷阱
引用做为函数返回值时,不要返回函数内的局部变量。局部变量出了函数作用域生命周期就结束了,虽然有的编译器不会将这部分空间清0,但是这部分空间已经不属于我们了,在尝试访问或修改这部分空间属于非法访问内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| int& Add(int x, int y) { int result = x + y; return result; }
int main() { int& ret = Add(5, 5); cout << "ret = " << ret << endl; Add(1, 1); cout << "ret = " << ret << endl;
return 0; }
|
输出
add函数内的result是局部变量,出了函数作用域生命周期结束,返回result并保存在ret中已经属于非法访问内存了。测试环境中编译器并没有将这部分空间清0,所以再次调用函数后,ret值变为了2
四、引用与指针对比
4.1 相同点
●引用的本质就是指针,只是编译器对引用做了封装而已
● 引用与指针所占空间大小一样
4.2 不同点
●引用在定义时必须初始化,指针没有要求
●一旦引用一个对象,再不能改为引用其他对象,而指针可以随时指向一个同类型对象
●不能引用NULL,但有NULL指针
● 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数
● 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
● 有多级指针,但是没有多级引用
●访问对象方式不同,指针需要显式解引用,引用编译器替我们解引用处理