深入解析C++引用:从别名机制到函数特性实践
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
的值发生改变,地址却不会有变化,如果真是引用,那么ra
和b
的地址打印结果应该相同。调试结果如下:
在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;//20cout << b << endl;//10return 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;//rightint b = 20;const int& rb = b;//error C3892 : “rb”: 不能给常量赋值//rb++;b++;//rightreturn 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);//errfunc1(, , 3);//errFunc1(1, , 3);//errFunc1();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 内联函数的作用
- 减少函数调用开销常规函数调用需要保存寄存器、跳转指令、恢复现场等操作,存在固定开销。
- 内联函数会在编译阶段将函数体直接替换到调用处,避免了这些开销,尤其适合短小、高频调用的函数。
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)
内联函数的限制
- 函数体应简洁:复杂函数(如循环、递归、switch)可能被编译器忽略 inline 请求。
- 必须在调用前可见:内联函数的定义需在调用点之前(通常放在头文件中),否则编译器无法展开。
- 与 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
推荐场景:
- 高频调用的小函数
- 替代简单宏:避免宏的副作用,同时保持效率。
- 模板函数:结合内联减少编译期开销。
不推荐场景:
- 函数体复杂(如包含循环、递归):编译器可能拒绝内联,甚至导致代码膨胀。
- 大函数:内联会导致目标代码体积增大,可能降低缓存效率。
- 递归函数:递归深度可能导致栈溢出,且难以有效内联。
相关文章:

深入解析C++引用:从别名机制到函数特性实践
1.C引用 1.1引用的概念和定义 引用不是新定义⼀个变量,而是给已存在变量取了⼀个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同⼀块内存空间。比如四大名著中林冲,他有一个外号叫豹子头,类比到C里就…...
Fuse.js:打造极致模糊搜索体验
Fuse.js 完全学习指南:JavaScript模糊搜索库 🎯 什么是 Fuse.js? Fuse.js 是一个轻量、强大且无依赖的JavaScript模糊搜索库。它提供了简单而强大的模糊搜索功能,可以在任何 JavaScript 环境中使用,包括浏览器和 Nod…...
MyBatis分页插件(以PageHelper为例)与MySQL分页语法的关系
MyBatis分页插件(以PageHelper为例)与MySQL分页语法关系总结 MyBatis的分页插件(如PageHelper)底层实现依赖于数据库的分页语法。对于MySQL数据库来说,其分页逻辑最终会转化为LIMIT语句,下面展开详细说明&…...
CentOS 7.9 安装 宝塔面板
在 CentOS 7.9 上安装 宝塔面板(BT Panel) 的完整步骤如下: 1. 准备工作 系统要求: CentOS 7.x(推荐 7.9)内存 ≥ 1GB(建议 2GB)硬盘 ≥ 20GBroot 权限(需使用 root 用户…...
使用Redis作为缓存优化ElasticSearch读写性能
在现代数据密集型应用中,ElasticSearch凭借其强大的全文搜索能力成为许多系统的首选搜索引擎。然而,随着数据量和查询量的增长,ElasticSearch的读写性能可能会成为瓶颈。本文将详细介绍如何使用Redis作为缓存层来显著提升ElasticSearch的读写…...

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

从0开始学习R语言--Day15--非参数检验
非参数检验 如果在进行T检验去比较两组数据差异时,假如数据里存在异常值,会把数据之间的差异拉的很大,影响正常的判断。那么这个时候,我们可以尝试用非参数检验的方式来比较数据。 假设我们有A,B两筐苹果,…...
Linux或者Windows下PHP版本查看方法总结
确定当前服务器或本地环境中 PHP 的版本,可以通过以下几种方法进行操作: 1. 通过命令行检查 这是最直接且常用的方法,适用于本地开发环境或有 SSH 访问权限的服务器。 方法一:php -v 命令 php -v输出示例:PHP 8.1.12 (cli) (built: Oct 12 2023 12:34:56) (NTS) Copyri…...

EC2 实例详解:AWS 的云服务器怎么玩?☁️
弹性计算、灵活计费、全球可用,AWS EC2 全攻略 在 AWS 生态中,有两个核心服务是非常关键的,一个是 S3(对象存储),另一个就是我们今天的主角 —— Amazon EC2(Elastic Compute Cloud)…...

第三发 DSP 点击控制系统
背景 在第三方 DSP 上投放广告,需要根据 DP Link 的点击次数进行控制。比如当 DP Link 达到 5000 后,后续的点击将不能带来收益,但是后续的广告却要付出成本。因此需要建立一个 DP Link 池,当 DP Link 到达限制后,…...
saveOrUpdate 有个缺点,不会把值赋值为null,解决办法
针对 MyBatis-Plus 的 saveOrUpdate 方法无法将字段更新为 null 的问题,这是因为 MyBatis-Plus 默认会忽略 null 值字段。以下是几种解决方案: 方案 1:使用 update(entity, wrapper) 手动指定更新条件 原理:通过 UpdateWrapper …...
Java面试:企业协同SaaS中的技术挑战与解决方案
Java面试:企业协同SaaS中的技术挑战与解决方案 面试场景 在一家知名互联网大厂,面试官老王正在对一位应聘企业协同SaaS开发职位的程序员谢飞机进行技术面试。 第一轮提问:基础技术 老王:谢飞机,你好。首先…...

【笔记】在 MSYS2 MINGW64 环境中降级 NumPy 2.2.6 到 2.2.4
📝 在 MSYS2 MINGW64 环境中降级 NumPy 到 2.2.4 ✅ 目标说明 在 MSYS2 的 MINGW64 工具链环境中,将 NumPy 从 2.2.6 成功降级到 2.2.4。 🧰 环境信息 项目内容操作系统Windows 11MSYS2 终端类型MINGW64(默认终端)Py…...
前端限流如何实现,如何防止服务器过载
前端限流是一种控制请求频率的技术,旨在防止过多的请求在同一时间段内发送到服务器,避免造成服务器过载或触发反爬虫机制。实现前端限流的方法有很多,下面介绍几种常见的策略和技术: 1. 时间窗口算法 时间窗口算法是最简单的限流…...
基于大模型的慢性硬脑膜下血肿预测与诊疗系统技术方案
目录 一、术前阶段二、并发症风险预测三、手术方案制定四、麻醉方案生成五、术后护理与康复六、系统集成方案七、实验验证与统计分析八、健康教育与随访一、术前阶段 1. 数据预处理与特征提取 伪代码: # 输入:患者多模态影像数据(CT/MRI)、病史、生理指标 def preproce…...

vue入门环境搭建及demo运行
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 vue简介:第一步:安装node.jsnode简介第二步:安装vue.js第三步:安装vue-cli工具第四步 :安装webpack第五步…...
git checkout C1解释
git checkout C1 的意思是: 让 Git 切换到某个提交(commit)ID 为 C1 的状态。 🔍 更具体地说: C1 通常是一个 commit 的哈希值(可以是前几位,比如 6a3f9d2) git checkout C1 会让你…...

原始数据去哪找?分享15个免费官方网站
目录 一、找数据的免费官方网站 (一)国家级数据宝库:权威且全面 1.中国国家统计局 2.香港政府数据中心 3.OECD数据库 (二)企业情报中心:洞察商业本质 4.巨潮资讯 5.EDGAR数据库 6.天眼查/企查查&a…...

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

# [特殊字符] Unity UI 性能优化终极指南 — LayoutGroup篇
🎯 Unity UI 性能优化终极指南 — LayoutGroup篇 🧩 什么是 LayoutGroup? LayoutGroup 是一类用于 自动排列子节点 的UI组件。 代表组件: HorizontalLayoutGroupVerticalLayoutGroupGridLayoutGroup 可以搭配: Conte…...
Apache Iceberg 如何实现分布式 ACID 事务:深度解析大数据时代的可靠数据管理
引言:大数据时代的事务挑战 在大数据时代,传统数据库的 ACID 事务模型面临前所未有的挑战: 海量数据:PB 级数据难以使用传统事务机制管理多并发写入:数十甚至上百个作业同时写入同一数据集复杂分析:长时间运行的查询需要一致性视图混合负载:批处理和流处理同时访问相同…...
计算A图片所有颜色占B图片红色区域的百分比
import cv2 import numpy as npdef calculate_overlap_percentage(a_image_path, b_image_path):# 读取A组和B组图像a_image cv2.imread(a_image_path)b_image cv2.imread(b_image_path)# 将图像从BGR转为HSV色彩空间,便于颜色筛选a_hsv cv2.cvtColor(a_image, c…...

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

如何监测光伏系统中的电能质量问题?分布式光伏电能质量解决方案
根据光伏相关技术规范要求,通过10(6)kV~35kV电压等级并网的变流器类型分布式电源应在公共连接点装设满足GB/T 19862要求的A级电能质量监测装置。用于监测分布式光伏发出的电能的质量,指标包括谐波、电压偏差、电压不平衡度、电压波动和闪变等。 CET中电…...
电子电路:全面深入了解晶振的定义、作用及应用
本次了解重点: 1.压电效应的数学描述 2.生产工艺以及关键工序 3.电路设计部分如负阻原理和匹配电容计算 4.失效案例比如冷启动问题 5.新形态晶振技术引入5G和量子计算 6.温补晶振的补偿机制 7故障案例讲解-更换负载电池或增加预热电路 蓝牙音频断续-频偏导致 工控机死机-起振电…...
Day-15【选择与循环】选择结构-if语句
目录 一、if语句 (1)单分支选择结构 (2)双分支选择结构 (3)多分支选择结构 (4)if-else的嵌套使用 二、开关分支语句(switch) (1)…...
定时器时钟来源可以从输入捕获引脚输入
外部时钟模式 和 输入捕获。 核心结论: 外部时钟模式的输入引脚 ≠ 输入捕获功能的输入引脚(通常情况): 外部时钟模式有专用的输入引脚 (ETR) 和可选的替代输入通道(如TI1, TI2)。 输入捕获功能有自己的专…...

SPL 轻量级多源混算实践 4 - 查询 MongoDB
除了以上常见数据源,还有 NoSQL、MQ 等数据源,其中以 MongoDB 最为常用。我们用 SPL 连接 MongoDB 做计算。 导入 MongoDB 数据。 外部库 SPL 支持的多种数据源大概分两类,一类是像 RDB 有 JDBC 直接使用,或者文件等直接读取&a…...
星敏感器:卫星姿态测量的“星空导航仪”
星敏感器:卫星姿态测量的“星空导航仪” 1. 引言 在卫星、航天器和深空探测器的姿态控制系统中,星敏感器(Star Tracker) 是最精确的姿态测量设备之一。它通过识别恒星的位置,计算出航天器在惯性空间中的三轴姿态&…...
Cat.1与Cat.4区别及应用场景
Cat.1 和 Cat.4 都是 LTE(4G)网络中的终端设备类别,主要区别在于 数据传输速率、复杂度和功耗,这直接影响了它们的应用场景和成本。 以下是它们的主要区别: 数据传输速率 (核心区别): Cat.1 (Category 1)&…...