深入解析C++引用:从别名机制到函数特性实践

news2025/6/6 10:08:08

1.C++引用

1.1引用的概念和定义

引用不是新定义⼀个变量,而是给已存在变量取了⼀个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同⼀块内存空间。比如四大名著中林冲,他有一个外号叫豹子头,类比到C++里就像变量a,有一个别名叫b,它们所代表的其实都是一个东西,只是名称不同。

类型& 引用别名 = 引用对象

C++中为了避免引入太多的运算符,会复用C语言的⼀些符号,这里引用和取地址使用了同⼀个符号&,大家要注意区分。
我们来看一段代码:

#include<iostream>
using namespace std;

int main()
{
	int a = 10;

	int& b = a;
	int& c = a;
	int& d = b;//引用不仅能给变量取别名,还能给变量的别名取别名

	cout << &a << endl;
	cout << &b << endl;
	cout << &c << endl;
	cout << &d << endl;

	return 0;
}

根据代码调试结果和运行结果都可以看到,a,b,c,d共用的是一块内存空间。
在这里插入图片描述

1.2引用的特性

• 引用在定义时必须初始化

int main()
{
	int a = 10;

	//如果引用没有被初始化,会报下面这个错误
	// error C2530: “b”: 必须初始化引用
	//int& b;

	int& b = a;
	return 0;
}

• ⼀个变量可以有多个引用
这个特性在之前代码中已经有所体现。

• 引用一旦引用一个实体,再不能引用其他实体

int main()
{
	int a = 10;
	int b = 20;

	int& ra = a;
	ra = b;

	cout << &a << endl;
	cout << &b << endl;
	cout << &ra << endl;
	return 0;
}

上面这个代码我们要格外注意的是ra = b并不是让ra引用b,而是将b赋值ra,这将导致ra连带着a的值发生改变,地址却不会有变化,如果真是引用,那么rab的地址打印结果应该相同。调试结果如下:
在这里插入图片描述
在C++中引用不能改变指向,一旦确定,就无法指向其他变量。

1.3引用的使用

引用在实践中主要是用于引用传参和引用做返回值时减少拷贝提高效率和改变引用对象时同时改变被引用对象。

引用传参举个最简单的Swap函数例子:

void Swap(int& x, int& y)
{
	int temp = x;
	x = y;
	y = temp;
}

int main()
{
	int a = 10, b = 20;
	Swap(a, b);
	cout << a << endl;//20
	cout << b << endl;//10
	return 0;
}

之前写Swap函数传参我们要借助指针传参,因为直接传参传的是形参,形参的改变不会改变实参,我们现在可以用引用来代替指针的写法更便捷,因为引用传参不需要显式解引用(*)或取地址(&)操作。引用必须初始化且不能重新绑定,减少了空指针风险。

引用传参跟指针传参功能是类似的,引用传参相对更方便⼀些。
引用和指针在实践中相辅相成,功能有重叠性,但是各有特点,互相不可替代。C++的引用跟其他语言的引用(如Java)是有很大的区别的,除了用法,最大的点,C++引用定义后不能改变指向,Java的引用可以改变指向。

引用做返回值相比传参要复杂一点,我们这里也看一个例子:

int& getElement(int arr[], int index) {
    return arr[index];
}

int main() {
    int data[3] = { 10, 20, 30 };

    getElement(data, 1) = 200; 

    for (int i = 0; i < 3; ++i) 
    {
        cout << data[i] << " ";
    }
    return 0;
}

在这段代码中,getElement 函数使用 引用返回值(int&) 的核心作用是:允许通过函数返回值直接修改原始数组中的元素。

普通值返回(int)的局限性
如果函数返回值类型为 int(值返回),getElement(data, 1) 会返回 data[1]拷贝值(20)。此时执行 getElement(data, 1) = 200; 会报错,因为 无法对临时拷贝值进行赋值(临时值是右值,不能作为赋值的左值)。

引用返回的优势
返回引用时,getElement(data, 1) 等价于 data[1] 的别名。对返回值的赋值操作会直接作用于原始数组元素,就像直接操作 data[1] 一样。

在后面的博文中我们会进一步对引用返回值进行探究。

1.4 const引用

const修饰变量我们在之前的博文中有所提及,大家可以去看指针(一)这篇博文。

在这里我们要用const修饰引用,看下面的例子:

int main()
{
	const int a = 10;

	//error C2440 : “初始化”: 无法从“const int”转换为“int& ”
	//int& ra = a;

	const int& ra = a;//right

	int b = 20;
	const int& rb = b;

	//error C3892 : “rb”: 不能给常量赋值
	//rb++;

	b++;//right
	return 0;
}

C++ 中引用初始化的重要规则:非 const 引用不能绑定到 const 对象,但 const 引用可以绑定到非 const 对象。const引用增加了只读限制,编译器禁止通过该引用修改内存。因为对象的访问权限在引用过程中可以缩小,但是不能放大。

int main()
{
	int a = 10;

	//error C2440 : “初始化”: 无法从“int”转换为“int& ”
	//int& ra = a * 3;

	const int& ra = a * 3;

	double d = 10.3;

	//error C2440: “初始化”: 无法从“double”转换为“int &”
	//int& rd = d;

	const int& rd = d;

	return 0;
}

需要注意的是类似 int& rb = a*3; double d = 12.34; int& rd = d; 这样⼀些场景下a*3的结果保存在⼀个临时对象中, int& rd = d 也是类似,在类型转换中会产生临时对象存储中间值,也就是说,rb和rd引用的都是临时对象,而C++规定临时对象具有常性,所以这里就触发了权限放大,必须要用常引用才可以。

所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象,C++中把这个未命名对象叫做临时对象。

其实这里编译器的报错也不是很对,并不是无法转换,而是C++规定临时对象具有常性,权限要匹配的上。

还要注意的是,这里ra和rd的地址空间并不与a和d的地址空间相同,看调试信息:
在这里插入图片描述

1.5引用与指针的关系

C++中指针和引用就像两个性格迥异的亲兄弟,指针是哥哥,引用是弟弟,在实践中他们相辅相成,功能有重叠性,但是各有自己的特点,互相不可替代。

• 语法概念上引用是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。

• 引用在定义时必须初始化,指针建议初始化,但是语法上不是必须的。

• 引用在初始化时引用⼀个对象后,就不能再引用其他对象;而指针可以在不断地改变指向对象。

• 引用可以直接访问指向对象,指针需要解引用才是访问指向对象。

• sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8byte)

• 指针很容易出现空指针和野指针的问题,引用很少出现,引用使用起来相对更安全⼀些。

2.缺省参数

• 在 C++ 中,缺省参数(Default Arguments) 是指函数声明时为参数指定一个默认值,当函数调用时未传递该参数时,编译器会自动使用默认值。这可以简化函数调用,减少函数重载的数量。(有些地方把缺省参数也叫默认参数)

• 缺省参数分为全缺省和半缺省参数,全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。

• 带缺省参数的函数调用,C++规定必须从左到右依次给实参,不能跳跃给实参。

//C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。
void Func1(int a = 10, int b, int c = 30)//err
void Func1(int a, int b = 20, int c)//err
void Func1(int a = 10, int b, int c)//err


//全缺省
void Func1(int a = 10, int b = 20, int c = 30)//right
{
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
}

int main()
{
	//带缺省参数的函数调用,C++规定必须从左到右依次给实参,不能跳跃给实参。
	Func1(, 2, 3);//err
	func1(, , 3);//err
	Func1(1, , 3);//err
	
	
	Func1();
	Func1(1);
	Func1(1,2);
	Func1(1,2,3);

	return 0;
}

函数声明和定义分离时,缺省参数只能在函数声明中指定,不能在函数定义中重复指定

在这里插入图片描述

3.函数重载

C++支持在同⼀作用域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。这样C++函数调用就表现出了多态行为,使用更灵活。C语言是不支持同⼀作用域中出现同名函数的。

// 1、参数类型不同
void Swap(int& a, int& b)
{
	int temp = a;
	a = b;
	b = temp;
}

void Swap(double& a, double& b)
{
	double temp = a;
	a = b;
	b = temp;
}

// 2、参数个数不同
void f()
{
	cout << "f()" << endl;
}

void f(int a)
{
	cout << "f(int a)" << endl;
}

// 3、参数类型顺序不同
void f(int a, char b)
{
	cout << "f(int a,char b)" << endl;
}

void f(char b, int a)
{
	cout << "f(char b, int a)" << endl;
}


// 返回值不同不能作为重载条件,因为调⽤时也⽆法区分
//error C2556 : “int fxx(void)” : 重载函数与“void fxx(void)”只是在返回类型上不同
//error C2371: “fxx”: 重定义;不同的基类型
void fxx()
{

}

int fxx()
{
	return 0;
}

int main()
{
	fxx();
	return 0;
}

我们要注意一种情况,当函数重载与缺省参数在同一作用域中结合时,极有可能引发二义性(Ambiguity)问题,导致编译器无法确定该调用哪个函数。

void f()
{
	cout << "f()" << endl;
}
void f(int a = 10)
{
	cout << "f(int a)" << endl;
}
int main()
{
	f();// error C2668: “f”: 对重载函数的调用不明确

	return 0;
}

4.inline

在 C++ 中,inline 关键字用于定义内联函数,其核心目的是通过将函数体直接嵌入调用处来减少函数调用的开销,提高程序运行效率。

4.1 内联函数的作用

  1. 减少函数调用开销常规函数调用需要保存寄存器、跳转指令、恢复现场等操作,存在固定开销。
  2. 内联函数会在编译阶段将函数体直接替换到调用处,避免了这些开销,尤其适合短小、高频调用的函数。
inline int add(int a, int b) 
{ 
    return a + b; 
}

int main() 
{
    int c = add(1, 2);// 编译后等价于 int c = 1 + 2;
    return 0;
}

内联函数具有函数的所有优点(类型检查、作用域规则),同时具备宏的展开特性。

4.2语法与规则

在函数声明或定义前加 inline 关键字(通常放在定义处)。

inline void func(); // 声明(可选)
inline void func() { /* 函数体 */ } // 定义(必须标记 inline)

内联函数的限制

  1. 函数体应简洁:复杂函数(如循环、递归、switch)可能被编译器忽略 inline 请求。
  2. 必须在调用前可见:内联函数的定义需在调用点之前(通常放在头文件中),否则编译器无法展开。
  3. 与 static 结合:static inline 函数具有文件作用域,避免链接冲突。

编译器的自主性
inline 是对编译器的建议,而非强制命令。编译器会根据函数复杂度、优化级别等决定是否真正内联。

4.3内联函数与宏的对比

我们之前学过的宏其实有很多隐患,不说隐患,你现在写一个Add宏,你能正确写出来吗?

// 实现⼀个ADD宏函数的常⻅问题
//#define ADD(int a, int b) return a + b;
//#define ADD(a, b) a + b;
//#define ADD(a, b) (a + b)

// 正确的宏实现
#define ADD(a, b) ((a) + (b))
// 为什么不能加分号?
// 为什么要加外⾯的括号?
// 为什么要加⾥⾯的括号?

int main()
{
	int ret = ADD(1, 2);
	cout << ADD(1, 2) << endl;
	
	cout << ADD(1, 2)*5 << endl;
	
	int x = 1, y = 2;
	ADD(x & y, x | y); // -> (x&y+x|y)
	
	return 0;
}

我们可以发现宏定义是有非常多坑的,但是用内联函数就没有以上问题了,特别简单。

inline int Add(int a, int b)
{
	return a + b;
}
特性内联函数
类型安全有(编译时类型检查)无(仅文本替换)
作用域遵循函数作用域规则全局有效
参数计算仅计算一次(按值传递)可能多次计算(如 ADD(x++, y++))
调试支持可调试(保留函数名等信息)调试困难(展开后无原始名称)
语法错误检查无(替换后由编译器检查)

4.4何时使用inline

推荐场景:

  1. 高频调用的小函数
  2. 替代简单宏:避免宏的副作用,同时保持效率。
  3. 模板函数:结合内联减少编译期开销。

不推荐场景:

  1. 函数体复杂(如包含循环、递归):编译器可能拒绝内联,甚至导致代码膨胀。
  2. 大函数:内联会导致目标代码体积增大,可能降低缓存效率。
  3. 递归函数:递归深度可能导致栈溢出,且难以有效内联。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2398449.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

项目交付后缺乏回顾和改进,如何持续优化

项目交付后缺乏回顾和改进可通过建立定期回顾机制、实施反馈闭环流程、开展持续学习和培训、运用数据驱动分析、培养持续改进文化来持续优化。 其中&#xff0c;实施反馈闭环流程尤其重要&#xff0c;它能够确保反馈信息得到有效传递、处理与追踪&#xff0c;形成良好的改进生态…

从0开始学习R语言--Day15--非参数检验

非参数检验 如果在进行T检验去比较两组数据差异时&#xff0c;假如数据里存在异常值&#xff0c;会把数据之间的差异拉的很大&#xff0c;影响正常的判断。那么这个时候&#xff0c;我们可以尝试用非参数检验的方式来比较数据。 假设我们有A&#xff0c;B两筐苹果&#xff0c…

EC2 实例详解:AWS 的云服务器怎么玩?☁️

弹性计算、灵活计费、全球可用&#xff0c;AWS EC2 全攻略 在 AWS 生态中&#xff0c;有两个核心服务是非常关键的&#xff0c;一个是 S3&#xff08;对象存储&#xff09;&#xff0c;另一个就是我们今天的主角 —— Amazon EC2&#xff08;Elastic Compute Cloud&#xff09…

第三发 DSP 点击控制系统

背景 ​ 在第三方 DSP 上投放广告&#xff0c;需要根据 DP Link 的点击次数进行控制。比如当 DP Link 达到 5000 后&#xff0c;后续的点击将不能带来收益&#xff0c;但是后续的广告却要付出成本。因此需要建立一个 DP Link 池&#xff0c;当 DP Link 到达限制后&#xff0c;…

【笔记】在 MSYS2 MINGW64 环境中降级 NumPy 2.2.6 到 2.2.4

&#x1f4dd; 在 MSYS2 MINGW64 环境中降级 NumPy 到 2.2.4 ✅ 目标说明 在 MSYS2 的 MINGW64 工具链环境中&#xff0c;将 NumPy 从 2.2.6 成功降级到 2.2.4。 &#x1f9f0; 环境信息 项目内容操作系统Windows 11MSYS2 终端类型MINGW64&#xff08;默认终端&#xff09;Py…

vue入门环境搭建及demo运行

提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 vue简介&#xff1a;第一步&#xff1a;安装node.jsnode简介第二步&#xff1a;安装vue.js第三步&#xff1a;安装vue-cli工具第四步 &#xff1a;安装webpack第五步…

原始数据去哪找?分享15个免费官方网站

目录 一、找数据的免费官方网站 &#xff08;一&#xff09;国家级数据宝库&#xff1a;权威且全面 1.中国国家统计局 2.香港政府数据中心 3.OECD数据库 &#xff08;二&#xff09;企业情报中心&#xff1a;洞察商业本质 4.巨潮资讯 5.EDGAR数据库 6.天眼查/企查查&a…

宝塔部署 Vue + NestJS 全栈项目

宝塔部署 Vue NestJS 全栈项目 前言一、Node.js版本管理器1、安装2、配置 二、NestJS项目管理&#xff08;等同Node项目&#xff09;1、Git安装2、拉取项目代码3、无法自动认证4、添加Node项目5、配置防火墙&#xff08;两道&#xff09; 三、Vue项目管理1、项目上传2、Nginx安…

# [特殊字符] Unity UI 性能优化终极指南 — LayoutGroup篇

&#x1f3af; Unity UI 性能优化终极指南 — LayoutGroup篇 &#x1f9e9; 什么是 LayoutGroup&#xff1f; LayoutGroup 是一类用于 自动排列子节点 的UI组件。 代表组件&#xff1a; HorizontalLayoutGroupVerticalLayoutGroupGridLayoutGroup 可以搭配&#xff1a; Conte…

2024-2025-2-《移动机器人设计与实践》-复习资料-8……

2024-2025-2-《移动机器人设计与实践》-复习资料-1-7-CSDN博客 08 移动机器人基础编程 单选题&#xff08;6题&#xff09; 在ROS中&#xff0c;用于移动机器人速度控制的消息类型通常是&#xff1f; A. std_msgs/StringB. geometry_msgs/TwistC. sensor_msgs/ImageD. nav_ms…

如何监测光伏系统中的电能质量问题?分布式光伏电能质量解决方案

根据光伏相关技术规范要求&#xff0c;通过10(6)kV~35kV电压等级并网的变流器类型分布式电源应在公共连接点装设满足GB/T 19862要求的A级电能质量监测装置。用于监测分布式光伏发出的电能的质量&#xff0c;指标包括谐波、电压偏差、电压不平衡度、电压波动和闪变等。 CET中电…

SPL 轻量级多源混算实践 4 - 查询 MongoDB

除了以上常见数据源&#xff0c;还有 NoSQL、MQ 等数据源&#xff0c;其中以 MongoDB 最为常用。我们用 SPL 连接 MongoDB 做计算。 导入 MongoDB 数据。 外部库 SPL 支持的多种数据源大概分两类&#xff0c;一类是像 RDB 有 JDBC 直接使用&#xff0c;或者文件等直接读取&a…

Maestro CLI云端测试以及github cl,bitrise原生cl的测试流程

昨天我们了解了maestro测试框架以及maestro studio工具以及创建我们的第一个flow&#xff0c;然后通过例子在maestro cli云端进行测试请求并且成功&#xff0c;今天我们就在我们自己的app上简单的进行三种测试流程&#xff0c;maestro cli云端测试&#xff0c;github cl集成测试…

25年宁德时代新能源科技SHL 测评语言理解数字推理Verify题库

宁德时代新能源科技的SHL测评中&#xff0c;语言理解部分主要考察阅读理解、逻辑填空和语句排序等题型&#xff0c;要求应聘者在17分钟内完成30题。阅读理解需要快速捕捉文章主旨和理解细节信息&#xff1b;逻辑填空则要根据语句逻辑填入最合适的词汇&#xff1b;语句排序是将打…

AutoGenTestCase - 借助AI大模型生成测试用例

想象一下&#xff0c;你正在为一个复杂的支付系统编写测试用例&#xff0c;需求文档堆积如山&#xff0c;边缘场景层出不穷&#xff0c;手动编写让你焦头烂额。现在&#xff0c;有了AutoGenTestCase&#xff0c;这个AI驱动的“测试用例生成机”可以从需求文档中自动生成数百个测…

vue+cesium示例:3Dtiles三维模型高度调整(附源码下载)

接到一位知识星友的邀请&#xff0c;实现他需要3Dtiles三维模型的简单高度调整需求&#xff0c;适合学习Cesium与前端框架结合开发3D可视化项目。 demo源码运行环境以及配置 运行环境&#xff1a;依赖Node安装环境&#xff0c;demo本地Node版本:推荐v18。 运行工具&#xff1a;…

线程池RejectedExecutionException异常

文章目录 1、报错2、定位3、修复4、线程池使用的一点思考 1、报错 检索项目日志时&#xff0c;发现一个异常堆栈信息&#xff0c;核心报错&#xff1a; java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.CompletableFuture$AsyncSupply480a10c7…

当 “欧洲版 Cursor” 遇上安全危机

在 AI 编程助手蓬勃发展的当下&#xff0c;安全问题正成为行业不容忽视的隐忧。近期&#xff0c;AI 编程助手公司 Replit 与号称 “欧洲版 Cursor” 的 Lovable 之间&#xff0c;因安全漏洞问题掀起了一场风波&#xff0c;引发了业界的广泛关注。​ Replit 的员工 Matt Palmer…

【如何在IntelliJ IDEA中新建Spring Boot项目(基于JDK 21 + Maven)】

AA. 我的开发环境配置与核心工具链解析 一、开发环境全览 C:\Users\Again>java -version java version "21.0.1" 2023-10-17 LTS Java(TM) SE Runtime Environment (build 21.0.112-LTS-29) Java HotSpot(TM) 64-Bit Server VM (build 21.0.112-LTS-29, mixed m…

(Python网络爬虫);抓取B站404页面小漫画

目录 一. 分析网页 二. 准备工作 三. 实现爬虫 1. 抓取工作 2. 分析工作 3. 拼接主函数&运行结果 四. 完整代码清单 1.多线程版本spider.py&#xff1a; 2.异步版本async_spider.py&#xff1a; 经常逛B站的同志们可能知道&#xff0c;B站的404页面做得别具匠心&…