【C++学习笔记】引用及引用的本质

代码运行环境:Win10 32bits Vs2013

引用的概念及用法

1.引用的概念

引用不是新定义一个变量,而是给已存变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间,
类型&引用变量名(对象名)= 引用实体

int a = 10;
int &ra = a;    //类型必须和引用实体是同种类型
2.引用特性
  • 引用在定义时必须初始化
  • 一个变量可以有多个引用
  • 引用一旦引用一个实体,再不能引用其他实体
int a = 10;
int &ra;        //这条语句编译时会出错
int &ra = a;    
int &rra = a;   

C++语言规定,引用变量在定义的时候就必须初始化,也就是将引用变量与被引用对象进行绑定。而这种引用关系一旦确定就不允许改变,直到引用变量结束其生命期。

3.常引用
const int a = 10;
int &ra  = a; //这条语句会出错,因为a是常量
const int &ra = a;

int &b = 10;    //这条语句也会出错,因为10是常量
const int &b = 10;

下面再来看看这种情况

double d = 12.34int &rd = d;        //这条语句会出错,类型不同
const int &rd = d;

不加const引用会出错的原因是,通过rd可以改 d的值,这是不被允许的,因此会编译出错。加上const之后,虽然类型不匹配,但是加上const之后,就变成了常引用,不能通过rd改变d的值,因此这是可以的。但是这种 引用会丢失数据,引用后rd的值为12。

4.数组的引用
int n3[3] = {2, 4, 6};
int (&rn3)[3] = n3;     //引用一个包含3个int的数组

看起来有点怪异,
用typedef看一下,就很好理解了

int n3[3] = {2, 4, 6};
typedef int Int3[3];
Int3 &rn3 = n3;
4.指向指针的引用

引用本身不是一个对象,因此不能定义指向引用的指针。但指针是对象,所以存在对指针的引用。

int i = 42;
int *p ;
int *&r = p;  //r是对指针p的引用

要理解r的类型到底是什么,最简单的办法是从右向左阅读r的定义。离变量名最近的符号对变的类型有最直接的影响。

5.使用场景
  • 作为函数形参
void Swap(int &left, int &right)
{
    int temp = left;
    left = right;
    right = temp;
}
  • 作为函数返回值
int &TestReturn(int &a)
{
    a += 10;
    return a;
}

接下来我们看一个有趣的代码

int &Add(int a, int b)
{
    int c = a + b;
    return c;
}
int main()
{
    int &ret = Add(10,20);
    cout <<ret<<endl;
    cout <<ret <<endl;
}

>
【运行结果】:
30
20380984

第一次的结果是正确的,但是第二次打印的结果时错误的,原因是因为返回的引用,是栈上的空间,我们都知道,系统收回栈上的空间,只是收回这块空间的使用权,如果不在用这块空间,这块空间的值是不会被改变的,当用这块空间的值时,这块空间就会被覆盖。第一次打印正确的原因是临时变量C的值先传给打印函数,然后函数进行入栈操作。第二次出错,是引用这块空间已经被用过了。


引用的本质

引用本身是一个变量,只不过这个变量的定义和使用与普通变量有显著的不同,我们看一个代码来分析引用的底层实现。

int i = 10;
int &ra = i;
ra = 20;

VS2013反汇编查看源码对应的汇编代码的步骤是:调试->窗口->反汇编
反汇编如下:

    int i = 10;
003E83A8  mov         dword ptr [i],0Ah  
    int &ra = i;
003E83AF  lea         eax,[i]  
003E83B2  mov         dword ptr [ra],eax  
    ra = 20;
003E83B5  mov         eax,dword ptr [ra]  
003E83B8  mov         dword ptr [eax],14h  

    return 0;
003E83BE  xor         eax,eax  

在汇编代码中,ri的数据类型为dword,也就是说,ra在内存中占据4个字节的位置。所以,ra的确是一个变量,它存放的是被引用对象的地址。

由于通常情况下,地址是由指针变量存放的,我们再来看一个代码:

int i = 10;
int *p = &i;
*p = 20;

反汇编:

    int i = 10;
009983A8  mov         dword ptr [i],0Ah  
    int *p = &i;
009983AF  lea         eax,[i]  
009983B2  mov         dword ptr [p],eax  
    *p = 20;
009983B5  mov         eax,dword ptr [p]  
009983B8  mov         dword ptr [eax],14h  

    return 0;
009983BE  xor         eax,eax  

神奇的发现:
(1)只要将p换成ra,所得汇编代码与第一段所对应的汇编代码完全一样。所以,引用变量在功能上等于一个指针常量,即一旦指向某一个单元就不能在指向别处。
(2)在底层,引用变量由指针按照指针常量的方式实现。

指针和引用的区别
  • 相同点
    底层的实现方式相同,都是按照指针的方式来实现的
  • 不同点
    • 引用在定义时必须初始化,指针没有要求
    • 一旦一个引用被初始化为指向一个对象,就不能在指向其他对象,而指针可以在任何时候指向一个同类型的对象
    • 没有NULL引用,但有NULL指针
    • 在sizeof中含义不同,引用结果为引用类型的大小,但指针始终时地址*空间所占字节个数
    • 引用自加改变变量的内容,指针自加改变了指针指向
    • 有多级指针,但没有多级引用
    • 指针需要手动寻址,引用通过编译器实现寻址
    • 引用比指针使用起来相对更安全

猜你喜欢

转载自blog.csdn.net/lyjwonderful/article/details/80637593