我们上节讲了 C++ 中的引用,那么我们就来看下引用的本质。引用作为变量别名而存在,因此在一些场合可以代替指针。引用相对于指针来说具有更好的可读性和实用性。注意:函数中的引用参数不需要进行初始化!

下来我们来看看 swap 函数的实现对比,如下

voidswap(int*a,int*b)//指针形式的{intt=*a;*a=*b;*b=t;}voidswap(int&a,int&b)//引用形式的{intt=a;a=b;b=t;}

那么这块就有个特殊的引用,便是 const 引用了。在 C++ 中可以声明 const 引用,它的格式为 const Type& name = var;const 引用让变量拥有只读属性。当使用常量对 const 引用进行初始化时,C++ 编译器会为常量值分配空间并将引用作为这段空间的别名。使用常量对 const 引用初始化后将生成一个只读变量!

下来我们以代码为例进行分析,看看引用的特殊意义,代码如下

#include<stdio.h>voidExample(){printf("Example:\n");inta=3;constint&b=a;int*p=(int*)&b;//b=5;*p=5;printf("a=%d\n",a);printf("b=%d\n",b);}voidDemo(){printf("Demo:\n");constint&c=1;int*p=(int*)&c;//c=5;*p=5;printf("c=%d\n",c);}intmain(intargc,char*argv[]){Example();printf("\n");Demo();return0;}

我们在 Example 函数中定义了变量 a,用 b const 引用 a,然后用指针 p 指向 b。然后通过指针 p 改变 b 的值,但是这块 b 是 const 引用,所以不能直接改变 b。我们看看 a 和 b 会是多少。在 Demo 函数中,我们通过 const 引用 c 为 1,并且定义指针 p 指向它。同样不能直接改变 c,但是可以通过指针 p 来改变它的值。我们先来看看通过指针 p 改变后的值是否为 5 呢?看看编译结果

我们看到值已经都改变了,我们再来去掉第 11 和 26 行的注释,看看直接改变 const 引用会怎样?

我们看到报的都是它们是只读变量。那么我们思考下:引用有自己的存储空间吗?我们通过程序来看看

#include<stdio.h>structtest{char&c;};intmain(intargc,char*argv[]){charc='c';char&rc=c;testr={c};printf("sizeof(char&)=%d\n",sizeof(char&));printf("sizeof(rc)=%d\n",sizeof(rc));printf("sizeof(test)=%d\n",sizeof(test));printf("sizeof(r.c)=%d\n",sizeof(r.c));return0;}

我们在第 3 行定义了一个结构体变量 test,但它里面只有一个 char 类型的引用 c。我们来看看这个结构体占用内存吗?编译如下

我们看到引用本身只占用了一个字节,但是结构体 test 占用了 4 个字节的内存。我们猜想它是不是跟指针有某种联系呢?其实引用在 C++ 中的内部实现是一个指针常量。关系如下

注意:a> C++ 编译器在编译过程中用 指针常量 作为引用的内部实现,因此引用所占的空间大小与指针相同;b> 从使用的角度,引用只是一个别名,C++ 为了实用性而隐藏了引用的存储空间这一细节。下来我们通过一个示例代码进行说明

#include<stdio.h>structTRef{char*before;char&ref;char*after;};intmain(intargc,char*argv[]){chara='a';char&b=a;charc='c';TRefr={&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);return0;}

我们看到在结构体 TRef 内部只有 3 个成员,两个指针,一个引用。我们通过打印结构体的大小和它的 before 指针和 after 指针的大小和地址来分别看看中间的引用究竟是什么

我们看到结构体总共占 12 个字节的内存,指针 before 和 after 各占 4 个字节,并且他们的地址相差 8,从而双重说明了中间的引用占 4 个字节的内存空间,引用便是指向一个地址的。那么它的本质便是指针了。

那么为什么还要弄个引用来代替指针呢?我们知道在 C 语言中,凡是涉及到指针的操作都是容易出 bug 的地方,因此 C++ 设计了引用来在大部分情况下代替指针。从功能性来说,可以满足大多数的需要使用指针的场合;从安全性来说,可以避免由于操作指针不当而带来的内存错误;从操作性来说,简单易用,又不失功能强大。下面我们来看看函数返回引用的一个示例

#include<stdio.h>int&demo(){intd=0;printf("demo:d=%d\n",d);returnd;}int&func(){staticints=0;printf("func:s=%d\n",s);returns;}intmain(intargc,char*argv[]){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");return0;}

我们在 demo 函数里返回了局部变量 d,因此这个肯定会出问题。在 func 函数里返回的加 static 修饰的变量,因此它是会放在全局数据区,不会出错。我们在第 23 和 24 行用 demo 和 func 函数进行初始化,因此这会打印出 d = 0 和 s = 0;在第 27 和 28 行打印 rd 和 rs 的值,因为 demo 函数返回之后 d 会丢失,这时 rd 便是一个野指针了。所以 rd 指向的是一个随机数,但是 rs 还是为 0;第 31 和 32 行分别对 rd 和 rs 进行重新赋值,再次调用 demo 和 func 函数时,d 还是为 0,s 就为 11 了;最后第 38 和 39 行会打印出 rd 为随机数,rs 为 11。我们来看看编译结果和我们分析的是否一致

我们看到它在编译的时候都已经报警告了,打印的结果和我们所分析的是一致的。通过对引用本质的学习,总结如下:1、引用作为变量别名而存在旨在代替指针;2、const 引用可以使得变量具有只读属性;3、引用在编译器内部使用指针常量实现,它的最终本质为指针;4、引用可以尽可能的避开内存错误。


欢迎大家一起来学习 C++ 语言,可以加我QQ:243343083。