深入分析C++引用

原文链接

关于引用和指针的区别的文章很多很多,但是总是找不到他们的根本区别,偶然在codeproject上看到这篇文章,觉得讲的挺好的,

所以翻译了下,希望对大家有帮助。

原文地址: http://www.codeproject.com/KB/cpp/References_in_c__.aspx

 

引言

      我选择写 C++ 中的引用是因为我感觉大多数人误解了引用。而我之所以有这个感受是因为我主持过很多 C++ 的面试,并且我很少从面试者中得到关于 C++ 引用的正确答案。

       那么 c++ 中引用到底意味这什么呢?通常一个引用让人想到是一个引用的变量的别名,而我讨厌将 c++ 中引用定义为变量的别名。这篇文章中,我将尽量解释清楚, c++ 中根本就没有什么叫做别名的东东。

 

背景

 c/c++ 中,访问一个变量只能通过两种方式被访问,传递,或者查询。这两种方式是:

1. 通过值 访问 / 传递变量

2. 通过地址 访问 / 传递变量 – 这种方法就是指针

 

       除此之外没有第三种访问和传递变量值的方法。引用变量也就是个指针变量,它也拥有内存空间。最关键的是引用是一种会被编译器自动解引用的指针。很难相信么?让我们来看看吧。。。

 

下面是一段使用引用的简单 c++ 代码

 

[cpp] view plain copy
 
 print?
  1. #include <iostream.h>  
  2. int main()  
  3. {  
  4.     int i = 10;   // A simple integer variable  
  5.     int &j = i;   // A Reference to the variable i  
  6.     j++;   // Incrementing j will increment both i and j.  
  7.     // check by printing values of i and j  
  8.     cout<<  i  <<  j  <<endl; // should print 11 11  
  9.     // Now try to print the address of both variables i and j  
  10.     cout<<  &i  <<  &j  <<endl;  
  11.     // surprisingly both print the same address and make us feel that they are  
  12.     // alias to the same memory location.  
  13.     // In example below we will see what is the reality  
  14.     return 0;  
  15. }   

 

 

引用其实就是 c++ 中的常量指针。表达式   int &i = j; 将会被编译器转化成 int *const i = &j; 而引用之所以要初始化是因为 const 类型变量必须初始化,这个指针也必须有所指。下面我们再次聚焦到上面这段代码,并使用编译器的那套语法将引用替换掉。

 

 

[cpp] view plain copy
 
 print?
  1. #include <iostream.h>  
  2. int main()  
  3. {  
  4.     int i = 10;            // A simple integer variable  
  5.     int *const j = &i;     // A Reference to the variable i  
  6.     (*j)++;                // Incrementing j. Since reference variables are   
  7.                           // automatically dereferenced by compiler  
  8.     // check by printing values of i and j  
  9.     cout<<  i  <<  *j  <<endl; // should print 11 11  
  10.     // A * is appended before j because it used to be reference variable  
  11.     // and it should get automatically dereferenced.  
  12.     return 0;  
  13. }  

 

 

    读者一定很奇怪为什么我上面这段代码会跳过打印地址这步。这里需要一些解释。因为引用变量时会被编译器自动解引用的,那么一个诸如   cout << &j << endl; 的语句,编译器就会将其转化成语句   cout << &*j << endl;  现在 &* 会相互抵消,这句话变的毫无意义,而 cout 打印的 j 值就是 i 的地址,因为其定义语句为 int *const j = &i;

 

      所以语句 cout << &i << &j << endl; 变成了 cout << &i << &*j << endl; 这两种情况都是打印输出 i 的地址。这就是当我们打印普通变量和引用变量的时候会输出相同地址的原因。

 

      下面给出一段复杂一些的代码,来看看引用在级联 (cascading) 中是如何运作的。

 

 

[cpp] view plain copy
 
 print?
  1. #include <iostream.h>  
  2. int main()  
  3. {  
  4.     int i = 10; // A Simple Integer variable  
  5.     int &j = i; // A Reference to the variable  
  6.     // Now we can also create a reference to reference variable.   
  7.     int &k = j; // A reference to a reference variable  
  8.     // Similarly we can also create another reference to the reference variable k  
  9.     int &l = k; // A reference to a reference to a reference variable.  
  10.     // Now if we increment any one of them the effect will be visible on all the  
  11.     // variables.  
  12.     // First print original values  
  13.     // The print should be 10,10,10,10  
  14.     cout<<  i  <<  ","  <<  j  <<  ","  <<  k  <<  ","  <<  l  <<endl;  
  15.     // increment variable j  
  16.     j++;   
  17.     // The print should be 11,11,11,11  
  18.     cout<<  i  <<  ","  <<  j  <<  ","  <<  k  <<  ","  <<  l  <<endl;  
  19.     // increment variable k  
  20.     k++;  
  21.     // The print should be 12,12,12,12  
  22.     cout<<  i  <<  ","  <<  j  <<  ","  <<  k  <<  ","  <<  l  <<endl;  
  23.     // increment variable l  
  24.     l++;  
  25.     // The print should be 13,13,13,13  
  26.     cout<<  i  <<  ","  <<  j  <<  ","  <<  k  <<  ","  <<  l  <<endl;  
  27.     return 0;  
  28. }  

 

 

下面这段代码是将上面代码中的引用替换之后代码,也就是说明我们不依赖编译器的自动替换功能,手动进行替换也能达到相同的目标。

 

 

[cpp] view plain copy
 
 print?
  1. #include <iostream.h>  
  2. int main()  
  3. {  
  4.     int i = 10;         // A Simple Integer variable  
  5.     int *const j = &i;     // A Reference to the variable  
  6.     // The variable j will hold the address of i  
  7.     // Now we can also create a reference to reference variable.   
  8.     int *const k = &*j;     // A reference to a reference variable  
  9.     // The variable k will also hold the address of i because j   
  10.     // is a reference variable and   
  11.     // it gets auto dereferenced. After & and * cancels each other   
  12.     // k will hold the value of  
  13.     // j which it nothing but address of i  
  14.     // Similarly we can also create another reference to the reference variable k  
  15.     int *const l = &*k;     // A reference to a reference to a reference variable.  
  16.     // The variable l will also hold address of i because k holds address of i after  
  17.     // & and * cancels each other.  
  18.     // so we have seen that all the reference variable will actually holds the same  
  19.     // variable address.  
  20.     // Now if we increment any one of them the effect will be visible on all the  
  21.     // variables.  
  22.     // First print original values. The reference variables will have * prefixed because   
  23.     // these variables gets automatically dereferenced.  
  24.     // The print should be 10,10,10,10  
  25.     cout<<  i  <<  ","  <<  *j  <<  ","  <<  *k  <<  ","  <<  *l  <<endl;  
  26.     // increment variable j  
  27.     (*j)++;   
  28.     // The print should be 11,11,11,11  
  29.     cout<<  i  <<  ","  <<  *j  <<  ","  <<  *k  <<  ","  <<  *l  <<endl;  
  30.     // increment variable k  
  31.     (*k)++;  
  32.     // The print should be 12,12,12,12  
  33.     cout<<  i  <<  ","  <<  *j  <<  ","  <<  *k  <<  ","  <<  *l  <<endl;  
  34.     // increment variable l  
  35.     (*l)++;  
  36.     // The print should be 13,13,13,13  
  37.     cout  <<  i  <<  ","  <<  *j  <<  ","  <<  *k  <<  ","  <<  *l  <<endl;  
  38.     return 0;  
  39. }  

 

 

         我们通过下面代码可以证明 c++ 的引用不是神马别名,它也会占用内存空间的。

 

[cpp] view plain copy
 
 print?
  1. #include <iostream.h>  
  2. class Test  
  3. {  
  4.     int &i;   // int *const i;  
  5.     int &j;   // int *const j;  
  6.     int &k;   // int *const k;   
  7. };  
  8. int main()  
  9. {      
  10.     // This will print 12 i.e. size of 3 pointers  
  11.     cout<<  "size of class Test = "  <<   sizeof(class Test)  <<endl;  
  12.     return 0;  
  13. }  

 

 

 

结论

我希望这篇文章能把 c++ 引用的所有东东都解释清楚,然而我要指出的是 c++ 标准并没有解释编译器如何实现引用的行为。所以实现取决于编译器,而大多数情况下就是将其实现为一个 const 指针。

 

 

引用支持 c++ 虚函数机制的代码

 

 

[cpp] view plain copy
 
 print?
  1. #include <iostream.h>  
  2. class A  
  3. {  
  4. public:  
  5.          virtual void print() { cout<<"A.."<<endl; }  
  6. };  
  7. class B : public A  
  8. {  
  9. public:  
  10.          virtual void print() { cout<<"B.."<<endl; }  
  11. };  
  12.    
  13. class C : public B  
  14. {  
  15. public:  
  16.          virtual void print() { cout<<"C.."<<endl; }  
  17. };  
  18. int main()  
  19. {  
  20.          C c1;  
  21.          A &a1 = c1;  
  22.          a1.print(); // prints C  
  23.          A a2 = c1;  
  24.          a2.print(); // prints A  
  25.          return 0;  
  26. }  

 

 

 

上述代码使用引用支持虚函数机制。如果引用仅仅是一个别名,那如何实现虚函数机制,而虚函数机制所需要的动态信息只能通过指针才能实现,所以更加说明引用其实就是一个 const 指针。

 

查看评论
20楼 LightRefraction 2017-01-15 17:47发表 [回复]
[cpp] view plain copy
 
 print?
  1. #include <iostream>  
  2. using namespace std;  
  3. int main(int argc, char const *argv[])  
  4. {  
  5.     int a = 10;  
  6.     int &a1 = a;            //定义a的引用  
  7.     int *const a2 = &a;     //定义指针常量指向a  
  8.     cout << &a1 << endl;  
  9.     cout << a2 << endl;  
  10.     cout << &&a1 << endl;  
  11.     // 若int &a1 = a 等价 int *const a2 = &a,则&&a1等价于&&(*a2)=&a2  
  12.     cout << &a2 << endl;  
  13.     return 0;  
  14. }  

编译输出:
error: label 'a1' used but not defined
楼主解释一下
19楼 luobonic 2017-01-12 16:44发表 [回复]
假设有个函数
void func(int&);
它的参数类型究竟是?
好像使用引用当参数类型,当模板要用到该函数的参数时,会造成传参困难。c++11是不是有了auto,这类泛化函数指针的模板就好些多了?
18楼 zhanyiwp 2015-11-17 08:43发表 [回复]
[cpp] view plain copy
 
 print?
  1. #include <iostream>  
  2. using namespace std;  
  3. class A  
  4. {  
  5.     int a;  
  6.     int b;  
  7.     int c;  
  8.     char d[21];  
  9. };  
  10.   
  11. int main()  
  12. {  
  13.     A a;                                                                                                                                                                                       
  14.     A &ra = a;  
  15.     A *pa = &a;   
  16.     A const* cpa = &a;   
  17.     int *p = NULL;  
  18.     cout << "ra " << sizeof(ra) << " pa " << sizeof(pa) << " a " << sizeof(a) << " cpa " << sizeof(cpa) << endl;  
  19.     return 0;  
  20. }  

输出 ra 36 pa 8 a 36 cpa 8 
请问,如果引用是const 指针的话 上述代码要怎么理解?
17楼 ZhouYates 2015-05-09 16:44发表 [回复]
看编译器实现吧,最简单的实现是全部实现成指针常量。
也可以在函数调用时实现成指针常量;在非函数调用时实现成别名,相当于编译的时候替换引用的符号表,这种实现就没有解应用操作了。不过很鸡肋,一般引用使用场景都是用在函数调用。
16楼 shenlanzifa 2014-10-22 11:02发表 [回复]
应该是指针常量,常量指针的定义为:
1)const int *p;
2)int const *p;
15楼 你你你你你居然还被使用了 2014-09-09 11:06发表 [回复]
碉堡了
14楼 passion_wu128 2014-08-07 14:06发表 [回复]
翻译得不错,我看了原文,个人觉得“retrieved”翻译成“获取”恰当些(你翻译得是“查询”)。
但原文的这个观点是错误的:
"References are nothing but constant pointers in C++"
个人觉得引用本质上是必须初始化的指针,而且自动解引用。
13楼 张亚成 2014-04-29 11:34发表 [回复]
牛逼 的很
12楼 inertialy 2014-04-17 10:40发表 [回复]
原文里面的评论也是一边倒的反对意见,不过倒是可以理解作者的观点,本来看书的时候就有这个想法,否则引用操作符就偏偏要用取地址的符号
11楼 gl_Lei 2014-03-06 10:47发表 [回复]
原来可以引用数组,int a[10] = {0}; int (&b)[10] = a;这样就可以引用了
10楼 gl_Lei 2014-03-06 09:29发表 [回复]
看了这篇文章真心受益匪浅,不过文中有一个错误需要更改一下,应该将文中的“常量指针”更改为“指针常量”。常量指针是指向常量的指针,也可以指向变量,无法用*操作,但是可以改变指向。指针常量才是无法改变指向。
9楼 tYyDy_ 2014-02-26 20:01发表 [回复]
第一段代码,在打印&i,&j时结果并不是一样的.GCC中,
Re: gl_Lei 2014-03-06 09:21发表 [回复]
回复tYyDy_:不会吧?能否贴出图?
8楼 hullhello 2013-07-23 17:18发表 [回复]
“cout << &*j << endl; 现在 &* 会相互抵消,这句话变的毫无意义”,这句话是否值得商榷?毕竟&*j的意思是存储j的地址,并不是简单的&*可以互相抵消。
Re: gl_Lei 2014-03-06 09:25发表 [回复]
回复hullhello:&*j的意思不是代表存储j的地址,首先j和*结合,代表的是j指针指向的变量(左值),也就是文中说的i变量。然后在前面&表示的是i的地址,也就是j变量存储的地址值,和j是等价的。楼主说法没有问题。j == &*j == *&j;这都是等价的!
7楼 lxh364287156 2013-07-09 12:28发表 [回复]
个人认为是对引用分析的最好的一篇文章,怒顶!!!
6楼 HandleHard 2013-05-24 20:14发表 [回复]
其实确实是有别名这种东西的。c++标准规定,引用可以占内存也可以不占,取决于编译器实现。占内存的实现就是使用指针了。很多编译器为了简单都全部采用这种实现方法。不占内存的实现呢,是引用和引用的对象在同一个函数中的时候。局部变量名其实是一个相对于栈基址的偏移量,这种情况下你定义这个局部变量的引用,编译器可以直接给这个引用赋同样的偏移值,这样引用名和变量名完全没有区别。这就是别名的含义了。不过引用作为函数参数的时候确实只有用指针实现这种方法了。
Re: shenzhoushen 2014-07-18 18:47发表 [回复]
回复HandleHard:说的不错,但是在一个函数中使用引用的话,好像没啥意义
5楼 pliro 2013-04-30 20:35发表 [回复]
[cpp] view plain copy
 
 print?
  1. void swap(int&a, int&b)  
  2. {  
  3.     int temp;  
  4.     a=temp;  
  5.     b=a;  
  6.     b=temp;  
  7. }  

引用作为形参呢,又怎么解释。如果还是常量指针的话,怎么交换
Re: 我厂忠义虎 2013-05-02 16:19发表 [回复]
[cpp] view plain copy
 
 print?
  1. void swap(int * const a, int * const b)    
  2. {    
  3.     int temp;    
  4.     *a=temp;    
  5.     *b=*a;    
  6.     *b=temp;    
  7. }  
Re: 我厂忠义虎 2013-05-02 16:17发表 [回复]
回复pliro:指针没交换,交换的应该是两个指针指向的值,所以与常量指针不矛盾
Re: pliro 2013-05-03 13:40发表 [回复]
回复我厂忠义虎:你说的对
4楼 wvtear 2013-03-21 17:42发表 [回复]
先上代码:
#include<iostream>
using namespace std;
int main()
{
int a = 30;
const int &b = 30;
cout << "a= " << a << "\tb= " << b << endl;
a = 40;
cout << "a= " << a << "\tb= " << b << endl;
return 0;
}

请问这段代码能否编译成功并且执行?若能,如何解释?
Re: gl_Lei 2014-03-06 09:32发表 [回复]
回复wvtear:对一个常量(右值)进行引用,则编译器首先建立一个临时变量,然后将常量的值置入到临时变量中。也就是说b指针存储的不是a变量的地址,而是临时变量的地址。对a操作,对b指针来说完全没有任何关系。
Re: ertuy123 2013-03-22 09:12发表 [回复]
回复wvtear:怎么解释?先回去看看c++标准,别不懂乱砸场子。引用可以指向一个“30“这样的字面常量,b压根就不是a的引用。还好意思在这显摆,你这代码肯定能编译。但是指向”30“的这种例子,尽量不用,容易造成误解
Re: pliro 2013-04-30 20:15发表 [回复]
回复ertuy123:很对
3楼 ai_47 2013-02-27 16:26发表 [回复]
[cpp] view plain copy
 
 print?
  1. #include<iostream>  
  2. using namespace std;  
  3. int fo(){  
  4.     int i;  
  5.     return i;  
  6. }  
  7. int main()  
  8. {  
  9.     int a=47;  
  10.     int& b=a;  
  11.     cout<<"a:"<<a<<"b:"<<b<<endl;  
  12.     b++;  
  13.     cout<<"a:"<<a<<"b:"<<b<<endl;  
  14.     cout<<"&a:"<<&a<<"&b:"<<&b<<endl;  
  15.     cout<<"a:"<<sizeof a<<"b:"<<sizeof b<<endl;  
  16.     b=fo();  
  17.     cout<<"a:"<<a<<"b:"<<b<<endl;  
  18.   
  19. }  
2楼 ishouyong 2012-11-30 15:06发表 [回复]
如何引用一个指针倒是不知道,但是引用不能用于数组!
Re: gl_Lei 2014-03-06 09:42发表 [回复]
回复ishouyong:文中说到引用是用int * const p来表示(指针常量),如果对一个变量进行引用,比如:int a = 32;则实现为 int *const p = &a; 如果对一个数组进行引用,比如:int a[10] = {0}; 则实现为:int *const p = &a;这里又牵扯到数组a和数组&a的区别,它们不属于一个概念,属于不同的level.int *const p无法存储&a!那么谁可以存储?数组指针可以,如果改成 int (*const p)[10] = &a;则没有任何问题!
Re: ishouyong 2014-03-09 12:54发表 [回复]
回复gl_Lei:整整一年过去了,个人的很多知识都更新了。现在回过头来看之前的评论,那时倒也是理解不对。c++相对c而言,有很多的优点;就应用的引入,就内存安全性而言提高了很多。但是就相关概念却是更加的容易出现混淆。你的回到我也很赞同,引用其实就是一个C++中的语法糖。
Re: ishouyong 2014-03-09 12:55发表 [回复]
回复ishouyong:晕了,打出了一堆错别字。
1楼 liang361762021 2012-10-25 16:01发表 [回复] [引用] [举报]
如果要引用一个指针或者引用一个数组,编译器怎么编译呢?
Re: gl_Lei 2014-03-06 09:44发表 [回复]
回复liang361762021:不同的level无法相互引用。比如引用指针得用二维指针来实现,引用数组得用数组指针来实现,但是编译器没有实现此功能,并不是说不可以实现!
Re: shenone 2012-12-18 10:56发表 [回复]
回复liang361762021:引用指针叫做:指向指针的引用。譬如:int *&p,一般用于参数传递吧。不能引用数组。
Re: ishouyong 2012-11-30 15:06发表 [回复] [引用] [举报]
回复liang361762021:如何引用一个指针倒是不知道,但是引用不能用于数组!
Re: luffy2405 2013-01-22 12:52发表 [回复] [引用] [举报]
回复ishouyong:。。。引用人家说了是和const *一样。。那常指针了,你还能改变地址吗?不能改变地址。那人家除非重载了,然后创建一个新的非常指针后才能改变地址,才能做到用在数组上啊,所以人家这个对引用的解释是非常完美的。
Re: ishouyong 2013-01-24 17:54发表 [回复] [引用] [举报]
回复luffy2405:我是c++菜鸟。我认真的看了几次,还是没有看懂你的意思。
重载运算符可以用于数组??
我也认为他的这个解释是相当到位的。

 

posted @ 2017-03-22 12:08  poluner  阅读(132)  评论(0编辑  收藏  举报