上节我们讲到,引用不能绑定到临时数据,这在大多数情况下是正确的,但是当使用 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 行代码,编译器会创建临时变量来存储临时数据。也就是说,编译器只有在必要时才会创建临时变量。