引用的概念及用法
- 引用:给一个已经定义的变量重新起一个别名。引入了对象的一个同义词。定义引用的表示方法与定义指针相似,只是用&代替了*。对引用的操作与对变量直接操作完全一样。
- 声明方法:类型标识符 &引用名=目标变量名;
- 注意:
- 一个变量可以有多个别名。
- 引用变量必须初始化。
- 只能在初始化引用一次,不能改变为再引用的其他变量。
#include<iostream>
int main() {
int a = 10;
int& b = a;//给a起一个别名叫做b b引用了a b是a的别名
int& c = a;//一个变量可以有多个别名
int& d = c;//这种定义方式也是合理的
//对其中任意一个别名进行赋值和修改,都会影响其他的值。
system("pause");
return 0;
}
- const引用
- const在C语言中:限定一个变量不允许被改变,产生静态作用。
- const在C++中:它把一个对象转换成一个常量。
- 如果所要引用的变量具有常性,则需要加上const。
- 如果所要引用的是常数,也需要加入const。
const int a = 10;
const int& b = a;//如果要起别名的变量有const修饰,别名也要用const修饰
const int& a = 20;//如果要给常量起别名,也要用const修饰
int d = 10;
double e = d;//隐式类型转换
double& f = d; //这是错误的,因为在隐式类型转换的时候,
//会产生一个临时变量,而临时变量具有常性,
//所以在引用的时候要用const修饰。
const double& g = d;//正确做法。
- 从这个图可以看出,在隐式类型转换的过程中会产生一个临时变量,而如果按照以上的代码引用d,f实际上是引用了临时变量,而临时变量具有常性,所以应该类似于引用常数,加上const做修饰。
即:读写权限只能缩小,不能放大。
引用做参数
- 传值
void Swap (int left, int right){
int temp = left;
left = right;
right = temp;
}
这段代码是不能实现交换的,在调用Swap(a,b)时,只是将a,b原来具有的值传给了left和right。例如,a = 1,b = 2,传值以后,left = 1,right = 2,此时在Swap函数内部交换left和right的值,只会使得left = 2,right = 1,而a,b还是原来的值,并没有改变。
- 传址
void Swap (int* left, int* right){
int temp = *left;
*left = *right;
*right = temp;
}
调用Swap(&a,&b)是可以完成交换的,因为传送的是a和b的地址,在Swap函数接收到地址之后,交换的时候就会解引用,找到a,b的位置,函数内交换的left和right,实际上就是在交换a和b的值,从而可以完成交换。
- 传引用
void Swap (int& left, int& right){
int temp = left;
left = right;
right = temp;
}
调用Swap(a,b)即可完成交换。在这里,传的参数是引用,即形参是实参的别名。有点类似于传址,只不过传址是根据指针找到a,b,而传引用是根据别名找到a,b。即:left是a的别名,right是b的别名,交换left和right实际上就是交换了a和b。
举个例子,王小明的小名叫做明明,张小花的小名叫做花花,老师说:王小明和张小花换个位置,和,老师说:明明和花花换个位置的效果是一样的。
注意:
在这里也许有人会问,在前面不是说过,引用必须要初始化吗?可是这里并没有初始化呀!
其实在参数传递的过程中,是进行了初始化的 :int& left = a,int& right = b。
引用做返回值
- 先看下面的一段代码
int Add(int a,int b){
int c = a + b;
return c;
}
int main(){
int& ret = Add(1,2);//错误的,应该加上const
}
这段代码显然是错误的,在这里,返回的已经不是c了,因为c的生命周期已经到了。随着函数栈帧的销毁,c所占的空间已经返回给了内存,c已经释放了。此时返回的是一个临时变量(或称匿名变量,一般由寄存器充当)。既然是临时变量,就如我们之前所了解的,在ret引用的时候应该加上const做修饰,如果不加const也可以,可以让函数返回一个引用。
- 这两种写法是正确的:
int Add(int a,int b){
int c = a + b;
return c;
}
int main(){
const int& ret = Add(1,2);
}
int& Add(int a,int b){
int c = a + b;
return c;
}
int main(){
int& ret = Add(1,2);
}
既然返回值是引用,那么就可以理解为此时返回了一个引用,假设返回了int& x(这个x就是为了方便理解起的名字,其实这个变量可能就没有名称,但是确实存在),它就是c的别名,即int& x = c,而在调用函数的时候,又给返回值起了一个别名叫做int& ret = x,所以,ret 是x的别名,x又是c的别名,这样ret也是c的别名,就可以获取到正确的返回值了。
- 下面再看一组代码:
int& Add(int a,int b){
int c = a + b;
return c;
}
int main(){
int& ret = Add(1,2);
Add(10,20);
cout << ret << endl;
return 0;
}
在我的电脑环境运行输出的结果是30。为什么呢?还是要根据函数栈帧来解释,当第一次调用Add(1,2)结束后,栈帧被销毁,所占的空间已经还给了内存,但是根据不同的系统,可能那段空间内所存的值并没有被销毁,c刚刚使用过的空间的值还是3。第二次调用Add(10,20)时,虽然没有给它起别名,但是形成的栈帧在同一段区域,此时c还在原来的空间,那里的值已经变为了30。当我们输出ret的时候,会根据ret找到它的引用,也就是c所占那块空间的位置,里面的值是30,输出的结果就是30。但是这个结果是和操作系统有关系,看一段栈帧结束后系统会怎么样处理那段返回的空间,可能有的是30,有的就是随机值。
引用和指针的区别
引用和指针的区别和联系
- 引用只能在定义时初始化一次,之后不能改变指向其它变量(从一而终);指针变量的值可变。
- 引用必须指向有效的变量,指针可以为空。
- sizeof指针对象和引用对象的意义不一样。sizeof引用得到的是所指向的变量的大小,而sizeof指针是对象地址的大小。
- 指针和引用自增(++)自减(–)意义不一样。
相对而言,引用比指针更安全。
总结一下: 指针比引用更灵活,但是也更危险。使用指针时一定要注意检查指针是否为空。指针所指的地址释放以后最好置0,否则可能存在野指针问题。