深入分析C++引用(指针常量)

引言

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

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

 

背景

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

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

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

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

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

#include <iostream.h>
int main()
{
    int i = 10;   // A simple integer variable
    int &j = i;   // A Reference to the variable i
    j++;   // Incrementing j will increment both i and j.
    // check by printing values of i and j
    cout<<  i  <<  j  <<endl; // should print 11 11
    // Now try to print the address of both variables i and j
    cout<<  &i  <<  &j  <<endl;
    // surprisingly both print the same address and make us feel that they are
    // alias to the same memory location.
    // In example below we will see what is the reality
    return 0;
} 

 引用其实就是 c++ 中的指针常量(指针自身的值是一个常量,不可改变,始终指向同一个地址。在定义的同时必须初始化)。表达式   int &i = j; 将会被编译器转化成 int *const i = &j; 引用之所以要初始化是因为 const 类型变量必须初始化,这个指针也必须有所指。下面我们再次聚焦到上面这段代码,并使用编译器的那套语法将引用替换掉。

#include <iostream.h>
int main()
{
    int i = 10;            // A simple integer variable
    int *const j = &i;     // A Reference to the variable i
    (*j)++;                // Incrementing j. Since reference variables are 
                          // automatically dereferenced by compiler
    // check by printing values of i and j
    cout<<  i  <<  *j  <<endl; // should print 11 11
    // A * is appended before j because it used to be reference variable
    // and it should get automatically dereferenced.
    return 0;
}

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

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

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

#include <iostream.h>
int main()
{
    int i = 10; // A Simple Integer variable
    int &j = i; // A Reference to the variable
    // Now we can also create a reference to reference variable. 
    int &k = j; // A reference to a reference variable
    // Similarly we can also create another reference to the reference variable k
    int &l = k; // A reference to a reference to a reference variable.
    // Now if we increment any one of them the effect will be visible on all the
    // variables.
    // First print original values
    // The print should be 10,10,10,10
    cout<<  i  <<  ","  <<  j  <<  ","  <<  k  <<  ","  <<  l  <<endl;
    // increment variable j
    j++; 
    // The print should be 11,11,11,11
    cout<<  i  <<  ","  <<  j  <<  ","  <<  k  <<  ","  <<  l  <<endl;
    // increment variable k
    k++;
    // The print should be 12,12,12,12
    cout<<  i  <<  ","  <<  j  <<  ","  <<  k  <<  ","  <<  l  <<endl;
    // increment variable l
    l++;
    // The print should be 13,13,13,13
    cout<<  i  <<  ","  <<  j  <<  ","  <<  k  <<  ","  <<  l  <<endl;
    return 0;
}

 

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

 

#include <iostream.h>
int main()
{
    int i = 10;         // A Simple Integer variable
    int *const j = &i;     // A Reference to the variable
    // The variable j will hold the address of i
    // Now we can also create a reference to reference variable. 
    int *const k = &*j;     // A reference to a reference variable
    // The variable k will also hold the address of i because j 
    // is a reference variable and 
    // it gets auto dereferenced. After & and * cancels each other 
    // k will hold the value of
    // j which it nothing but address of i
    // Similarly we can also create another reference to the reference variable k
    int *const l = &*k;     // A reference to a reference to a reference variable.
    // The variable l will also hold address of i because k holds address of i after
    // & and * cancels each other.
    // so we have seen that all the reference variable will actually holds the same
    // variable address.
    // Now if we increment any one of them the effect will be visible on all the
    // variables.
    // First print original values. The reference variables will have * prefixed because 
    // these variables gets automatically dereferenced.
    // The print should be 10,10,10,10
    cout<<  i  <<  ","  <<  *j  <<  ","  <<  *k  <<  ","  <<  *l  <<endl;
    // increment variable j
    (*j)++; 
    // The print should be 11,11,11,11
    cout<<  i  <<  ","  <<  *j  <<  ","  <<  *k  <<  ","  <<  *l  <<endl;
    // increment variable k
    (*k)++;
    // The print should be 12,12,12,12
    cout<<  i  <<  ","  <<  *j  <<  ","  <<  *k  <<  ","  <<  *l  <<endl;
    // increment variable l
    (*l)++;
    // The print should be 13,13,13,13
    cout  <<  i  <<  ","  <<  *j  <<  ","  <<  *k  <<  ","  <<  *l  <<endl;
    return 0;
}

 

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

#include <iostream.h>
class Test
{
    int &i;   // int *const i;
    int &j;   // int *const j;
    int &k;   // int *const k; 
};
int main()
{    
    // This will print 12 i.e. size of 3 pointers
    cout<<  "size of class Test = "  <<   sizeof(class Test)  <<endl;
    return 0;
}

 

结论

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

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

#include <iostream.h>
class A
{
public:
         virtual void print() { cout<<"A.."<<endl; }
};
class B : public A
{
public:
         virtual void print() { cout<<"B.."<<endl; }
};
 
class C : public B
{
public:
         virtual void print() { cout<<"C.."<<endl; }
};
int main()
{
         C c1;
         A &a1 = c1;
         a1.print(); // prints C
         A a2 = c1;
         a2.print(); // prints A
         return 0;
}

 

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

 

引用的使用规则:

a:引用在创建的时候必须被初始化;指针可以不初始化(指针使用时必须先判断是否为空值

b:不存在指向空值的引用这个事实,意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。相反,指针则应该总是被测试,防止其为空。

c:一旦引用初始化后,就不能改变引用所指向的变量; 指针可以改变指向别的对象

d: 引用是一个常量,占内存,并和指针占用的内存大小一样; 指针也占内存,它是一个变量

其实引用是否占内存标准中并没有规定,可以视它为指针常量,并且在编译器优化后它不占内存

e:理论上,对于指针的级数没有限制,但是引用只能是一级。如下:
  int** p1;         // 合法。指向指针的指针
  int*& p2;         // 合法。指向指针的引用
  int&* p3;         // 非法。指向引用的指针是非法的
  int&& p4;         // 非法。指向引用的引用是非法的
  注意上述读法是从左到右。 

f:“sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小;

g:指针和引用的自增(++)运算意义不一样;

h:引用是类型安全的,而指针不是 (引用比指针多了类型检查

引用作函数的参数:

引用作参数时不产生引用指向的对象的副本,节省时间和空间,且能够直接对指向的对象进行操作,可以达到指针同样的效果,但是使用方法更简洁,不易错

 

引用作为函数的返回值:

不能返回函数内部临时对象的引用

当需要返回的对象作为左值时需要返回引用 比如重载操作符 [ ] 时就必须使用引用 ,例 str[3] = 'a'; 

  • 12
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值