本篇继续C++学习记录,引用。
引用是C++与C的一大扩展。引用变量是已定义变量的别名。
与其它变量不同,引用变量必须在声明的同时初始化,通过&
标识,如下所示:
int a;
int& aa = a;
此时,&
不再表示取地址,int&
表示整型引用。
变量a
与引用变量aa
的值和地址是相同的。因此,修改a
或者aa
都会使对方出现相同的改变。
此外,引用变量初始化之后,就不能修改引用的“指向”了,这点和指针不同。
引用的本质,实际上是一个指针常量:
// 引用
int i;
int& ii = i;
// 实质
int* const pi = &i;
引用变量ii
与*pi
完全一致。
指针常量保存的地址不能被改变,这也就是创建引用变量后不能再修改“指向”的原因。
上篇函数内容中提到两种传参方法,也就是传值和传址。C++提供的第三种传参方法,就是传引用。
通过传引用,函数也可以像传址一样修改函数外部的变量。
还是以经典的二值交换函数为例,通过引用参数:
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
void main(){
int x = 1;
int y = 2;
swap(x, y);
}
调用函数时,swap函数内的引用a, b
作为外部变量x, y
的别名,函数内部a, b
值交换,外部变量也将跟着交换。这一点从地址的角度来看更容易理解,因为a, b
的地址与x, y
相同。
引用的本质是指针常量,因此传参时也不会拷贝被引用者的完整数据。
注意:无论是引用变量,还是引用参数,非const引用类型必须一致。下面的程序将报错:
#include
#include
void swapr(double& a, double& b){
double temp = a;
a = b;
b = temp;
}
int main()
{
using namespace std;
float x=1., y=2.;
swapr(x, y);
cout << x << ", ,"<< y <<endl;
return 1;
}
error: cannot bind non-const lvalue reference of type 'double&' to an rvalue of type 'double'
swapr(x, y);
如果引用变量类型与被引用的变量类型不同,C++会生成临时变量。
由于函数内部创建的变量在函数结束时销毁,修改临时变量是没有意义的,因此编译器认为非const引用参数生成的临时变量是没有意义的。这就是上面报错的原因。
有关const关键字的使用将在下篇C++学习中详细记录。
函数常规返回的方式是按值返回,也就是函数把返回值复制到某个内存的某个临时位置,然后被程序访问,示例如下:
int func(int a){
return a;
}
void main(){
int x;
int y = func(x);
}
上面的程序中,func1返回a时,在内存中临时创建了一个保存a值的空间,然后主程序从该临时空间里的值拷贝到变量y。
如果返回的是结构或者对象,那么函数把返回的结构或对象复制到内存中,然后再让程序来访问。这样就非常消耗时间和内存了。
返回引用则不会在内存中创建临时空间,而是直接将引用变量返回。返回引用的函数声明为TypeName& FuncName(ParamList)
。示例如下:
int& func(int &a){
return a;
}
void main(){
int x;
int y = func(x);
}
上面的程序中,func1直接返回引用变量a,主程序拷贝a的值。
注意:返回引用时,禁止返回局部变量,也就是说不能返回函数结束后被销毁的变量。指针同理。上面返回引用的函数中,参数列表也使用了引用参数,就是为了延长a的生命周期。
最简单的判断方法:能取址的是左值,不能取址的是右值。
也就是说,左值在内存空间中占据确定的位置,而右值则只是在计算过程中被临时创造,没有确定的内存空间,计算结束即销毁。
一般而言,左值才能被引用。上面提到的引用都是左值引用。因此,不特殊声明时认为,引用就是左值引用。
左值引用:
float v;
float& lv = v;
C++11中添加了右值引用,也就是可以引用右值,通过&&
创建:
double&& j = 15. + .5;
右值引用被用于std::move()
的实现,以后会学习到。
引用的本质:指针常量。
使用引用参数的好处:
因此,引用与指针一样,适合用作访问和修改大型数据。典型的例子是通过传指针在函数内修改数组,通过传引用在函数内修改结构和对象。