1. 引用的概念
引用C++语言中引入的概念,引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和引用的变量共用同一块内存空间。 就像一个人既有正名,也会有小名或者外号~
//引用的写法 引用变量类型& 引用变量名 = 引用实体
int main
{
int a = 0;
int& b = a;//b为a的引用
return 0;
}
//引用的类型必须和引用的实体相同
a,b共用一块内存。
2. 引用的特点
2.1 引用在定义时必须初始化
int main
{
int a = 10;
//int& b; 编译不通过
int& b = a;
}
2.2 一个变量可以有多个引用
int main
{
int a = 10;
int& b = a;
int& c = a;
int& d = c;
}
// b、c、d都是a的引用
2.3 引用只能引用一个实体,引用后不能再引用其他实体
int main
{
int a = 10;
int& b = a;
int c = 5;
//b = c 编译不通过
}
3. 常引用
常引用就是在定义引用变量时,在前面加上一个const,和C语言中的const作用一样,将变量可读可写的权限变成只能可读的权限。相当于是权利缩小。
int main
{
//引用的权利可以和实体相同,或者是缩小,但是不能放大。
const int a = 10;
//int& b = a; 实体权力为只读,引用权力放大,所以不正确;
const int& b = a;
//int& c = 10; 引用可以直接引用整数,但是整数具有常属性,所以权力应该是只读的。
const int& c = 10;
double d = 2.2;
//int& e = d; 低精度类型引用实体时,会发生截断,在给e赋值时,中间会产生临时变量,d先赋值给
// 临时变量,再给到e,而临时变量具有常属性,所以引用要用const修饰。
const int& e = d;
}
可以验证一下上面的第三个例子,e并不是d的引用:
在vs2019下,可以看到d和e的地址并不相同,就说明d和e并不使用同一块内存,所以d不是e的引用,间接证明,e赋值给d的时候是产生了临时变量,引用了临时变量。
常引用在函数传参时有时会起到一些作用,要注意引用的权限。
4. 使用场景
4.1 引用做函数参数
交换数值在C语言中是用指针写的,用起来比较麻烦,在C++中可以用引用传参:
//用指针写的交换数值函数,用解引用,比较麻烦
void swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//用引用写的交换数值函数
//引用是实体的别名,所以直接交换两个引用,就是交换两个实体,方便容易理解
void swap(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}
4.2 引用做函数返回值
4.2.1
引用做函数返回值就是返回n的别名,不会产生新的临时变量。因为n由static修饰,所以n被储存在静态区,就算Count()函数执行完毕,n也不会被销毁。所以ret可以引用n的值。
如果不小心没加static,第一次打印ret也是可以打印出的,但是第二次打印就不能打印出1了。要解释这种现象就要从函数栈帧的角度看待这个问题。1.首先在第一次调用Count()函数时,n为1,这没问题,返回n的别名m(姑且称为m),但是问题是n是局部变量了,Count()函数调用完后n,m会随着它的栈帧一起销毁,为什么ret还能引用m呢?2. 因为函数栈帧只是通常意义上的“销毁”,里面的数据并没有马上销毁,它们仍然存在,只是操作系统不允许再访问了。而ret引用m,访问m是非法访问的!但是是可以访问的,(不允许但是可以,就像上中学时不允许学生谈恋爱,但是仍然有谈恋爱的。。)3. 输出函数也要调用函数栈帧,但是要先传参数,1先被传到了cout函数中,所以能被打印出来,但是这时Count()函数之前的栈帧已经被cout函数调用的栈帧覆盖了,原来n的位置也被覆盖了,所以在第二次调用cout时,ret不再是1了而是随机值!
4.2.2
这个道理和上面的一样,第一次调用Add得到3,但由于栈帧内的数据仍然存在,所以ret能访问到c,ret引用c的引用d。第二次调用,函数栈帧仍然在原来的地方,不过c变成了11,所以引用d也是11,ret仍然是那一块内存,所以也是11。
和4.21.相同,第一次可以打印出11,第二次由于刚刚调用了cout,所以属于c的那一块内存被覆盖了,c变成了随机值,因此打印出随机值。
为什么在这里可以打印出3,那是因为static修饰c只能被初始化一次!所以后面不管调用多少次答案都会是3。
如果将c=a+b的代数式和定义c的语句分隔开,那么之后的每次每次都会执行a+b了。
由以上的例子可知: 如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已 经还给系统了,则必须使用传值返回。
4.3 传值传引用,返回值返回引用的效率比较
5. 引用和指针的区别
- 在语法层面来讲引用就是一个别名,没有独立空间,和其引用实体共用同一块空间;
- 在底层实现方面来讲,引用是有空间的,并且实现方式和指针相同。
汇编语言:
虽然牌名不一样,但都是一个厂家生产的~
引用和指针的一些不同之处:
1. 引用 在定义时 必须初始化,指针没有要求2. 引用 在初始化时引用一个实体后,就 不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。3. 没有 NULL 引用 ,但有 NULL指针4. 在 sizeof 中含义不同 : 引用 结果为 引用类型的大小 ,但 指针 始终是 地址空间所占字节个数 (32 位平台下占 4个字节 )6. 有多级指针,但是没有多级引用7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理8. 引用比指针使用起来相对更安全