C++解析(4):引用的本质

0.目录

1.引用的意义

2.特殊的引用

3.引用的本质

4.函数返回引用

5.小结

1.引用的意义

  • 引用作为变量別名而存在,因此在一些场合可以代替指针
  • 引用相对于指针来说具有更好的可读性实用性


注意:函数中的引用形参不需要进行初始化!!!

2.特殊的引用

const引用:

  • 在C++中可以声明const引用
  • const Type& name = var;
  • const引用让变量拥有只读属性

当使用常量const引用进行初始化时,C++编译器会为常量值分配空间,并将引用名作为这段空间的别名。

结论:使用常量对const引用初始化后将生成一个只读变量!!!
(引用只能是另一个变量的别名,因此一个引用绝对不可能是一个常量值的别名,换句话说,不能定义一个引用,然后用一个常量对它进行初始化,这里的常量是字面常量。但是有一个例外,就是const引用。我们可以使用字面常量来对const引用进行初始化,编译器在这个时候会真正的产生一个只读变量。由于是只读变量,所以想要直接对它进行赋值肯定会编译出错,但是依旧可以通过指针来改变只读变量的值。)

引用的特殊意义:在C++中,想要一个使已经存在的变量拥有只读属性,变成一个只读变量,只需要定义一个const引用即可。

思考:引用有自己的存储空间吗?

运行下列程序:

#include <stdio.h>

struct TRef
{
    char& r;
};

int main()
{
    char c = 'c';
    char& rc = c;
    TRef ref = { c };
    
    printf("sizeof(char&) = %d\n", sizeof(char&));
    printf("sizeof(rc) = %d\n", sizeof(rc));
    
    printf("sizeof(TRef) = %d\n", sizeof(TRef));
    printf("sizeof(ref.r) = %d\n", sizeof(ref.r));
    
    return 0;
}

输出结果如下:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out 
sizeof(char&) = 1
sizeof(rc) = 1
sizeof(TRef) = 8
sizeof(ref.r) = 1

(引用所占用的内存空间的大小与指针相同!)

3.引用的本质

引用在C++中的内部实现是一个指针常量

注意:

  1. C++编译器在编译过程中指针常量作为引用的内部实现,因此引用所占用的空间大小和指针相同
  2. 从使用的角度引用只是一个别名C++为了是实用性而隐藏了引用的存储空间这一细节

在C++编译器内部是用指针实现了引用的概念:

#include <stdio.h>

struct TRef
{
    char* before;
    char& ref;
    char* after;
};

int main()
{
    char a = 'a';
    char& b = a;
    char c = 'c';
    
    TRef r = {&a, b, &c};
    
    printf("sizeof(r) = %d\n", sizeof(r));
    printf("sizeof(r.before) = %d\n", sizeof(r.before));
    printf("sizeof(r.after) = %d\n", sizeof(r.after));
    printf("&r.before = %p\n", &r.before);
    printf("&r.after = %p\n", &r.after);
    
    return 0;
}

运行结果为:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out 
sizeof(r) = 24
sizeof(r.before) = 8
sizeof(r.after) = 8
&r.before = 0x7ffd9d27af40
&r.after = 0x7ffd9d27af50

(在汇编层次,指针和引用的操作是一样的。)

C++中的引用旨在大多数的情况下代替指针

  • 功能性:可以满足多数需要使用指针的场合
  • 安全性:可以避开由于指针操作不当而带来的内存错误
  • 操作性:简单易用,又不失功能强大

4.函数返回引用

分析下面的代码:

#include <stdio.h>

int& demo()    // int* const
{
    int d = 0;
    
    printf("demo: d = %d\n", d);
    
    return d;  // return &d
}

int& func()
{
    static int s = 0;
    
    printf("func: s = %d\n", s);
    
    return s;  // return &s
}

int main()
{
    int& rd = demo();
    int& rs = func();
    
    printf("\n");
    printf("main: rd = %d\n", rd);
    printf("main: rs = %d\n", rs);
    printf("\n");
    
    rd = 10;
    rs = 11;
    
    demo();
    func();
    
    printf("\n");
    printf("main: rd = %d\n", rd);
    printf("main: rs = %d\n", rs);
    printf("\n");
    
    return 0;
}

运行结果为:

[root@bogon Desktop]# g++ test.cpp
test.cpp: In function ‘int& demo()’:
test.cpp:5: warning: reference to local variable ‘d’ returned
[root@bogon Desktop]# ./a.out 
demo: d = 0
func: s = 0

main: rd = 53
main: rs = 0

demo: d = 0
func: s = 11

main: rd = 53
main: rs = 11

demo()函数:
返回了一个局部变量的引用,其实返回了一个局部变量的地址。切记:在使用引用时,我们不用返回局部变量的引用。

func()函数:
依旧想要返回一个局部变量的引用,但是这个局部变量是静态的。因为静态的局部变量它的存储空间是一个全局的存储区,所以它所对应的空间不会由于函数调用的返回而被摧毁,因此这样的做法是没有问题的。

程序编译会有警告(warning):不要返回局部变量的引用。

main函数内部:
rd所代表的变量已经在demo函数返回的时候被摧毁了,这时候rd代表的已经是一个不存在的变量了,从指针的角度来看,这时候rd已经相当于一个野指针了。第31行rd想要对栈上已经被释放的4个字节赋值为10,不但没意义,而且很危险。

结论:
这个例子想告诉大家:引用能在最大的程度上去避开内存操作的错误,但是不能寄希望于有了引用就可以乱来,引用不能完全避免内存方面的错误,原因是引用的本质就是指针。只要它还是指针,必然多多少少还可能遇见指针方面的操作问题,以上就是其中一个最典型的问题。

5.小结

  • 引用作为变量别名而存在旨在代替指针
  • const引用可以使得变量具有只读属性
  • 引用在编译器内部使用指针常量实现
  • 引用的最终本质为指针
  • 引用可以尽可能的避开内存错误
posted @ 2018-12-05 16:02  PyLearn  阅读(525)  评论(0编辑  收藏  举报