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引用可以使得变量具有只读属性
  • 引用在编译器内部使用指针常量实现
  • 引用的最终本质为指针
  • 引用可以尽可能的避开内存错误

C++解析(4):引用的本质的更多相关文章

  1. WCF关于svcutil生成关于绑定出现 元数据包含无法解析的引用的解决方案

    元数据包含无法解析的引用. 没有终结点在侦听可以接受消息的 net.tcp://localhost:8000/service.这通常是由于不正确的地址或者 SOAP 操作导致的.如果存在此情况,请参阅 ...

  2. WCF使用Net.tcp绑定时候出现错误:元数据包含无法解析的引用

    在WCF服务编程中,客户端添加引用服务时,出现如下错误: 元数据包含无法解析的引用:“net.tcp://192.168.1.105:1314/LoginService”. 套接字连接已中止.这可能是 ...

  3. 读经典——《CLR via C#》(Jeffrey Richter著) 笔记_运行时解析类型引用

    public sealed class Program{ public static void Main() { System.Console.WriteLine("Hi"); } ...

  4. warning MSB3245: 未能解析此引用。未能找到程序集“CemeteryBLL”。请检查磁盘上是否存在该程序集。 如果您的代码需要此引用,则可能出现编译错误。

    多层架构,在每次重新生成解决方案的时候,老是提示:warning MSB3245: 未能解析此引用.未能找到程序集“CemeteryBLL”.请检查磁盘上是否存在该程序集. 如果您的代码需要此引用,则 ...

  5. C++学习笔记---引用的本质

    本质:引用本质上是C++内部实现的一个指针常量 发现是引用的话,自动帮我们转换成指针常量 运行后,发现修改ref的值那么a的值也会一起改变,这就说明了引用的本质就是指针

  6. 20190620_二次开发BarTender打印机时,未能解析主引用“Seagull.BarTender.Print, Version=1.0.0.0, Culture=neutral, processorArchitecture=x86”

    错误提示: 严重性 代码 说明 项目 文件 行 禁止显示状态警告 未能解析主引用"Seagull.BarTender.Print, Version=1.0.0.0, Culture=neut ...

  7. 源码解析Django CBV的本质

    Django CBV模式的源码解析 通常来说,http请求的本质就是基于Socket Django的视图函数,可以基于FBV模式,也可以基于CBV模式. 基于FBV的模式就是在Django的路由映射表 ...

  8. 【DUBBO】Dubbo原理解析-服务引用

    服务引用是服务的消费方向注册中心订阅服务提供方提供的服务地址后向服务提供方引用服务的过程. 服务的应用方在spring的配置实例如下: <dubbo:referenceid="demo ...

  9. C++中引用的本质分析

    引用的意义 引用作为变量别名而存在,因此在一些场合可以代替指针 引用相对于指针来说具有更好的可读性和实用性 swap函数的实现对比: void swap(int* a, int* b) { int t ...

随机推荐

  1. 【BZOJ4362】isn

    [BZOJ4362]isn 题面 bzoj 题解 设\(f[i][j]\)表示当前在\(i\),长度为\(j\)的最长不降子序列有多少个 这个可以用树状数组\(n^2logn\)求出 设\(g[i]\ ...

  2. js的视频和音频采集

    js的视频和音频采集 今天要写的,不是大家平时会用到的东西.因为兼容性实在不行,只是为了说明下前端原来还能干这些事. 大家能想象前端是能将摄像头和麦克风的视频流和音频流提取出来,再为所欲为的么.或者说 ...

  3. MyBatis.Net 配置

    假设我们现在有这样的需求,要对学生信息进行管理 学生表有要以下要求 字段名称 数据类型 说明 stuNo 字符 学号,该列必填,为主键递增 stuName 字符 学生姓名,该列必填,要考虑姓氏可能是两 ...

  4. shell loop

    #!/bin/sh date i=0 while [ $i -le 30 ] do         echi $i /usr/sbin/r2/np_test_acl -f rule.txt i=$(e ...

  5. [Ubuntu] <uptime>命令

    uptime 命令 就是查看系统启动时间的,前几个大家应该都很熟悉:当前时间.系统启动时间.正在登陆的用户数 最后的三个数字,分别代表过去 1分钟  5分钟  15分钟  的平均负载(Load Ave ...

  6. TCP/IP 网路基础

    一.引子         TCP/IP是"Transmission Control Protocol/Internet Protocol"的简写,翻译成中文为传输控制协议/互联网网 ...

  7. [leetcode]三数之和

    三数之和 给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组. 注意:答案中不可以包含重复 ...

  8. Nginx特性验证-反向代理/负载均衡/页面缓存/URL重定向

    原文发表于cu:2016-08-25 参考文档: Nginx 反向代理.负载均衡.页面缓存.URL重写等:http://freeloda.blog.51cto.com/2033581/1288553 ...

  9. shell基础 -- 基本语法

    本文介绍一下 shell 的语法. 一.变量 在 shell 里,使用变量之前通常并不需要事先为他们做出声明,需要使用的时候直接创建就行了.默认情况下,所有变量都被看做字符串并以字符串来存储,即使它们 ...

  10. leetcode12_C++整数转罗马数字

    小弟不才,有错误或者更好解,求留言. 罗马数字包含以下七种字符: I, V, X, L,C,D 和 M. 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如, ...