本文目标
通过本文我将同各位看博客的朋友共同总结一下C++中的引用,从以下4个点进行总结:
- 简单了解引用
- 引用场景实例
- 对比指针和引用
- 引用实现原理
本文使用代码:github地址
1. 简单了解引用
引用的含义
简单来说,引用就是给变量取一个别名。
就像一个人可以有多种称呼,对于父母来说,应该称之为儿子,对于爷爷奶奶辈来说,应称之为孙子或者外孙,这很好理解。
引用的格式
类型& 引用变量名 = 已定义变量名
引用的特点
- 一个变量可以有多个别名(就好像一个人可以有多个称呼)
- 引用必须初始化(对比人的称呼,一个称呼当然要对应到人才有用)
- 引用只能在初始化时引用一次,之后不能改变所指对象(就好像你是你爷爷奶奶的孙子,不可能变成别人的孙子)
void test1()
{
//我习惯于int和&放在一起,也有喜欢&和变量名放在一起的,这无伤大雅,都可以
int a = 10;
int* p = &a;
int& b = a;
cout << "a.value:" << a << endl;
cout << "b.value:" << b << endl;
cout << "a.address:" << &a << endl;
cout << "b.address:" << &b << endl;
a += 5;
//输出一下a和b发现a改变,b也改变了
cout << "a.value:" << a << endl;
cout << "b.value:" << b << endl;
}
运行结果:
2. 引用场景实例
const引用
const
作为关键字和引用在一起表示这个变量不能被改变,只能读不能写。
如果原来的变量不是const
的变量,相当于引用变量的权限缩小了,但是原变量的权限不受影响,这和const int*
很类似
这里需要注意的便是引用时的权限变化(只能缩小权限不能扩大)和引用常量时必须注意常量具有常属性(其实和前面一点一样)
额外了解一下double到int类型的引用,但是这个应该是没人去这么做的。
void test2()
{
int d1 = 10;
const int& d2 = d1;
d1 += 5;
//d2 += 5; 此句非法,b没有权限修改a
const int d3 = 10;
//int& d4 = d3; 非法,权限只能缩小不能放大
const int& d5 = 5;//合法,因为常量也有空间,但是常量不能修改,所以必须有const
double d6 = 1.5;
//int& d7 = d6; 非法,由于在引用时类型不同,需要转换,d6转换为临时变量,具有常属性,不能引用
const int& d8 = d6;//理由同d5一致
}
引用传参
普通传参
c语言中函数参数传参为非引用传值的方式,会生成局部临时变量接收实参的值。
说白了就是我传一个数组,函数执行的话在一开始就需要再弄一块空间把原数组拷贝过来,用完这个临时数组就不在了,改变的是临时数组,自然不会影响到原数组。
简单举个值传参的例子:
void swap(int left, int right)
{
//这个函数很明显是不能完成交换数据的任务的
//交换只是把临时变量给换了
int temp = left;
temp = left;
left = right;
right = temp;
}
引用传参
起个名再传吧,要交换的数据一个叫left
一个叫right
,反正是原数据的别名,改动会影响到原数据,就达到了交换的效果。
void swap(int& left, int& right)
{
//虽然代码一样但是传参用的引用,能够交换两数
int temp = left;
temp = left;
left = right;
right = temp;
}
常引用传参
如果不想改变参数内部的值,那就用const
这里注意一下引用不能引用数组,但是可以引用vector
模板
int getNumber(const vector<int> arr)
{
return arr.back();
}
指针传参
void swap(int *left, int *right)
{
int temp = *left;
temp = *left;
*left = *right;
*right = temp;
}
引用返回值
错误的引用返回值
下面有一个例子,虽然他并不会报任何错误,但是我们要知道这种方式是错误的。
由于ret
是在Add函数内部定义的,他的作用域在Add函数
内,出了Add函数
,ret
对应的空间就被释放,不再是属于ret
了,他可以被分配给任何一个变量(这个和栈帧的创建与销毁有关,关于栈帧可以看我的另一篇博客),所以如果返回ret
的引用,显然是错误的,如果这块空间被别的变量占有,那么这个新的变量更改值得话,将会导致不可预知的错误。
int& add(int a, int b)//引用返回值
{
int ret = a + b;
return ret;
}
int addNormal(int a, int b)//普通的值返回方式
{
int ret = a + b;
return ret;
}
错误的引用返回值方式汇编
普通的值返回
从两张图中可以对比出来,普通的值返回返回的是一个值,而引用返回返回的是地址,也就是说这个地址如果返回,那么地址对应的变量一定需要存在那么返回才有意义,不然这个地址都不知道是谁的,返回意义何在
举个例子,好比每个班级里面都有一个班长,但是前任班长被撤职了,新班长都没选出来,叫班长是叫谁呢?
正确的返回值引用方式
只有当变量在出了作用域依旧存在时,可以使用引用返回值,例如具有static
属性的变量以及传参时是引用传入的变量作为返回值返回。
int& getMaxiumNum(int& a, int& b)
{
//返回a和b中的较大值,由于a和b是参数引用传入的,所以可以作为引用返回值返回
return a > b ? a : b;
}
引用返回值小结
- 不要返回一个临时变量;
- 如果返回对象出了函数作用域仍存在,则最好用引用返回,因为效率更高
为啥效率更高,这里偷个懒,可以通过系统函数GetTickCount()
来比较调用函数经过的时间,这里就不写了啊,有兴趣的朋友可以自己试试。
3. 对比指针和引用
引用和指针的区别与联系(重点,笔试常考)
- 引用在定义的时候就必须初始化,且初始化之后不能指向其他变量,
指针可以在后续过程中指向别的变量,只要有需要随时可以变 - 引用必须指向有效变量,
指针可以为空 - sizeof引用的变量,得到大小为变量的大小,
sizeof指针变量,得到的的大小为指针的大小(32位下为4字节) - 引用++如果为int是值++,
指针++为跳过当前变量的大小,指向下一个相同类型变量的位置 - 相对而言,指针比引用更灵活,可以说是缺点也可以说是优点,
反过来说就是引用比指针更安全
4. 引用实现原理
引用的实现我们可以通过汇编来看,执行test1()
中的下面这段语句
int a = 10;
int* p = &a;
int& b = a;
执行这段话时,查看汇编如下
可以看到引用和指针的语句在汇编实现的时候是一样的,也就是说引用的实现原理和指针一样,只不过他是用的语法不一样,引用的这个“指针”不能被改变指向的变量,只能用不能改位置。
引用按照我的理解,就类似于int* const p = &a
,p的位置不能被改变,但是p指向的空间内的内容可以修改,只不过引用在使用上不需要加上*
来解引用,语法上不同而已。