对引用的深入理解
引用名是变量的一个别名,这就暗示了两点:
- 已经存在了某个变量;
- 它是某个已经存在的这个变量的另一个名字
把引用初始化为某个变量之后,就可以使用这个变量的引用来指向变量。
基本形式:类型标识符& 引用名 = 目标变量名;
引用的本质可以理解为所引用对象的地址
通过下面反汇编 的代码理解一下:
反汇编的实现可以参考使用VS和codeblocks实现反汇编
#include <iostream>
using namespace std;
int main()
{
int a = 1;
int &b = a;
cout << "a: " << a<<endl;
cout << "b: " << b<<endl;
cout<<"a 的地址:"<<&a<<endl;
cout<<"b 的地址:"<<&b<<endl;
return 0;
}
输出:
a: 1
b: 1
a 的地址:0x6dfee8
b 的地址:0x6dfee8
下面是在VS上反汇编之后的代码:
int main()
{
00B91490 push ebp
00B91491 mov ebp,esp
00B91493 sub esp,0D8h
00B91499 push ebx
00B9149A push esi
00B9149B push edi
00B9149C lea edi,[ebp-0D8h]
00B914A2 mov ecx,36h
00B914A7 mov eax,0CCCCCCCCh
00B914AC rep stos dword ptr es:[edi]
int a = 1;
00B914AE mov dword ptr [a],1
int &b = a;
00B914B5 lea eax,[a]
00B914B8 mov dword ptr [b],eax
cout << "a: " << a<<endl;
00B914BB mov esi,esp
00B914BD mov eax,dword ptr [__imp_std::endl (0B9A318h)]
00B914C2 push eax
00B914C3 mov edi,esp
00B914C5 mov ecx,dword ptr [a]
00B914C8 push ecx
00B914C9 push offset string "a: " (0B97854h)
00B914CE mov edx,dword ptr [__imp_std::cout (0B9A31Ch)]
00B914D4 push edx
00B914D5 call std::operator<<<std::char_traits<char> > (0B9114Ah)
00B914DA add esp,8
00B914DD mov ecx,eax
00B914DF call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B9A320h)]
00B914E5 cmp edi,esp
00B914E7 call @ILT+400(__RTC_CheckEsp) (0B91195h)
00B914EC mov ecx,eax
00B914EE call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B9A324h)]
00B914F4 cmp esi,esp
00B914F6 call @ILT+400(__RTC_CheckEsp) (0B91195h)
cout << "b: " << b<<endl;
00B914FB mov esi,esp
00B914FD mov eax,dword ptr [__imp_std::endl (0B9A318h)]
00B91502 push eax
00B91503 mov edi,esp
00B91505 mov ecx,dword ptr [b]
00B91508 mov edx,dword ptr [ecx]
00B9150A push edx
00B9150B push offset string "b: " (0B97850h)
00B91510 mov eax,dword ptr [__imp_std::cout (0B9A31Ch)]
00B91515 push eax
00B91516 call std::operator<<<std::char_traits<char> > (0B9114Ah)
00B9151B add esp,8
00B9151E mov ecx,eax
00B91520 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B9A320h)]
00B91526 cmp edi,esp
00B91528 call @ILT+400(__RTC_CheckEsp) (0B91195h)
00B9152D mov ecx,eax
00B9152F call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B9A324h)]
00B91535 cmp esi,esp
00B91537 call @ILT+400(__RTC_CheckEsp) (0B91195h)
cout<<"a 的地址:"<<&a<<endl;
00B9153C mov esi,esp
00B9153E mov eax,dword ptr [__imp_std::endl (0B9A318h)]
00B91543 push eax
00B91544 mov edi,esp
00B91546 lea ecx,[a]
00B91549 push ecx
00B9154A push offset string "a \xb5\xc4\xb5\xd8\xd6\xb7\xa3\xba" (0B97840h)
00B9154F mov edx,dword ptr [__imp_std::cout (0B9A31Ch)]
00B91555 push edx
00B91556 call std::operator<<<std::char_traits<char> > (0B9114Ah)
00B9155B add esp,8
00B9155E mov ecx,eax
00B91560 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B9A304h)]
00B91566 cmp edi,esp
00B91568 call @ILT+400(__RTC_CheckEsp) (0B91195h)
00B9156D mov ecx,eax
00B9156F call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B9A324h)]
00B91575 cmp esi,esp
00B91577 call @ILT+400(__RTC_CheckEsp) (0B91195h)
cout<<"b 的地址:"<<&b<<endl;
00B9157C mov esi,esp
00B9157E mov eax,dword ptr [__imp_std::endl (0B9A318h)]
00B91583 push eax
00B91584 mov edi,esp
00B91586 mov ecx,dword ptr [b]
00B91589 push ecx
00B9158A push offset string "b \xb5\xc4\xb5\xd8\xd6\xb7\xa3\xba" (0B97830h)
00B9158F mov edx,dword ptr [__imp_std::cout (0B9A31Ch)]
00B91595 push edx
00B91596 call std::operator<<<std::char_traits<char> > (0B9114Ah)
00B9159B add esp,8
00B9159E mov ecx,eax
00B915A0 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B9A304h)]
00B915A6 cmp edi,esp
00B915A8 call @ILT+400(__RTC_CheckEsp) (0B91195h)
00B915AD mov ecx,eax
00B915AF call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B9A324h)]
00B915B5 cmp esi,esp
00B915B7 call @ILT+400(__RTC_CheckEsp) (0B91195h)
return 0;
00B915BC xor eax,eax
}
其中下面这一段就是引用部分反汇编之后的内容:
int a = 1;
00B914AE mov dword ptr [a],1
int &b = a;
00B914B5 lea eax,[a]
00B914B8 mov dword ptr [b],eax
- 其中
dword ptr
指令的功能是指明内存单元的长度为双字, mov dword ptr [a],1
就表示将数字1放到变量a所在的内存处;- lea是地址传送指令,即Load Effective Address,指令的格式是
lea 目标寄存器 源操作数
;是有效地址传送指令,源操作数是内存操作数,将内存单元的有效地址(注意不是内容)传送到目标寄存器中,也就是将目的操作数 的偏移地址送到寄存器中;通过下面这张图也可以加深理解:
因此lea eax,[a]
可以理解为把变量a的地址赋值给寄存器eax; mov dword ptr [b],eax
表示把寄存器eax中内容(也就是变量a 的地址)放到变量b所在的内存处;
如果只是简单的赋值,不使用引用,像下面这样:
#include <iostream>
using namespace std;
int main()
{
int a = 1;
//int &b = a;
int b = a;
cout << "a: " << a<<endl;
cout << "b: " << b<<endl;
cout<<"a 的地址:"<<&a<<endl;
cout<<"b 的地址:"<<&b<<endl;
return 0;
}
输出:
a: 1
b: 1
a 的地址:0x6dfeec
b 的地址:0x6dfee8
反汇编之后的结果是:
int a = 1;
00DF14AE mov dword ptr [a],1
//int &b = a;
int b = a;
00DF14B5 mov eax,dword ptr [a] //把变量a的值赋给了寄存器eax
00DF14B8 mov dword ptr [b],eax
与使用引用之后的结果进行比较我们也能看出来,引用的机理是把一个变量的地址赋给了另一个变量。
int a = 1;
00B914AE mov dword ptr [a],1
int &b = a;
00B914B5 lea eax,[a] //把变量a的地址赋给了寄存器eax
00B914B8 mov dword ptr [b],eax
经过上边的分析我们就能得出结论:引用的本质就是把一个变量的地址赋给了另一个变量;也就是说,引用本身存放的是被引用对象的地址;有种指针的意味,不过引用和指针是两个概念,不要混淆。
- 引用只能在定义时初始化一次,之后不能再改变指向其他的变量,但是指针变量的值是可变的;
- 引用不能为空,必须指向一个有效的变量,但是指针可以置为空(注意指针不要悬挂)
- sizeof(指针)和sizeof(引用)的意义不一样,sizeof(引用)的结果是所指向的变量的大小,而sizeof(指针)的结果是对象地址的大小。
另外,这位前辈博客中的例子我觉得非常好,安利一哈:
具体的:在类的成员函数中,使用引用参数和引用返回值都不需要产生临时对象,减少了一次对象的拷贝,提高了函数的效率。
因此,能用引用作为参数时,尽量使用引用,对提升效率有很大的帮助;