C++入门:引用
目录
一. 什么是引用
1.1 引用的概念
1.2 引用的定义
二. 引用的性质和用途
2.1 引用的三大主要性质
2.2 引用的主要应用
三. 引用的效率测试
3.1 传值调用和传引用调用的效率对比
3.2 值返回和引用返回的效率对比
四. 常引用
4.1 权限放大和权限缩小问题
4.2 跨数据类型的引用问题
五. 引用和指针的区别
一. 什么是引用
1.1 引用的概念
引用,通俗的讲,就是给已经存在的变量取一个别名,而不是创建一个新的变量。引用和被引用对象共同使用一块内存空间。
引用就好比一个人的大名和小名,大名和小名都是一个人。再比如,李逵外号黑旋风,叫李逵和黑旋风表示同一个人。
1.2 引用的定义
引用定义的语法格式:类型& 引用的名称 = 被引用实体
如,定义int a = 10,希望再定义一个引用b,来表示整形变量a的别名,语法为:int& b = a。演示代码1.1展示了引用的定义过程,对原变量a和引用b的其中任意一个赋值,都会使a和b的值均发生改变,这是因为a和b共用一块内存空间。
演示代码1.1:
int main()
{int a = 10;int& b = a; //b是a的引用(别名)printf("a = %d, b = %d\n", a, b); //10,10a = 20; //对a赋值,同时改变a和bprintf("a = %d, b = %d\n", a, b); //20,20b = 30; //对b赋值,同时改变a和bprintf("a = %d, b = %d\n", a, b); //30,30return 0;
}

二. 引用的性质和用途
2.1 引用的三大主要性质
1、引用在定义时必须初始化
定义引用时必须给出这个引用的被引用实体是谁,如:int &b; -- 是非法的。
演示代码2.1:
int main()
{int a = 10;int& b; //报错int& c = a; //初始化引用return 0;
}

2、一个变量可以有多个引用
我们可以为一个变量取多个别名。如演示代码2.2所示,给a变量取b、c、d三个别名是可行的。对a、b、c、d中的任意一个赋值,都会使a、b、c、d的值均发生改变。a、b、c、d共用一块内存空间。
演示代码2.2:
int main()
{int a = 10;int& b = a;int& c = a;int& d = a; //为a取b、c、d三个别名printf("a = %d, b = %d, c = %d, d = %d\n", a, b, c, d); //10,10,10,10c = 20;printf("a = %d, b = %d, c = %d, d = %d\n", a, b, c, d); //20,20,20,20return 0;
}

3、一个引用一旦引用了某个实体,就不能再引用其他实体
演示代码2.3中的b = c并不是将b变为变量c的引用,而是将变量c的值赋给b,通过打印b和c的地址,我们可以发现b和c并不共用一块内存空间,而赋值之后,a和b的值都变为了20。
演示代码2.3:
int main()
{int a = 10;int& b = a;int c = 20;b = c; //将c的值赋给b,而不是让b变为c的引用printf("&b = %p, &c = %p\n", &b, &c); //b和c的地址不一致printf("a = %d, b = %d\n", a, b); //a、b都变为了c的值20return 0;
}

正是因为引用一旦引用了某个实体之后就不能再引用其他实体,所以引用无法替代指针来实现链表数据结构。否则就无法实现链表的增、删等操作,链表的增删操作需要改变指针的指向。
2.2 引用的主要应用
1、引用做函数参数
要写一个swap函数,实现两个整形数据的交换,如果用C语言来写这个函数,就必须使用指针来作为函数的参数,即:void swap(int* px, int* py)。但是,如果使用C++来写,则可以用引用传参来替代指针传参,因为引用和被引用实体共用一块内存空间,引用传参使得函数内部可以控制实参所占用的内存空间,这是,swap可以声明为:void swap(int& rx, int& ry)。
演示代码2.4:
void swap(int& rx, int& ry)
{int tmp = rx;rx = ry;ry = tmp;
}int main()
{int x = 10, y = 20;printf("交换前:x = %d,y = %d\n", x, y); //10,20swap(x, y);printf("交换后:x = %d,y = %d\n", x, y); //20,10return 0;
}

至此,可以总结出函数的三种调用方法:
- 传值调用。
- 传地址调用。
- 传引用调用。
问题:void swap(int x, int y)和void swap(int& x, int& y)能否构成函数重载?
答案是可以的。因为其满足构成函数重载的条件之一 :参数的类型不同。
但是,在调用这两个swap函数时,会存在歧义。通过语句swap(x,y)调用,无法确定是调用swap(int x, int y)还是swap(int&x, int& y)。
2、引用做函数的返回值
在演示代码2.5中,定义函数int& Add(int x, int y),函数返回z的别名。我们希望这个函数能够对x+y进行计算。但是显然,这段代码是有潜在问题的,因为在add函数调用结束后,为add函数创建的栈帧会被销毁,这块栈空间会还给操作系统。此时再使用add函数的返回值,就会造成对内存空间的非法访问,而大部分情况下,编译器不会对非法访问内存报错。
演示代码2.5:
int& add(int x, int y)
{int z = x + y;return z;
}int main()
{int& ret = add(1, 2);printf("ret = %d\n", ret);return 0;
}
对于演示代码2.5的运行结果,可以分为两种情况讨论:
- 函数栈帧销毁后,编译器不对被销毁的栈空间进行清理,打印函数的返回值,结果依旧为x + y的值。
- 函数栈帧销毁后,编译器对被销毁的栈空间进行清理,函数的返回值为随机值。
在VS2019 编译环境下,演示代码2.5的运行结果为3,说明VS编译器不会清理被销毁的函数栈帧空间中内容。

既然VS编译器不会对被销毁的函数栈帧进行清理,那么是否在VS编译环境下,可以正常使用演示代码2.5中的add函数呢?答案显然是否定的,这可以从以下两个方面解释:
- 如果在其他编译环境下进行编译,则被销毁的函数空间可能会被清理,这样会降低代码的可移植性。
- 即使函数栈帧空间不被清理,但这块空间已经换给了操作系统,如果调用完add函数后再调用其他函数,那么原本为z开辟的空间可能会被覆盖,从而改变ret的值。
如演示代码2.6所示,第一次调用add函数使用ret来接收返回值,第二次调用add函数不接收返回值。但是第二次调用add函数之后,ret的值却变为了30,这是因为第二次调用add函数覆盖了第一次调用时创建的函数栈帧,原来第一次调用存放变量z的内存空间的内容由3变为了30,因此,程序运行的结果为30。这段代码在运行过程中栈帧的创建和销毁情况见图2.7。
演示代码2.6:
int& add(int x, int y)
{int z = x + y;return z;
}int main()
{int& ret = add(1, 2);cout << ret << endl;add(10, 20);cout << ret << endl;return 0;
}


总结(什么时候可以用引用返回,什么时候不可以):
- 如果出了函数作用域,函数返回的对象被销毁了,则不能使用引用类型作为返回值。
- 如果出了函数作用域,函数的返回对象还没有被销毁(存储返回对象的内存还没有还给操作系统),则可以使用引用作为返回值。
演示代码2.7给出了两种可以使用引用作为返回的情况,一种是以静态变量作为返回对象,另一种是返回对象为调用函数中开辟的一块内存空间中的内容(调用函数中开辟的数组)。
演示代码2.7:
int& func1()
{static int n = 0;++n;return n;
}char& func2(char* str, int i)
{return str[i];
}int main()
{cout << func1() << endl; //1cout << func1() << endl; //2char ch[] = "abcdef";for (int i = 0; i < strlen(ch); ++i){func2(ch, i) = '0' + i;}cout << ch << endl; //012345return 0;
}

思考问题:既然函数完成调用时才会返回,而调用完成时函数栈帧又会被销毁。那么,以值作为函数返回类型时,时如何从函数中接收返回值的呢?
就比如演示代码2.8中的add函数,函数返回值时add函数中的临时变量z的值,在主函数中的ret如何从add函数中接收z值。
演示代码2.8:
int add(int x, int y)
{int z = x + y;return z;
}int main()
{int ret = add(2, 3);return 0;
}
答案其实很简单,ret并不是直接从add函数栈帧的空间中接收返回值,而是在add函数完成调用、函数栈帧销毁之前,存储一个临时变量用于接收函数的返回值,然后在将临时变量的值赋给ret。
那么,这个临时变量存储在什么位置呢?分两种情况讨论:
- 如果返回值比较小,则使用寄存器充当临时变量。
- 如果返回值比较大,则将临时变量放在调用add函数的函数内部,在调用add函数之前在调用add的函数的栈帧中预先开辟一块空间用于存储临时变量。

三. 引用的效率测试
3.1 传值调用和传引用调用的效率对比
演示代码3.1分别执行100000次传值调用和100000次传引用调用,每次传值调用传给函数的形参的大小为40000bytes,记录传值调用和传引用调用消耗的时间。
程序运行结果显示,10000次传值调用耗时71ms,100000次传引用调用耗时2ms,传引用调用的效率远高于传值调用。这是因为传引用调用不用再为形参开辟一块内存空间,而为形参开辟空间存在一定的时间消耗。
演示代码3.1:
#include<iostream>
#include<time.h>
using namespace std;//大小为40000bytes的结构体
typedef struct A
{int arr[10000];
}A;void Testvaluefunc(A a) { }; //传值调用测试函数
void TestReffunc(A& a) { }; //传引用调用测试函数void TestRefAndValue1()
{A a;int i = 0;size_t begin1 = clock(); //记录开始传值调用的时间(传值调用100000次)for (i = 0; i < 100000; ++i){Testvaluefunc(a);}size_t end1 = clock(); //记录结束传值调用的时间size_t begin2 = clock(); //记录开始传引用调用的时间(调用100000次)for (i = 0; i < 100000; ++i){TestReffunc(a);}size_t end2 = clock();cout << "传值调用10000次耗费时间:" << end1 - begin1 << endl;cout << "传引用调用10000次耗费时间:" << end2 - begin2 << endl;
}

3.2 值返回和引用返回的效率对比
演示代码3.2分别执行100000次值返回函数和100000次引用返回函数,记录调用值返回函数和调用引用返回函数消耗的时间。程序运行结果表明,调用100000次值返回函数耗时136ms,调用100000次引用返回函数耗时2ms,引用返回的效率远高于值返回。
演示代码3.2:
#include<iostream>
#include<time.h>
using namespace std;typedef struct A
{int arr[10000];
}A;A a;A TestValuefunc2()
{return a;
}A& TestReffunc2()
{return a;
}void TestRefAndValue2()
{int i = 0;size_t begin1 = clock(); //记录开始时间(调用100000次)for (i = 0; i < 100000; ++i){TestValuefunc2();}size_t end1 = clock(); //记录结束时间size_t begin2 = clock(); //记录开始的时间(调用100000次)for (i = 0; i < 100000; ++i){TestReffunc2();}size_t end2 = clock(); //记录结束时间cout << "以值作为返回:" << end1 - begin1 << "ms" << endl;cout << "以引用作为返回:" << end2 - begin2 << "ms" << endl;
}int main()
{TestRefAndValue2(); //引用作为返回和值作为返回的效率测试return 0;
}

四. 常引用
4.1 权限放大和权限缩小问题
如果int& b = a,而a是整形常量,被const关键字修饰,那么b就不能作为a的别,因为a变量是只读的,而将b定义为int&类型,则表明b是可读可写的类型,b对a存在权限放大问题。
对于int a = 10,使用const int& b = a来表示a的别名是可以编译通过的。因为a为读写类型,而b为只读类型,b相对于a权限缩小,C++允许权限缩小。
总结:C++允许权限缩小,不允许权限放大。
演示代码4.1:
int main()
{//权限放大问题const int a = 10;//int& b = a; //报错const int& b = a; //编译通过//权限缩小int c = 10;const int& d = c; //能够编译通过return 0;
}
4.2 跨数据类型的引用问题
看一个很诡异的问题。在演示代码4.2中,定义一个双精度浮点型数据double d = 1.1,编译程序,出现下面的现象:
- 将d赋给int型数据i1,编译通过。
- 用int& i2 = d来作为d的引用(别名),编译报错。
- 但是,使用const int& i3 = d来作为d的引言,编译通过。
演示代码4.2:
int main()
{double d = 11.11;int i1 = d; //强转,编译通过//int& i2 = d; //编译报错const int& i3 = d; //编译通过printf("&d = %p\n", &d);printf("&i3 = %p\n", &i3);return 0;
}
那么,为什么const int& i3类型的可以作为d的引用,而int& i2却不行?问题出在强制类型转换上。要理解这个问题,首先要清楚强制类型转换的过程,强制类型转换(int i1 = d),并不是将d强转后的数据直接赋给i1,而是先将d强转为int类型数据的值存储在一个临时变量中,然后再将临时变量的值传给i1,详见图4.1。

临时变量具有常性,只可读不可改。因此,int& i2 = d就存在权限放大的问题,编译无法通过,而const int& i3 = d不会存在权限放大的问题,可以编译通过。但是,这里的i3就不再是d的别名,而是存储d强转为int类型数据值的临时变量的别名,因此i3和d的地址也就不同。演示代码4.2打印了i3和d的地址,表面他们不同,i3其实并不是d的别名。
提示:一定要弄清楚强转类型转换时临时变量做中间值的问题!

五. 引用和指针的区别
- 引用是定义一个变量的别名,而指针存储一个地址。
- 引用不占用额外的内存空间,而指针要占用4bytes或8bytes的内存空间。
- 引用在定义时必须初始化,而指针可以不初始化。(建议指针在定义时避免不初始化)。
- 引用一旦引用了某个实体,便不能更改被引用实体,而指针可以更改指向。
- 对引用自加,即对被引用的实体+1,指针自加,向后偏移一个指针类型的大小(bytes)。
- 没有多级引用,有多级指针。
- 访问实体时,引用直接由编译器处理即可,指针需要解应用。
- 没有空引用,但有空指针NULL。
- 引用相对于指针更加安全。
因为指针存在野指针、空指针等问题,造成指针过于灵活,所以指针的安全性不如引用。
引用的底层是通过指针来实现的。
引用最大的局限性在于不能更改引用实体,因此虽然引用的底层是通过指针实现的,但引用不能替代指针来实现链表数据结构。因为链表的操作需要更改指针的指向。
相关文章:

C++入门:引用
目录 一. 什么是引用 1.1 引用的概念 1.2 引用的定义 二. 引用的性质和用途 2.1 引用的三大主要性质 2.2 引用的主要应用 三. 引用的效率测试 3.1 传值调用和传引用调用的效率对比 3.2 值返回和引用返回的效率对比 四. 常引用 4.1 权限放大和权限缩小问题 4.2 跨…...

SpringSecurity的权限校验详解说明(附完整代码)
说明 SpringSecurity的权限校是基于SpringSecurity的安全认证的详解说明(附完整代码) (https://blog.csdn.net/qq_51076413/article/details/129102660)的讲解,如果不了解SpringSecurity是怎么认证,请先看下【SpringSecurity的安…...

Java-集合(5)
Map接口 JDK8 Map接口实现子类的特点 Map和Collection是并列关系,Map用于保存具有映射关系的数据:Key-ValueMap中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中Map中的key不允许重复,原因和HashSet一样Map…...
研制过程评审活动(四)设计定型阶段
1、设计定型阶段主要任务 设计定型的主要任务是对武器装备性能和使用要求进行全面考核,以确认产品是否达到《研制任务书》和《研制合同》的要求。 设计定型阶段应最终确定《产品规范》、《工艺规范》和《材料规范》的正式版本,并形成正式的全套生产图样、有关技术文件及目…...

【Linux】进程替换
文章目录进程程序替换替换原理替换函数函数返回值函数命名理解在makefile文件中一次生成两个可执行文件总结:程序替换时运行其它语言程序进程程序替换 程序要运行要先加载到内存当中 , 如何做到? 加载器加载进来,然后程序替换 为什么? ->冯诺依曼 因为CPU读取数据的时候只…...

LeetCode171-Excel表列序号(进制转换问题)
LeetCode171-Excel表列序号1、问题描述2、解题思路:进制转换3、代码实现1、问题描述 给你一个字符串columnTitle,表示Excel表格中得列名称。返回该列名称对应得列序号。 例如: A -> 1 B -> 2 C -> 3 ... Z -> 26 AA -> 27 AB -> 28 …...

React SSR
ReactDOMServer 参考链接:https://zh-hans.reactjs.org/docs/react-dom-server.html ReactDOMServer 对象允许你将组件渲染成静态标记。通常,它被使用在 Node 服务端上 // ES modules import * as ReactDOMServer from react-dom/server; // CommonJS v…...
如何系统地优化页面性能
页面优化,其实就是要让页面更快地显示和响应。由于一个页面在它不同的阶段,所侧重的关注点是不一样的,所以如果要讨论页面优化,就要分析一个页面生存周期的不同阶段。 通常一个页面有三个阶段:加载阶段、交互阶段和关…...

Vulnhub 渗透练习(八)—— THE ETHER: EVILSCIENCE
环境搭建 环境下载 靶机和攻击机网络适配都选 NAT 即可。 信息收集 主机扫描 两个端口,22 和 80,且 apache httpd 2.4.0~2.4.29 存在换行解析漏洞。 Apache HTTPD是一款HTTP服务器,它可以通过mod_php来运行PHP网页。其2.4.0~2.4.29版本中…...
华为OD机试题 - 水仙花数 2(JavaScript)| 代码+思路+重要知识点
最近更新的博客 华为OD机试题 - 字符串加密(JavaScript) 华为OD机试题 - 字母消消乐(JavaScript) 华为OD机试题 - 字母计数(JavaScript) 华为OD机试题 - 整数分解(JavaScript) 华为OD机试题 - 单词反转(JavaScript) 使用说明 参加华为od机试,一定要注意不要完全背…...

字符设备驱动基础(二)
目录 一、五种IO模型------读写外设数据的方式 二、阻塞与非阻塞 三、多路复用 3.1 应用层:三套接口select、poll、epoll 3.2 驱动层:实现poll函数 四、信号驱动 4.1 应用层:信号注册fcntl 4.2 驱动层:实现fasync函数 一、…...

看见统计——第三章 概率分布
看见统计——第三章 概率分布 参考 https://github.com/seeingtheory/Seeing-Theory中心极限定理 概率分布描述了随机变量取值的规律。 随机变量Random Variables 🔥 定义:将样本空间中的结果映射到实数的函数 XXX 称为随机变量(random variable)&a…...

【基于众包标注的语文教材句子难易度评估研究 论文精读】
基于众包标注的语文教材句子难易度评估研究 论文精读信息摘 要0 引言1 相关研究2 众包标注方法3 语料库构建3.1 数据收集3.1 基于五点量表的专家标注3.3 基于成对比较的众包标注4 特征及模型4.1 特征抽取4.2 模型与实验设计4.2.1 任务一:单句绝对难度评估4.2.2 任务二:句对相对…...

实例五:MATLAB APP design-APP登录界面的设计
一、APP 界面设计展示 注:在账号和密码提示框输入相应的账号和密码后,点击登录按钮,即可跳转到程序中设计的工作界面。 二、APP设计界面运行结果展示...
作用域和闭包:
1、LHS和RHS查询编译一段代码,需要js引擎和编译器(js引擎负责整个程序运行时所需的各种资源的调度,编译器只是js引擎的一部分,负责将JavaScript源码编译成机器能识别的机器指令,然后交给引擎运行)编译的过程…...
Vue常见面试题?
1、说说你对SPA单页面的理解,它的优缺点是什么? SPA(single-page application)仅在Web页面初始化时加载相应的HTML、JavaScript和CSS。一旦页面加载完成,SPA不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机…...

前端借助Canvas实现压缩图片两种方法
一、具体代码 1、利用canvas压缩图片方法一 // 第一种压缩图片方法(图片base64,图片类型,压缩比例,回调函数)// 图片类型是指 image/png、image/jpeg、image/webp(仅Chrome支持)// 该方法对以上三种图片类型都适用 压缩结果的图片base64与原类型相同// …...

2023年美赛C题Wordle预测问题二建模及Python代码详细讲解
更新时间:2023-2-19 相关链接 (1)2023年美赛C题Wordle预测问题一建模及Python代码详细讲解 (2)2023年美赛C题Wordle预测问题二建模及Python代码详细讲解 (3)2023年美赛C题Wordle预测问题三、四…...

【算法】双指针
作者:指针不指南吗 专栏:算法篇 🐾或许会很慢,但是不可以停下来🐾 文章目录1.双指针分类2.双指针思想3.双指针应用1.双指针分类 常见问题分类 (1) 对于一个序列,用两个指针维护一段区间, 比如快速排序。 …...

Flutter-Widget-学习笔记
Widget 是整个视图描述的基础。 参考:https://docs.flutter.dev/resources/architectural-overview Widget 到底是什么呢? Widget 是 Flutter 功能的抽象描述,是视图的配置信息,同样也是数据的映射,是 Flutter 开发框…...

Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...

vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...

Windows安装Miniconda
一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...
python爬虫——气象数据爬取
一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用: 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests:发送 …...
Leetcode33( 搜索旋转排序数组)
题目表述 整数数组 nums 按升序排列,数组中的值 互不相同 。 在传递给函数之前,nums 在预先未知的某个下标 k(0 < k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nu…...

Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践
在 Kubernetes 集群中,如何在保障应用高可用的同时有效地管理资源,一直是运维人员和开发者关注的重点。随着微服务架构的普及,集群内各个服务的负载波动日趋明显,传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...

对象回调初步研究
_OBJECT_TYPE结构分析 在介绍什么是对象回调前,首先要熟悉下结构 以我们上篇线程回调介绍过的导出的PsProcessType 结构为例,用_OBJECT_TYPE这个结构来解析它,0x80处就是今天要介绍的回调链表,但是先不着急,先把目光…...