引用概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间.
如: 孙悟空 又称为 齐天大圣 , 斗战胜佛.
类型& 引用变量名(别名) = 对象;
引用符号就是 &
int a = 10;
int& b = a;
printf("%p\n", &a);
printf("%p\n", &b);
其中 b 就是就是 a 的别名.
打印出来的两个地址也就是相同的.
所以当引用值改变时, 原先的值也会改变
int a = 10;
int& b = a;
cout << a << ": " << b << endl;
b = 20;
cout << a << ": " << b << endl;
a 的值从10 被修改为 20.
引用特性
1. 引用一旦定义了,就必须立即初始化.
一个对象可以有多个引用.
引用的类型需要和被引用的对象类型相同
int a = 10;
int& b; // 在定义是没有初始化, 会报错
int a1 = a;
int a2 = a;
int a3 = a;
// a1, a2, a3 都是 a 的引用, 修改三个中任意一个,
// 另外两个和 a 都会改变
double& c = a; // 这是不可以的, 类型一定要匹配
2. 引用不能被重新赋值给另一个对象.
int a = 10;
int b = 20;
int& c = a;
c = b; // 这条语句的意思是, 将 b 赋值给 c
// c 还是 a 的引用, 而不是 b 的引用
常引用
顾名思义就是常量的引用.
但是常量具有不可修改性. 所以引用需要加上 const.
const int a = 10;
// int& a1 = a; // 无法通过编译
const int& a1 = a; // 必须加上 const
使用场景
1. 作为参数
用引用作为函数参数:
引用的本质是取别名
所以传递参数时, 就不会产生拷贝
和传地址的作用相似
减少拷贝的消耗, 并且也可以在函数内部修改外部对象
void swap(int& a, int& b)
{
int tem = a;
a = b;
b = tem;
}
int a = 10;
int b = 20;
swap(a, b); // 因为参数是引用, 所以 swap 可以将 a 和 b 的值交换
// 这里调用函数传递参数时, 不需要使用 & 符号
2. 作为返回值
引用做返回值
也可以获得函数内部的值并且在函数外部修改
前提: 引用的变量出了函数之后, 不会被销毁
static int n = 0;
int& func()
{
n++;
return n;
}
int& tem = func();
tem = 10;
cout << tem << ": " << n;
局部变量出了作用域就会销毁, 所以这里 n 是静态变量, 修改 tem 也会改变 n.
int& func()
{
int c = 20;
return c;
}
这里是不能使用传值返回的.
因为 c 在函数结束时会被销毁,
将 c 的引用返回后,
如果使用会产生野指针的风险.
使用引用作为返回值, 返回的对象在出了作用必须还没有被销毁.
如果函数结束时, 对象已经被销毁了, 就不能使用引用作为返回值
传值和传引用比较
1. 作为函数参数:
传值: 形参实际上是对象的副本, 需要拷贝对象
传引用: 形参就是对象本身, 不需要拷贝, 减少了拷贝的消耗
2. 作为函数返回值:
传值: 返回过程中会产生临时对象(中间变量), 然后用这个临时对象去赋值给函数接收值.
传引用: 返回值并不需要创建临时对象, 省去了创建临时对象的开销
在对象的传递过程中, 引用作为参数和返回值
效率是比传值效率要高的
当对象类型非常大时, 效率就能进一步提高
引用和指针联系比较
1. 语法上:
引用在语法概念上就是一个别名, 本身不开辟空间.
指针是一个类型, 有自己的空间.
2. 底层实现:
引用在底层实际的实现上, 是有空间的
因为引用本质上就是通过指针来实现的.
场景 | 引用 | 指针 |
---|---|---|
初始化 | 引用一旦定义了,就必须立即初始化 | 指针在定义时可以不初始化 |
重新赋值 | 引用不能被重新赋值给另一个对象 | 指针可以被重新赋值,指向一个新地址 |
语法 | 引用使用'&'符号声明: int &b = a; | 指针使用'*'符号声明: int *a = &b; |
与NULL的关系 | 引用不能为NULL,引用必须绑定一个有效的对象 | 指针可以为NULL,表示这个指针不指向任何对象 |
内存地址 | 引用本身不占用内存空间,它只是对象别名 | 指针本身占用内存空间,存储了对象的地址 |
类型转换 | 引用在声明初始化时,类型必须匹配,不能进行类型转换 | 指针可以进行类型的转换,如:void* 转换为int *; |
使用场景 | 通常用于函数参数的传递,避免复制对象,减少空间消耗 | 用于动态内存分配、数组操作、复杂的数据结构实现等 |
安全性 | 引用必须绑定到一个对象,所以使用起来相对安全,不容易出现野引用 | 不当使用可能导致野指针、内存泄漏等问题。 |
sizeof | 引用结果为引用类型的大小,如: struct student st; sizeof(st) 结果为struct student 的大小 | 指针始终是地址空间所占字节数(32为平台下就是4个字节) |