引用
定义
引用不是新定义一个变量,而是给已存在变量取了一个别名(语法层面),编译器不会为引用变量开辟新的内存空间,引用和引用的变量共用同一块内存空间
注意:引用类型必须和引用实体是同种数据类型
语法
数据类型& 引用名(对象名)=引用实体变量;
使用示例
#include <iostream>
using namespace std;
void TestRedef() {
int a = 100;
int& ia = a;//引用
int* pa = &a;//指针
cout << "TestRedef():";
cout << &a << " " << &ia <<" " << pa << endl;
cout << a << " " << ia <<" "<< *pa << endl;
//验证引用和引用实体共用同一块内存空间
}
void TestRedef(int& a, int b) {
a = 101;
b = 200;
cout << "TestRedef(int& a,int b):";
cout << &a << " " << &b << endl;
//对比引用和非引用在函数参数上的差别
}
int main() {
int a = 0, b = 1;
TestRedef();
TestRedef(a, b);
cout << &a << " " << &b << endl;
return 0;
}
输出结果:
特性
1. 引用在定义时必须初始化
2. 一个变量可以有多个引用
3. 引用一旦引用一个实体,便再不能引用其他实体(从一而终,与指针进行对比)
这样的语句意义属于赋值而无法再次引用
常见使用场景
1. 引用作为函数参数(代替指针)
注意:传值,传地址和传引用三者可以构成函数重载,
然而,虽然传值与传引用可以构成重载,但在调用时会产生冲突(歧义)
特例:指针的引用代替二级指针
以单链表为例
输出结果
观察可知,在增添元素时,需要通过传二级指针以修改一级指针的指向,也就是修改二级指针存储的内容
在C++中,通过指针的引用,也可以实现修改一级指针的指向
输出结果不变
2. 引用作为函数返回值
调用函数的临时变量存储
如果临时变量较小,一般是寄存器充当临时变量
如果临时变量较大,则会放在调用函数的栈帧中。
注意:能否使用引用作为函数返回值取决于返回对象的生存期.
函数返回时离开函数作用域,如果返回对象还未还给系统(静态变量),
则可以使用引用返回;
如果已经销毁(局部变量),则必须使用传值返回函数值
static修饰的cnt相当于全局变量,生存期相对局部变量延长,此时函数返回类型是否引用不会构成影响
传值生成的是局部变量的拷贝值
引用返回的含义是:不生成临时变量的拷贝值,直接返回原变量的别名,近乎等价于直接返回原变量,一旦函数栈帧已销毁,通过引用会造成非法访问,此时取值取决于编译器实现并且随机不定
因此,一般过程不要轻易使用引用返回!!!
但是,在面向对象编程中,使用引用返回值具有重要意义
引用的价值
引用的存在可以解决指针的复杂性问题, 通过引用使部分场景简化;
通过不重复拷贝而提升效率和性能
1.作为参数
提高传参效率; 创造输出型参数(形参的修改可以影响实参)
2.作为函数返回值
简单场景下一般不使用引用作为返回值;
合适情景下使用可以提高效率,修改返回变量;
在参数或返回值类型较大(大对象+深拷贝对象)时,通过引用返回可以提升性能
引用的性能效率
1.传参性能对比(传引用较为自由)
2.返回性能对比(传引用使用受到限制)
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
在底层实现上实际是有空间的,因为引用的底层实质是按照指针方式来实现的。
引用和指针的区别
- 引用概念上定义一个变量的别名,指针存储一个变量地址
- 引用在定义时必须初始化,指针最好初始化,但是不初始化也不会报错
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型
实体- 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
- 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
总结:指针使用起来更复杂一些,更容易出错一些