深入解析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的值发生改变,地址却不会有变化,如果真是引用,那么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.pswp.cn/web/82388.shtml

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

相关文章

【从0-1的HTML】第1篇:HTML简介

1 HTML简介 HTML是用来描述网页的一种语言,是超文本标记语言的缩写(Hyper Text Markup Language),不属于编程语言的范畴&#xff0c;属于一种标记语言。 标记语言使用一套标记标签(Markup tag)&#xff0c;又称为标签,HTML就是使用标记标签来描述网页。 1.2 HTML标签 1、HTM…

vue+cesium示例:地形开挖(附源码下载)

基于cesium和vue绘制多边形实现地形开挖效果&#xff0c;适合学习Cesium与前端框架结合开发3D可视化项目。 demo源码运行环境以及配置 运行环境&#xff1a;依赖Node安装环境&#xff0c;demo本地Node版本:推荐v18。 运行工具&#xff1a;vscode或者其他工具。 配置方式&#x…

qwen大模型在进行词嵌入向量时,针对的词表中的唯一数字还是其他的?

qwen大模型在进行词嵌入向量时,针对的词表中的唯一数字还是其他的? Qwen大模型进行词嵌入向量时,针对的是词表中每个 Token 对应的唯一数字(Token ID) ,核心逻辑结合词表构建、嵌入过程展开 一、Qwen 词表与 Token ID Qwen 用 BPE 分词器(基于 tiktoken,以 cl100k 为…

动态规划-1143.最长公共子序列-力扣(LeetCode)

一、题目解析 对于给定了两个字符串中&#xff0c;需要找到最长的公共子序列&#xff0c;也就是两个字符串所共同拥有的子序列。 二、算法原理 1、状态表示 dp[i][j]&#xff1a;表示s1的[0,i]和s2的[0,j]区间内所有子序列&#xff0c;最长子序列的长度 2、状态转移方程 根…

互联网c++开发岗位偏少,测开怎么样?

通过这标题&#xff0c;不难看出问这个问题的&#xff0c;就是没工作过的。如果工作过&#xff0c;那就是不断往深的钻研&#xff0c;路越走越窄&#xff0c;找工作一般就是找原来方向的。没工作过的&#xff0c;那一般就是学生。 学生找什么方向的工作比较好&#xff1f; 学生…

推荐算法八股

跑路了&#xff0c;暑期0offer&#xff0c;华为主管面挂了&#xff0c;真幽默&#xff0c;性格测评就挂了居然给我一路放到主管面&#xff0c;科大迅飞太嚣张&#xff0c;直接跟人说后面要面华为&#xff0c;元戎启行&#xff0c;学了C后python完全忘了怎么写&#xff0c;挺尴尬…

Spring Boot微服务架构(九):设计哲学是什么?

一、Spring Boot设计哲学是什么&#xff1f; Spring Boot 的设计哲学可以概括为 ​​“约定优于配置”​​ 和 ​​“开箱即用”​​&#xff0c;其核心目标是​​极大地简化基于 Spring 框架的生产级应用的初始搭建和开发过程​​&#xff0c;让开发者能够快速启动并运行项目…

前端导入Excel表格

前端如何在 Vue 3 中导入 Excel 文件&#xff08;.xls 和 .xlsx&#xff09;&#xff1f; 在日常开发中&#xff0c;我们经常需要处理 Excel 文件&#xff0c;比如导入数据表格、分析数据等。文章将在 Vue 3 中实现导入 .xls 和 .xlsx 格式的文件&#xff0c;并解析其中的数据…

C++和C#界面开发方式的全面对比

文章目录 C界面开发方式1. **MFC&#xff08;Microsoft Foundation Classes&#xff09;**2. **Qt**3. **WTL&#xff08;Windows Template Library&#xff09;**4. **wxWidgets**5. **DirectUI** C#界面开发方式1. **WPF&#xff08;Windows Presentation Foundation&#xf…

刷leetcode hot100返航必胜版--链表6/3

链表初始知识 链表种类&#xff1a;单链表&#xff0c;双链表&#xff0c;循环链表 链表初始化 struct ListNode{ int val; ListNode* next; ListNode(int x): val&#xff08;x&#xff09;,next(nullptr) {} }; //初始化 ListNode* head new ListNode(5); 删除节点、添加…

软考 系统架构设计师系列知识点之杂项集萃(78)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之杂项集萃&#xff08;77&#xff09; 第139题 以下关于软件测试工具的叙述&#xff0c;错误的是&#xff08;&#xff09;。 A. 静态测试工具可用于对软件需求、结构设计、详细设计和代码进行评审、走查和审查 B. 静…

【Unity】云渲染

1 前言 最近在搞Unity云渲染的东西&#xff0c;所以研究了下官方提供的云渲染方案Unity Renderstreaming。注&#xff1a;本文使用的Unity渲染管线是URP。 2 文档 本文也只是介绍基本的使用方法&#xff0c;更详细内容参阅官方文档。官方文档&#xff1a;Unity Renderstreamin…

组相对策略优化(GRPO):原理及源码解析

文章目录 PPO vs GRPOPPO的目标函数GRPO的目标函数KL散度约束与估计ORM监督RL的结果PRM监督RL的过程迭代RL算法流程 GRPO损失的不同版本GRPO源码解析 DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models PPO vs GRPO PPO的目标函数 J P P O…

Linux或者Windows下PHP版本查看方法总结

确定当前服务器或本地环境中 PHP 的版本,可以通过以下几种方法进行操作: 1. 通过命令行检查 这是最直接且常用的方法,适用于本地开发环境或有 SSH 访问权限的服务器。 方法一:php -v 命令 php -v输出示例:PHP 8.1.12 (cli) (built: Oct 12 2023 12:34:56) (NTS) Copyri…

[Linux] MySQL源码编译安装

目录 环境包安装 创建程序用户 解压源码包 配置cmake ​编辑编译 安装 配置修改属性 属主和属组替换成mysql用户管理 系统环境变量配置 初始化数据库 服务管理 启动 环境包安装 yum -y install ncurses ncurses-devel bison cmake gcc gcc-c 重点强调&#xff1a;采…

【C++项目】负载均衡在线OJ系统-1

文章目录 前言项目结果演示技术栈&#xff1a;结构与总体思路compiler编译功能-common/util.hpp 拼接编译临时文件-common/log.hpp 开放式日志-common/util.hpp 获取时间戳方法-秒级-common/util.hpp 文件是否存在-compile_server/compiler.hpp 编译功能编写&#xff08;重要&a…

转战海外 Web3 远程工作指南

目录 一、明确职业目标和技能 二、准备常用软件 &#xff08;一&#xff09;通讯聊天工具 &#xff08;二&#xff09;媒体类平台 &#xff08;三&#xff09;线上会议软件 &#xff08;四&#xff09;办公协作工具 &#xff08;五&#xff09;云存储工具 &#xff08;六…

MongoDB账号密码笔记

先连接数据库&#xff0c;新增用户密码 admin用户密码 use admin db.createUser({ user: "admin", pwd: "yourStrongPassword", roles: [ { role: "root", db: "admin" } ] })用户数据库用户密码 use myappdb db.createUser({ user: &…

CSS强制div单行显示不换行

在CSS中&#xff0c;要让<div>的内容强制单行显示且不换行&#xff0c;可通过以下属性组合实现&#xff1a; 核心解决方案&#xff1a; css 复制 下载 div {white-space: nowrap; /* 禁止文本换行 */overflow: hidden; /* 隐藏溢出内容 */text-overflow: e…

RK3568-快速部署codesys runtime

前期准备 PC-win10系统 RK3568-debian系统,内核已打入实时补丁,开启ssh服务。PC下载安装CODESYS Development System V3.5.17.0 https://store.codesys.com/en/codesys.html#product.attributes.wrapperPC下载安装 CODESYS Control for Linux ARM64 SL 4.1.0.0.package ht…