引用可以简化原来使用指针的代码,至少看起来舒服点,不过今天发现个问题,先来看一段代码
#include <iostream> #include <cstdlib> #include <vector> using namespace std; int main() { vector<int> stack; stack.push_back(1); int& rx = stack[0]; int vx = stack[0]; int vy = rx; cout<<rx<<endl; stack.pop_back(); cout<<rx<<endl; stack.push_back(2); cout<<rx<<endl; system("pause"); return 0; }
输出:
1 1 2
可以看到引用的值发生了变化,并且在被引用值“不存在”的情况下也返回了一个值。我们说引用的值总是存在的,因此在不会出现NULL的地方且指向不变的地方可以用引用代替指针。实际上引用是一个指针,由于引用使用的语法的规则,它必须有初值(也就是说它不会为NULL),而它的作用域(生命周期)总是等于或小于被引用的变量,而且一旦指定被引用对象后无法改变,因而使用它比直接用指针来的安全。来看一下以上部分代码在VS2012中的反汇编
int& rx = stack[0]; 00FC6495 push 0 00FC6497 lea ecx,[stack] 00FC649A call std::vector<int,std::allocator<int> >::operator[] (0FC1276h) 00FC649F mov dword ptr [rx],eax int vx = stack[0]; 00FC64A2 push 0 00FC64A4 lea ecx,[stack] 00FC64A7 call std::vector<int,std::allocator<int> >::operator[] (0FC1276h) 00FC64AC mov eax,dword ptr [eax] 00FC64AE mov dword ptr [vx],eax int vy = rx; 00FC64B1 mov eax,dword ptr [rx] 00FC64B4 mov ecx,dword ptr [eax] 00FC64B6 mov dword ptr [vy],ecx
可以看到调用operator[]返回值存在eax中(函数调用约定),当初始化引用变量rx时,直接将eax给了rx,而当赋值给整型变量vx时,多了一句
013364AC mov eax,dword ptr [eax]
即将eax指向的内存内容重新赋值给eax,然后eax再把这个值给vx变量。从这里我们可以看出operator[]返回的eax实际上是一个指针,相应的引用rx也是一个指针,只不过在语法上做了限制,编译器做了检查和额外处理,让它用起来像一个普通值变量。比如语句
int vy = rx;
它将一个引用赋值给一个整型变量,对应的指令序列如下
1. 首先获得指向rx引用对象的指针 (mov eax,dword ptr [rx])
2. 然后根据指针值获得被引用对象的值 (mov ecx,dword ptr [eax])
3. 最后把值赋给变量vy (mov dword ptr [vy],ecx )
再来看一个指针版本的反汇编,就会发现与引用变量相关的汇编代码几乎是一致的。
int* p = &stack[0]; 00FC64BB lea ecx,[stack] 00FC64BE call std::vector<int,std::allocator<int> >::operator[] (0FC1276h) 00FC64C3 mov dword ptr [p],eax int vz = *p; 00FC64C6 mov eax,dword ptr [p] 00FC64C9 mov ecx,dword ptr [eax] 00FC64CB mov dword ptr [vz],ecx
这样就可以解释开始的问题了(即引用指向的值发生了改变,而且stack为empty时还能访问),因为引用实际上是一个指针,它指向stack[0]所在的内存空间,而vector会预先分配多余元素个数的空间,所以即使将vector清空了引用还是有效的(它指向的内存区没有被立即释放)。当再次push新的值放到stack[0]的位置上时,引用返回的值就是这个新的值了。
所以对于容器内元素的引用,我们应该小心使用,因为这个引用实际上只和引用初始化时被指向的容器的位置上的值相关。