最近在看408,可能会捡起来每日一题,emmm反正就每天很充实就是说,要期末八股文太多了hhh
引用
1. 概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它 引用的变量共用同一块内存空间。
- 引用必须初始化
int a = 1;
int& b = a;
- 一个变量可以有多个引用
int a = 1;
int& b = a;
int& c = a;
- 一旦引用了就不能更换了
int a = 1;
int b = 2;
int& c = a;
//c = b;//不可
使用建议:如果引用的地方,不需要改成引用其他地方,可以使用引用
2. 性质
int a = 1;
int& b = a;
a = 10;
//此时b也会变成10
b = 20;
//此时a也会变成20
int a = 1;
int& b = a;
int& c = b;
//可行
- 引用在不同的作用域可以同名
int a = 10;
int& b = a;
int c = 20;
b = c;//这里是赋值操作,不是b引用c
3. 引用作参数
以防用指针很麻烦
void swap(int& x, int& y)
{
int tmp = x;
y = x;
x = tmp;
}
4. 引用构成重载
虽然引用的底层是指针,但还是跟指针构成重载
void swap(int x, int y)
{
int tmp = x;
y = x;
x = tmp;
}
void swap(int* a, int* b)
{
int tmp = *x;
*y = *x;
*x = tmp;
}
void swap(int& x, int& y)
{
int tmp = x;
y = x;
x = tmp;
}
以上三个构成函数重载,在定义的时候不会报错,但在运行的时候(使用的时候)
//取地址能判断
swap(&a, &b);
//而传值的话int 和 int& 不能判断、
//匹配出问题,编译器看不懂
swap(a, b);
5. 引用作返回值
先看普通的int类型返回值如何传参
int add(int a, int b)
{
int c = a + b;
return c;
}
在反汇编中
在销毁函数函数之前,把c的值给寄存器eax
最后寄存器再传值
引用作为返回值
int& add(int a, int b)
{
int c = a + b;
return c;
}
lea指令用于把源操作数的地址偏移量传送目的操作数
这里是先把c的地址放到rax寄存器,rax再给eax寄存器,再给外面所赋值的地方
指针试了一下和上面的方式类似,再次说明引用的本质其实是指针
但存在风险:
- 如果该函数后面再次被调用或者调用其他函数,函数栈帧覆盖了上一次的结果
- 那么所赋值的c不会是我们需要的原来的值,只会是新的函数栈帧销毁之前该地址的值
6. 传值、传引用作为返回值类型或者参数的性能比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
如果给一个全局变量(在静态区)或者返回的地址是mallc出来的(在堆)或者是返回外部main函数中变量的地址(比如数组)
函数无论怎么调用都不会影响,而传值是拷贝,引用不会被影响且只用拷贝地址
所以引用更快(原因参考第五点)
7. 常引用
//权限相同,b可改a,a可改b
const int a = 10;
const int& b = a;
//a可改b,b不可改a没有修改权限
int a = 10;
const int& b = a;
遇到传参的时候
int a = 10;
const int& b = a;
//传参
//错误 权限放大
void fc(int& x)
{
}
//正确
void fc(int x)
{
}
void fc(const int& x)
{
}
如果要减少拷贝,且x在函数中不变,仅仅是使用值,建议使用
const引用
8. 引用和指针的区别
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
- 没有NULL引用,但有NULL指针
- 在
sizeof
中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占 4个字节) - 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
更具体见第五点的汇编代码解释