目录
概念:
引用的使用格式:
引用特性:
常引用
使用场景:
1、做参数
二级指针时的取别名
一级指针取别名
一般函数取别名
2、做返回值
函数返回值的原理:
引用的返回值使用:
引用和指针的对比:
语法层面:
底层:
概念:
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空 间,它和它引用的变量共用同一块内存空间。
int main()
{
int a=0;
// 引用,b就是a的别名
int& b= a;
cout <<&b<< endl;
cout<< &a<< endl;
return 0;
}
如上代码所示,引用符是&,但是要和C语言中的&区分,如上代码就第一个&是c++的,而第二个是C语言的 ,且我们开辟了一个空间,它叫做a ,同时他又叫做b,二者的空间地址都是一样的。
取别名的同时还可以给别名取别名!这就相当于把李逵叫铁牛的同时,又把李逵叫做黑旋风。
int main()
{
int a=0;
// 引用,b就是a的别名
int& b= a;
cout <<&b<< endl;
cout<< &a<< endl;
int& c= a;
//给别名取别名,如给别名b取了个别名叫做d
int& d= b;
return 0;
}
引用的使用格式:
类型& 引用变量名(对象名) = 引用实体;
void TestRef()
{
int a = 10;
int& ra = a;//<====定义引用类型
printf("%p\n", &a);
printf("%p\n", &ra);
}
注意:引用类型必须和引用实体是同种类型的。
引用特性:
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
最重要的是第三点!第三点通俗来讲就是,当一个空间A取了一个别名X,那么另一个空间B就不能取和A空间相同名字的别名,也就说空间B的别名不能取X。
1、引用必须初始化
int& b;//没有初始化,是错误的
b=c;
2、引用定义后,不能改变指向
int& b= a;
int c=2;
b=c; //本意是相将c取一个别名,但是这个意思只有赋值的意思,这是常见错误!
//且不能同时取一样名字的别名
常引用
关于常引用,当变量的前方加上了const 后,该变量就变成了常量,而对于常量而言,不论是加上了const的变量变成的,还是本身就是一个常量数值,它在引用时必须要加上const ,此外因为引用类型必须和引用实体是同种类型的,但是如果想要跨越类型引用,也必须加上const!
void TestConstRef()
{
const int a = 10;
//int& ra = a; // 该语句编译时会出错,a为常量
const int& ra = a;
// int& b = 10; // 该语句编译时会出错,b为常量
const int& b = 10;
double d = 12.34;
//int& rd = d; // 该语句编译时会出错,类型不同
const int& rd = d;
}
使用场景:
1、做参数
- 当引用使用在函数的参数上时,引用的用法就和指针一样,指针是因为C语言的形参只是拷贝,所以在传参时需要传递数值的地址,并且需要使用指针来存储地址并指向地址。
- 而引用是给变量取别名,空间地址是不变的,所以传递参数并且给它取了个别名使用和指针的用法是一样的!
二级指针时的取别名
//* & phead 是指给传过来的指针取了别名进行下面的函数使用 void PushBack(struct Node*& phead,int x) { phead = newnode; } int main() { struct Node* plist = NULL; return 0; }
一级指针取别名
void PushBack(struct pNode& phead,int x) { phead = newnode; } int main() { pNode plist = NULL; return 0; }
一般函数取别名
void Swap(int& a,int& b) { //... } int main() { int x=0,y=1; Swap(x,y); return 0; }
2、做返回值
函数返回值的原理:
- 如下图所示,在C语言中,ret 的值是调用了func函数,而func函数会返回一个a,但是按照函数栈帧来讲,mian空间里面开辟了一个空间,而func也开辟了空间。
- 但是func的空间会随着的调用结束而销毁(作用域的生命周期),同时返回值a也在销毁的地方,所以相当于a销毁了,于是乎ret得到的是个不知道是什么的值。
- 但是计算机也知道这个东西会这样的结果,所以a就会被计算机传到一个寄存器中,由寄存器保存并交给ret,所以ret拿到的是寄存器的地址和寄存器里面的东西。
引用的返回值使用:
引用的返回值必须使用在全局变量、静态变量、堆上变量否则会出问题。
静态变量的 返回值的 引用使用方法
//其中函数的前面是返回值的类型,而这里的返回类型是返回一个别名
//表示着返回了一个变量a的别名 a
int& func()
{
static int a = 0;
return a;
}
int main()
{
int ret = func();
cout << ret << endl;
return 0;
}
如果,返回值并未在上述中的区域出现,且贸然的使用引用,那么根据函数返回值的原理,主函数main 中的变量拿到的这个返回值是已经因为生命周期消失而消失的数值和地址,这个是不成立且得到的数值是随机性的!
就比如下面代码中的ret一样,拿到的并非是寄存器的地址和里面的数值,而是消失的空间地址和里面的数值,就和野指针一样,是一个“野别名”
错误示范!
int& func()
{
int a = 0;
return a;
}
int main()
{
int ret = func();
cout << ret << endl;
return 0;
}
引用和指针的对比:
语法层面:
- 引用是别名,不开空间,指针是地址,需要开空间存地址
- 引用必须初始化,指针可以初始化也可以不初始化
- 引用不能改变指向,指针可以
- 引用相对更安全,没有空引用,但是有空指针,容易出现野指针,但是不容易出现野引用
- 在sizeof中含义不同:引用结果为引用类型的大小(如int&、double&等),但指针始终是地址空间所占字节个数(32 位平台下占4个字节)
底层:
- 汇编层面上,没有引用,都是指针,引用编译后也转换成指针了
- 在底层代码中,引用其实就是指针,或者说引用会转化为指针并且开辟空间