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

article/2025/6/7 14:36:49

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;//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 内联函数的作用

  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.hkcw.cn/article/ZlaXbCXYnB.shtml

相关文章

【vue+ts】找不到模块“./App.vue”或其相应的类型声明

报错&#xff1a;找不到模块“./App.vue”或其相应的类型声明。 原因&#xff1a;typescript只能理解.ts文件&#xff0c;无法理解.vue文件。 解决&#xff1a;在src/env.d.ts下添加&#xff1a; /// <reference types"vite/client" /> // 三斜线引用告诉编译…

HTTP Error 400 Bad request 问题分析解决

文章目录 1.问题描述&#xff1a;2.异常信息如下&#xff1a;3.分析异常信息&#xff1a;4.总结&#xff1a; 1.问题描述&#xff1a; 前端保存老是报错HTTP ERROR 400 Bad Request。经过异常分析得出是前端传参导致的后端框架的验证拦截&#xff0c;包的错误。 2.异常信息如下…

数据库的操作

1.查看数据库 show databases; 2.库的创建 create database [IF NOT EXITS] db_name [creat_specification];[]内的是可选选项&#xff0c;IF NOT EXIT表示如果数据库名为db_name的数据库存在就创建数据库&#xff0c;否则就不创建&#xff0c;creat_specification是创建的特…

IP话机和APP拨打电话的区别

‌IP话机和IP电话App&#xff08;如Zoom Phone、Microsoft Teams、Skype等&#xff09;均基于互联网协议&#xff08;VoIP&#xff09;技术实现通话&#xff0c;但在硬件形态、使用场景、功能侧重等方面存在显著差异。以下是主要区别&#xff1a; 1. 硬件形态与部署 IP话机 物…

el-select 实现分页加载,切换也数滚回到顶部,自定义高度

el-select 实现分页加载&#xff0c;切换也数滚回到顶部&#xff0c;自定义高度 1.html <el-form-item label"俱乐部&#xff1a;" prop"club_id" label-width"120px"><el-select :disabled"Boolean(match_id)" style"w…

帝可得- 人员管理

一.需求说明 人员管理业务流程如下&#xff1a; 登录系统&#xff1a; 首先&#xff0c;后台管理人员需要登录到帝可得后台管理系统中。 新增工作人员&#xff1a; 登录系统后&#xff0c;管理人员可以新增工作人员&#xff0c;包括姓名、联系方式等信息。 关联角色&#xf…

【Java Web】7.事务管理AOP

&#x1f4d8;博客主页&#xff1a;程序员葵安 &#x1faf6;感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb; 文章目录 一、事务管理 1.1 事务回顾 1.2 Spring事务管理 1.3 事务进阶 rollbackFor propagation 二、AOP 2.1 AOP概述 2.2 AOP快速入门…

Matlab实现LSTM-SVM回归预测,作者:机器学习之心

Matlab实现LSTM-SVM回归预测&#xff0c;作者&#xff1a;机器学习之心 目录 Matlab实现LSTM-SVM回归预测&#xff0c;作者&#xff1a;机器学习之心效果一览基本介绍程序设计参考资料 效果一览 基本介绍 代码主要功能 该代码实现了一个LSTM-SVM回归预测模型&#xff0c;核心流…

【开源工具】Python+PyQt5打造智能桌面单词记忆工具:悬浮窗+热键切换+自定义词库

&#x1f4da;【深度解析】PythonPyQt5打造智能桌面单词记忆工具&#xff1a;悬浮窗热键切换自定义词库 &#x1f308; 个人主页&#xff1a;创客白泽 - CSDN博客 &#x1f525; 系列专栏&#xff1a;&#x1f40d;《Python开源项目实战》 &#x1f4a1; 热爱不止于代码&#x…

第二十二章 Shell脚本入门

第二十二章 Shell脚本入门 Shell脚本就是包含一系列命令的文件。Shell读取该文件并执行其中的命令&#xff0c;Shell的独特之处在于它即使系统强大的命令接口&#xff0c;又是脚本语言解释器。 创建并执行Shell脚本 创建并执行脚本&#xff0c;要做到3件事: 编写脚本。将脚…

Pandas取代Excel?

有人在知乎上提问&#xff1a;为什么大公司不用pandas取代excel&#xff1f; 而且列出了几个理由&#xff1a;Pandas功能比Excel强大&#xff0c;运行速度更快&#xff0c;Excel除了简单和可视化界面外&#xff0c;没有其他更多的优势。 有个可怕的现实是&#xff0c;对比Exce…

网络安全运维实训室建设方案

一、网络安全运维人才需求与实训困境 在数字化时代&#xff0c;网络安全已成为国家安全、社会稳定和经济发展的重要基石。随着信息技术的飞速发展&#xff0c;网络安全威胁日益复杂多样&#xff0c;从个人隐私泄露到企业商业机密被盗&#xff0c;从关键基础设施遭受攻击到社会…

【C++篇】STL适配器(下篇):优先级队列与反向迭代器的底层奥秘

&#x1f4ac; 欢迎讨论&#xff1a;在阅读过程中有任何疑问&#xff0c;欢迎在评论区留言&#xff0c;我们一起交流学习&#xff01; &#x1f44d; 点赞、收藏与分享&#xff1a;如果你觉得这篇文章对你有帮助&#xff0c;记得点赞、收藏&#xff0c;并分享给更多对C感兴趣的…

在使用十字滑台的过程中,我们需要注意哪些关键事项呢

在使用十字滑台的过程中&#xff0c;需要注意以下关键事项&#xff1a; 安全操作&#xff1a;在使用十字滑台时&#xff0c;务必要注意安全&#xff0c;戴好手套和护目镜&#xff0c;避免发生意外伤害。 稳定支撑&#xff1a;确保十字滑台稳固地放置在平坦稳定的表面上&#x…

【笔记】用命令手动下载并安装 tokenizers 库.whl文件(Python 3.12+)

Python 3.12 虚拟环境中安装 tokenizers 教程笔记 在 Python 3.12 虚拟环境中安装 tokenizers 库时&#xff0c;我们可能会遇到pip install tokenizers安装失败和找不到适配版本的公开 whl 文件&#xff0c;从而导致tokenizers库缺失的问题。 经过探索&#xff0c;我们找到了…

光子器件仿真软件基础与基于优化方法的器件逆向设计---案例片上米散射结构色超构表面单元仿真

以下为针对片上米散射结构色超构表面单元仿真的技术要点和方法整理&#xff1a; 仿真流程框架 import meep as mp import numpy as np # 创建超构表面单元模型 cell_size mp.Vector3(1, 1, 0) geometry [mp.Cylinder(height0.5, radius0.2, materialmp.Medium(index3.5))] …

软件工程的定义与发展历程

文章目录 一、软件工程的定义二、软件工程的发展历程1. 前软件工程时期(1940s-1960s)2. 软件工程诞生(1968)3. 结构化方法时期(1970s)4. 面向对象时期(1980s)5. 现代软件工程(1990s-至今) 三、软件工程的发展趋势 一、软件工程的定义 软件工程是应用系统化、规范化、可量化的方…

基于SDN环境下的DDoS异常攻击的检测与缓解

参考以下两篇博客&#xff0c;最后成功&#xff1a; 基于SDN的DDoS攻击检测和防御方法_基于sdn的ddos攻击检测与防御-CSDN博客 利用mininet模拟SDN架构并进行DDoS攻击与防御模拟&#xff08;Ryumininetsflowpostman&#xff09;_mininet模拟dos攻击-CSDN博客 需求 H2 模拟f…

VS下C++及C#项目打包发布方法

一.打包为单一exe文件 1.打开项目属性页&#xff08;右键项目 → 属性&#xff09; 2.选择配置&#xff08;如 Release&#xff09; 3.项目属性→ C/C → 代码生成→ 运行库 将 运行时库&#xff08;Runtime Library&#xff09; 设置为&#xff1a; /MT&#xff08;Release 模…

【免费】2004-2020年各省电力消费量数据

2004-2020年各省电力消费量数据 1、时间&#xff1a;2004-2020年 2、来源&#xff1a;国家统计局、统计年鉴 3、指标&#xff1a;行政区划代码、地区、年份、电力消费量(亿千瓦小时) 4、范围&#xff1a;31省 5、指标说明&#xff1a;电力消费量是指在一定时期内&#xff…