【C++】inline函数(内联函数)

文章目录

  • 前言
  • 一、宏函数的缺陷
  • 二、inline函数
    • 1.inline函数的展开规则
    • 2.inline的设计分析
    • 3.inline函数不建议声明和定义分离


前言

C程序频繁调用函数会使代码效率降低,因为创建函数栈帧需要消耗时间。于是C语言引入宏函数的概念,使用宏函数来替代一些功能简单的函数,宏函数会在预处理时替换展开,就不需要建立栈帧,可以提高代码效率。但使用宏函数需要注意运算符优先级等问题很容易出错,且不方便调试,不好用。于是C++引入inline的概念,设计inline的目的就是替代C语言的宏函数,它使用起来更方便,且不易出错。


一、宏函数的缺陷

示例(把普通ADD函数改成宏函数的实现方式):

#include<iostream>
using namespace std;int ADD(int a, int b)//普通ADD函数
{return a + b;
}int main()
{cout << ADD(1, 2) << endl;cout << ADD(1, 2) * 5 << endl;return 0;
}

在这里插入图片描述
那么如何把上述代码中ADD函数改成宏函数的实现方式呢,我们很容易想当然的写成如下形式:

#include<iostream>
using namespace std;#define ADD(a, b) a + bint main()
{cout << ADD(1, 2) << endl;cout << ADD(1, 2) * 5 << endl;//展开是:1 + 2 * 5return 0;
}

在这里插入图片描述
cout << ADD(1, 2) * 5 << endl; 的打印结果出现问题。
因为宏函数在预处理时是直接把ADD(1, 2)替换成 1+2,由于运算符优先级2先和5相乘再加1,得到的结果就出现问题了。那么为了避免出现这种问题,应该在给a + b外面加一个括号。如下:

#include<iostream>
using namespace std;#define ADD(a, b) (a + b) //给a + b外面加一个括号int main()
{cout << ADD(1, 2) << endl;cout << ADD(1, 2) * 5 << endl;//给a + b外面加了括号后得到了正确的结果ADD(5 & 4, 1 | 2);//展开为:(5 & 4 + 1 | 2)return 0;
}

在这里插入图片描述
ADD(5 & 4, 1 | 2); 该宏函数还是不能正确计算这种式子的结果。
我们希望ADD函数把 5&4 的结果和 1|2 的结果进行相加,但由于展开后 ‘+’ 的优先级高于 ‘&‘和’|’ 运算符,会先进行加法,那么得到的结果就会出现问题了。为了避免这个问题,还得给 a 和 b 分别再套一个括号。如下:

#include<iostream>
using namespace std;#define ADD(a, b) ((a) + (b)) //给 a 和 b 分别再套一个括号int main()
{cout << ADD(1, 2) << endl;cout << ADD(1, 2) * 5 << endl;ADD(5 & 4, 1 | 2);//展开为:((5 & 4) + (1 | 2))return 0;         //防止了因运算符优先级引发的问题
}

总结:C语言实现宏函数会在预处理时替换展开,就不需要建立栈帧,就可以提高效率。但是宏函数实现需要注意运算符优先级等问题很容易出错的,且不方便调试,所以不是特别好用。于是C++引入了inline的概念,设计inline的目的就是替代C语言的宏函数,它使用起来更加方便,而且不易出错。下面展开介绍。

二、inline函数

1.inline函数的展开规则

用inline修饰的函数叫做内联函数,编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就不需要建立栈帧了,就可以提高效率。

注:inline对于编译器而言只是⼀个建议,也就是说,你给函数加了inline编译器也可以选择在调用的地方不展开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适用于频繁调用的短小函数,对于递归函数,代码相对多一些的函数,加上inline也会被编译器忽略。

下面我要用vs2022演示inline函数在什么情况展开(在debug版本下调试代码,然后观察代码的汇编指令来确定inline函数是否展开)

注:vs编译器为了方便程序员调试代码,设置了debug版本下面默认不展开inline函数,那么我们就无法观察inline函数在什么情况展开了。为了正常观察inline函数的展开情况,我们需要修改vs编译器的一些设置,这样debug版本想也能正常展开inline函数了。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

设置好vs编译器后,在调试时观察以下代码的汇编指令:

代码一(代码短的inline函数会展开):

inline int Add(int x, int y)//一般来说代码短的inline函数会展开
{int ret = x + y;ret++;ret++;ret++;return ret;
}int main()
{int ret = Add(1, 2);// 可以通过汇编观察程序是否展开// 有call Add语句就是没有展开,没有就是展开了return 0;
}

在这里插入图片描述
没有发现call Add语句,证明Add函数直接在此展开了

补充:call Add语句的意思是调用Add函数。如果发现了call Add语句,就会调用Add函数,然后跳转到Add函数,创建Add函数的栈帧;如果没有发现call Add语句,证明Add函数直接在此展开了,可以直接在此执行Add函数里的指令,就不需要调用Add函数以及创建Add函数的栈帧,可以提升代码效率。

代码二(代码较长的inline函数不展开):

inline int Add(int x, int y)//故意加长inline函数的代码长度
{int ret = x + y;ret++;ret++;ret++;ret++;ret++;ret++;ret++;ret++;ret++;ret++;ret++;ret++;return ret;
}int main()
{int ret = Add(1, 2);return 0;
}

在这里插入图片描述
发现call Add语句,证明Add函数没有在此展开,而是跳转到Add函数去执行后续相加的操作了。

2.inline的设计分析

既然inline这么好用,可以提升代码效率,而且还不会出错。那么为什么把它设计成只能展开代码比较短的函数呢?设计成加了inline的所有函数都会展开,对代码效率的提升岂不是更大吗?
这么做确实可以进一步提升效率,但会显著增加编译成的可执行程序的代码长度,inline实际上是用以空间换时间的方式来提升代码的效率。

举个例子:
在这里插入图片描述
所以要把inline设计成只能展开代码比较短的函数,这样就能保证inline既不会对可执行程序的大小造成比较大的影响,又可以提升代码的运行效率。

3.inline函数不建议声明和定义分离

inline函数不建议声明和定义分离到两个文件,分离会导致链接错误。

(1)先观察一下普通函数声明和定义分离的情况
在这里插入图片描述
在编译过程中,每个C++文件都会生成一个符号表,用来存储这个C++文件里所有函数的函数名及其地址。在链接阶段,所有obj文件和链接库⼀起链接生成最终的可执行程序(exe程序),同时所有编译过程中形成的符号表合并成一个符号表,供exe程序使用。
在这里插入图片描述
在合并后的符号表中确实找到了 f 函数的有效地址,所以代码正常运行。

(2)再观察inline函数声明和定义分离的情况
在这里插入图片描述
在这里插入图片描述

总结:inline不建议声明和定义分离到两个⽂件,分离会导致链接错误。因为编译器认为inline函数会直接在使用的地方展开,inline函数的地址就不会被放进符号表,符号表中没有inline函数的有效地址,链接时就会报错。


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

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

相关文章

Android OpenGLES2.0开发(九):图片滤镜

“当你改变想法的时候&#xff0c;记得也要改变你的世界。”——诺曼文森特皮尔 Android OpenGLES开发&#xff1a;EGL环境搭建Android OpenGLES2.0开发&#xff08;一&#xff09;&#xff1a;艰难的开始Android OpenGLES2.0开发&#xff08;二&#xff09;&#xff1a;环境搭…

易语言OCR证件照文字识别

一.引言 文字识别&#xff0c;也称为光学字符识别&#xff08;Optical Character Recognition, OCR&#xff09;&#xff0c;是一种将不同形式的文档&#xff08;如扫描的纸质文档、PDF文件或数字相机拍摄的图片&#xff09;中的文字转换成可编辑和可搜索的数据的技术。随着技…

指针练习题15道【C语言】

1.利用指针变量将一个数组中的数据反向输出。 声明&#xff1a; void work1(int *, int); // 声明反向遍历 void work1_1(int *, int); // 声明正向遍历实现&#xff1a; // 利用指针变量将一个数组中的数据反向输出&#xff0c;反向遍历 void work1( int *p,int len) {p (…

单节点calico性能优化

在单节点上部署calicov3273后&#xff0c;发现资源占用 修改calico以下配置是资源消耗降低 1、因为是单节点&#xff0c;没有跨节点pod网段组网需要&#xff0c;禁用overlay方式网络(ipip&#xff0c;vxlan),使用route方式网络 配置calico-node的环境变量 CALICO_IPV4POOL_I…

在 Spring Boot 3 中实现基于角色的访问控制

基于角色的访问控制 (RBAC) 是一种有价值的访问控制模型,可增强安全性、简化访问管理并提高效率。它在管理资源访问对安全和运营至关重要的复杂环境中尤其有益。 我们将做什么 我们有一个包含公共路由和受限路由的 Web API。受限路由需要数据库中用户的有效 JWT。 现在用户…

电子系统中用于降噪的接地

当接地系统中存在EMI、RFI 或由焊机、变速驱动器、电器等引起的电脉冲等干扰时&#xff0c;它们会在中性线和接地之间产生共模噪声&#xff0c;可能会影响电子设备。 国家电气规范 (NEC) 允许安装隔离接地插座 (IG)&#xff0c;即磁轭和接地端子之间没有连接。轭是插座后面的金…

SmartX分享:SMTX ZBS 中 RDMA 技术简介

目录 背景如何实现存储网络是什么TCP/IPRDMARDMA 工作原理RDMA 的实现方案 ZBS 支持 RDMA 的要求 参考 背景 我们清楚&#xff0c;分布式存储将利用网络作不同设备的互联。最基础的如TCP/IP的IP SAN&#xff0c;进阶的有FC SAN、IB等等。 SmartX 支持 10G以上的TCP/IP网络作为…

《Vue3实战教程》5:响应式基础

如果您有疑问&#xff0c;请观看视频教程《Vue3实战教程》 响应式基础​ API 参考 本页和后面很多页面中都分别包含了选项式 API 和组合式 API 的示例代码。现在你选择的是 组合式 API。你可以使用左侧侧边栏顶部的“API 风格偏好”开关在 API 风格之间切换。 声明响应式状态…

QT网络(二):TCP通信

传输层概念 传输控制协议&#xff08;transmission control protocol&#xff0c;TCP&#xff09;是一种被大多数 Internet 网络协议用于数据传输的底层网络协议&#xff0c;它是可靠的、面向流和连接的传输协议&#xff0c;特别适合用于连续数据传输。 应用层在网络模型中的…

【记录50】uniapp安装uview插件,样式引入失败分析及解决

SassError: Undefined variable: "$u-border-color". 表示样式变量$u-border-color没定义&#xff0c;实际是定义的 首先确保安装了scss/sass 其次&#xff0c;根目录下 app.vue中是否全局引入 <style lang"scss">import /uni_modules/uview-ui/in…

TypeScript 与 JavaScript

文章目录 一、为 JavaScript 库添加类型定义(一)什么是类型定义文件(.d.ts 文件)(二)手动编写类型定义和使用现有类型定义(如 DefinitelyTyped)手动编写类型定义使用现有类型定义(如 DefinitelyTyped)二、在 TypeScript 项目中使用流行的 JavaScript 库(如 jQuery、…

算法—动态规划

一、简介 动态规划&#xff08;Dynamic Programming&#xff0c;简称 DP&#xff09;是一种通过将原问题分解为若干个子问题来求解最优化问题的算法思想。动态规划常常用于解决那些可以被分解为更小的重叠子问题的场景。 与分治法的区别在于&#xff0c;分治法会将问题分解成独…

Android GO 版本锁屏声音无效问题

问题描述 Android go版本 在设置中打开锁屏音开关&#xff0c;息屏灭屏还是无声音 排查 vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\keyguard\KeyguardViewMediator.java private void setupLocked() {...String soundPath Settings.G…

使用 NVIDIA DALI 计算视频的光流

引言 光流&#xff08;Optical Flow&#xff09;是计算机视觉中的一种技术&#xff0c;主要用于估计视频中连续帧之间的运动信息。它通过分析像素在时间维度上的移动来预测运动场&#xff0c;广泛应用于目标跟踪、动作识别、视频稳定等领域。 光流的计算传统上依赖 CPU 或 GP…

Tomcat的安装即使用

Tomcat的概念 Tomcat服务器是Java语言开发的&#xff0c;免费的开放源代码的Web应用服务器。 Tomcat处理静态HTML的能力远不及Apache或者Nginx&#xff0c;通常是作为一个Servlet和JSP容器&#xff0c;单独运行在后端。 Tomcat是由三个功能组合而成&#xff1a; java servlet&…

Linux:进程(环境变量、程序地址空间)

目录 冯诺依曼体系结构 操作系统 设计操作系统的目的 操作系统的管理 进程 PCB fork 进程状态 进程状态查看 僵尸进程 孤儿进程 进程优先级 查看、修改进程优先级命令 竞争、独立、并行、并发 进程切换 活动队列和运行队列 活动队列 过期队列 active指针…

对于使用exe4j打包,出现“NoClassDefFoundError: BOOT-INF/classes”的解决方案

jar使用exe4j打包exe&#xff0c;出现NoClassDefFoundError: BOOT-INF/classes 注意选取的jar包是使用build&#xff0c;而不是maven中的install 本文介绍解决这个方法的方案 点击Project Structure 按照如图所示选择 选择main class&#xff0c;选择你要打的main 如果遇到/M…

uniapp连接蓝牙操作(蓝牙设备地锁)

介绍&#xff1a; 本文采用uni-app框架来创建一个简单的用户界面&#xff0c;用于搜索、连接和发送命令给蓝牙设备。 1.打开蓝牙适配器 function openBluetooth() {uni.openBluetoothAdapter({success() {uni.offBluetoothDeviceFound();// 监听新设备发现事件uni.onBlueto…

web:pc端企业微信登录-vue版

官方文档&#xff1a;developer.work.weixin.qq.com/document/pa… 不需要调用ww.register&#xff0c;直接调用ww.createWWLoginPanel即可创建企业微信登录面板 - 文档 - 企业微信开发者中心 (qq.com) 引入 //通过 npm 引入 npm install wecom/jssdk import * as ww from we…

外观模式的理解和实践

外观模式&#xff08;Facade Pattern&#xff09;是一种常用的软件设计模式&#xff0c;它提供了一个统一的接口&#xff0c;用来访问子系统中的一群接口。该模式定义了一个高层的接口&#xff0c;使得子系统更容易使用。简单来说&#xff0c;外观模式就是通过引入一个外观角色…