上节我们讲到,引用不能绑定到临时数据,这在大多数情况下是正确的,但是当使用 const 关键字对引用加以限定后,引用就可以绑定到临时数据了。下面的代码演示了引用和 const 这一对神奇的组合:
typedef struct{
int a;
int b;
} S;
int func_int(){
int n = 100;
return n;
}
S func_s(){
S a;
a.a = 100;
a.b = 200;
return a;
}
S operator+(const S &A, const S &B){
S C;
C.a = A.a + B.a;
C.b = A.b + B.b;
return C;
}
int main(){
int m = 100, n = 36;
const int &r1 = m + n;
const int &r2 = m + 28;
const int &r3 = 12 * 3;
const int &r4 = 50;
const int &r5 = func_int();
S s1 = {23, 45};
S s2 = {90, 75};
const S &r6 = func_s();
const S &r7 = s1 + s2;
return 0;
}
这段代码在 GCC 和 Visual C++ 下都能够编译通过,这是因为将常引用绑定到临时数据时,编译器采取了一种妥协机制:编译器会为临时数据创建一个新的、无名的临时变量,并将临时数据放入该临时变量中,然后再将引用绑定到该临时变量。注意,临时变量也是变量,所有的变量都会被分配内存。
为什么编译器为常引用创建临时变量是合理的,而为普通引用创建临时变量就不合理呢?
1) 我们知道,将引用绑定到一份数据后,就可以通过引用对这份数据进行操作了,包括读取和写入(修改);尤其是写入操作,会改变数据的值。而临时数据往往无法寻址,是不能写入的,即使为临时数据创建了一个临时变量,那么修改的也仅仅是临时变量里面的数据,不会影响原来的数据,这样就使得引用所绑定到的数据和原来的数据不能同步更新,最终产生了两份不同的数据,失去了引用的意义。
以《C++引用的概念与基本使用》一节中讲到的 swap() 函数为例:
void swap(int &a, int &b){
int temp = a;
a = b;
b = temp;
}
如果编译器会为 a、b 创建临时变量,那么函数调用swap(10, 20)就是正确的,但是 10 不会变成 20,20 也不会变成 10,所以这种调用是毫无意义的。
总起来说,不管是从“引用的语义”这个角度看,还是从“实际应用的效果”这个角度看,为普通引用创建临时变量都没有任何意义,所以编译器不会这么做。
2) const 引用和普通引用不一样,我们只能通过 const 引用读取数据的值,而不能修改它的值,所以不用考虑同步更新的问题,也不会产生两份不同的数据,为 const 引用创建临时变量反而会使得引用更加灵活和通用。
以上节的 isOdd() 函数为例:
bool isOdd(const int &n){ //改为常引用
if(n/2 == 0){
return false;
}else{
return true;
}
}
由于在函数体中不会修改 n 的值,所以可以用 const 限制 n,这样一来,下面的函数调用就都是正确的了:
int a = 100;
isOdd(a); //正确
isOdd(a + 9); //正确
isOdd(27); //正确
isOdd(23 + 55); //正确
对于第 2 行代码,编译器不会创建临时变量,会直接绑定到变量 a;对于第 3~5 行代码,编译器会创建临时变量来存储临时数据。也就是说,编译器只有在必要时才会创建临时变量。