【C++】内联函数(inline function)详解

news2024/11/15 7:20:48

🦄个人主页:小米里的大麦-CSDN博客

🎏所属专栏:C++_小米里的大麦的博客-CSDN博客

🎁代码托管:C++: 探索C++编程精髓,打造高效代码仓库 (gitee.com)

⚙️操作环境:Visual Studio 2022

目录

一、前言

语法: 在函数定义前加上关键字 inline。

二、内联函数的正确使用

三、容易犯的错误

错误1:内联函数体太大

错误2:递归函数内联

错误3:条件编译下的调试问题

四、内联函数的特性

实践建议

五、与宏定义的区别

六、代码示例总结

总结 

共勉


一、前言

内联函数是一种建议编译器在调用函数时,不使用普通的函数调用机制(如压栈、跳转等),而是将函数体直接嵌入到调用点。它的优点是可以减少函数调用的开销,特别是对于频繁调用的小函数。以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,以提升程序运行的效率。

语法: 在函数定义前加上关键字 inline

inline int Add(int x, int y) {
    return (x + y) * 10;
}
  • 优点

    • 避免了函数调用的开销(如压栈、跳转、返回等)。
    • 适用于短小、频繁调用的函数。
  • 缺点

    • 如果函数较大,频繁嵌入会增大代码体积,可能导致性能下降(因为内存缓存可能溢出)。
    • 递归函数不能内联,因为无法确定函数的调用次数。

注意:

  1. 适用于短小的频繁调用的函数
  2. inline对于编译器仅仅只是一个建议,最终是否成为inline,编译器自己决定
  3. 如果函数过长或者过于复杂,即使加了inline也会被编译器否决掉(主要代表过长函数、递归函数),多长算长:编译器决定,每个编译器不同,vs默认在10行左右。
  4. 默认debug模式下,inline不会起作用,否则不方便调试了

二、内联函数的正确使用

内联函数适用于短小且逻辑简单的函数,因为函数体直接嵌入到代码中能减少函数调用开销,但函数过大会增加可执行文件的体积。

  • 适用场景频繁调用的短小函数,例如数学运算或简单的判断逻辑:

inline int Multiply(int x, int y) {
    return x * y;
}

编译器的决策: 虽然可以将函数声明为 inline,但最终是否内联是由编译器决定的。比如在以下情况下编译器会忽略内联建议:

  • 函数太复杂或体积过大。
  • 递归函数。
  • 函数包含了switchfor循环等复杂逻辑。
inline int ComplexFunc(int x) {
    if (x == 0) return 0;
    int result = 1;
    for (int i = 1; i <= x; i++) {
        result *= i;
    }
    return result;
}
// 这个函数较复杂,可能不会被内联。

三、容易犯的错误

虽然内联函数看似简单,但在实际使用中,存在一些常见的错误。

错误1:内联函数体太大

如果内联函数的逻辑复杂或体积较大,编译器可能会拒绝内联,从而使其成为普通函数调用。大部分编译器对于内联函数的体积有隐式的限制。

inline void LargeFunction() {
    for (int i = 0; i < 100000; i++) {
        cout << i << endl;
    }
}
// 这个函数过于庞大,编译器可能不会内联。

错误2:递归函数内联

递归函数不适合内联,因为内联意味着将函数体直接替换到调用点,而递归意味着函数会调用自身,导致无限的展开。

inline int Recursive(int n) {
    if (n <= 1) return 1;
    return n * Recursive(n - 1);
}
// 递归函数无法内联,因为函数体会无限展开。

错误3:条件编译下的调试问题

在Debug模式下,编译器一般不会对函数进行内联,因为内联后函数调试变得复杂。为了方便调试,编译器通常在Release模式下才会进行内联优化。

但是优秀的编译器vs提供了debug下的查看方法:

如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。

查看方式:

  1. 在release模式下,查看编译器生成的汇编代码中是否存在call Add
  2. 在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不会对代码进行优化,版本不同,对应的设置操作也不同)

四、内联函数的特性

  1. 空间换时间
    使用内联函数的主要目的是通过减少函数调用的开销,来提高程序的运行效率。编译器在编译阶段将函数体替换到函数调用处,避免了常规函数调用时的栈帧创建与销毁等操作。

    • 优点: 减少了函数调用的开销,程序的执行效率可能有所提高。
    • 缺点: 如果函数体积较大,会导致生成的目标文件变大,进而影响性能。
  2. 编译器只将inline当作建议
    关键的一点是,inline 并不是强制要求,编译器可以选择忽略这个建议。这意味着即使在代码中声明了某个函数为内联函数,编译器也可以根据实际情况决定是否将其内联。一般来说,对于较短小且频繁调用的函数,编译器才更倾向于内联处理。

    • 编译器通常不会对较长、包含递归调用或者复杂逻辑的函数进行内联优化。
  3. 内联函数和定义分离可能导致问题
    如果内联函数的声明和定义被分离到不同的文件中,可能会出现链接错误。这是因为内联函数没有函数地址,编译器无法找到相应的定义,从而导致链接器找不到函数实现。通常内联函数应该定义在头文件中,以便在不同的编译单元中直接替换。

实践建议

  • 控制函数规模: 尽量将内联函数的规模保持在合理的范围内,过大的函数会让编译器难以进行内联优化。
  • 不要滥用: 内联并不是适合所有函数,尤其是涉及复杂逻辑的函数,内联反而可能引起更多的问题。
  • 频繁调用的函数优先考虑: 如果某个函数被频繁调用且较为简单,考虑将其声明为内联函数,以获得性能提升。

五、与宏定义的区别

许多人在初学时会混淆内联函数和宏定义。两者有相似之处,但有几个重要的区别:

  • 宏定义:宏定义在预处理阶段展开,没有类型检查,容易出现隐患。例如:

#define Add(x, y) ((x) + (y))

int main() {
    int a = 5, b = 10;
    cout << Add(a, b) << endl;  // 输出15
    cout << Add(a++, b++) << endl;  // 这里有副作用,结果可能不是预期的
}
  • 内联函数:内联函数与普通函数一样,具有类型安全、可以调试。使用时更加可靠和灵活。
inline int Add(int x, int y) {
    return x + y;
}

int main() {
    int a = 5, b = 10;
    cout << Add(a, b) << endl;  // 输出15
    cout << Add(a++, b++) << endl;  // 正常处理,避免了宏的副作用
}

六、代码示例总结

下面是使用内联函数的正确示例,以及常见的错误对比:

  • 正确使用内联函数

inline int Add(int x, int y) {
    return x + y;
}

int main() {
    for (int i = 0; i < 10000; i++) {
        cout << Add(i, i + 1) << endl;  // 频繁调用的短小函数,适合内联
    }
    return 0;
}
  • 递归函数错误示例
inline int Factorial(int n) {
    if (n <= 1) return 1;
    return n * Factorial(n - 1);  // 递归函数不适合内联
}
  • 宏定义与内联函数的对比
#define Multiply(x, y) (x * y)

inline int MultiplyInline(int x, int y) {
    return x * y;
}

int main() {
    int a = 5, b = 10;
    cout << Multiply(a++, b++) << endl;   // 宏定义有副作用
    cout << MultiplyInline(a++, b++) << endl;  // 内联函数避免了这种副作用
    return 0;
}

总结 

  • 内联函数适合短小且频繁调用的函数,避免了宏定义的副作用,具有类型检查和调试功能。
  • 编译器有最终决策权,不一定会根据 inline 关键字做内联优化,特别是在函数较大或较复杂时。
  • 避免对递归函数和大型函数使用 inline

共勉

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

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

相关文章

2024华为杯研赛数学建模E题分析

2024华为杯数学建模E题分析如下&#xff0c;完整版本可查看最下方名片

基于SSM+Vue+MySQL的家教服务管理系统

系统展示 用户前台界面 管理员后台界面 系统背景 随着现代社会对教育质量要求的不断提升&#xff0c;家教行业迎来了前所未有的发展机遇。然而&#xff0c;家教市场也面临着信息不对称、管理不规范、匹配效率低等挑战。为了解决这些问题&#xff0c;提高家教服务的质量和效率&a…

【Python】Anaconda插件:Sublime Text中的Python开发利器

上班的时候没人问我苦不苦&#xff0c;下班的时候总有人问为什么走这么早。 Anaconda 是一个专为Sublime Text打造的开源Python开发插件&#xff0c;旨在为开发者提供类似于IDE的丰富功能&#xff0c;提升Python编码效率。该插件提供了代码补全、语法检查、代码片段提示等多项…

U9多组织单据关连生单时的错误提示

开立采购退货单时&#xff0c;有以下的错误提示。从这段文字来看。生成【采购退货单】同时生成关联公司的【退回处理单】&#xff0c;检查退回处理单的单据类型是正常的。不明所以。系统商出来的错误提示一般是用来迷惑人的&#xff0c;不可尽信。 【未找到满足条件【上游推式…

工程师 - Windows下使用WSL本地安装Linux

Setting Up to Use Windows Subsystem For Linux (WSLv2) 1&#xff0c;WinR&#xff0c;运行ver命令&#xff1a; 我的是Win11系统&#xff0c;但版本还是10.xx的。要求 Windows 10 builds > 18917&#xff0c;才能使用WSLv2。 如果需要版本升级&#xff0c;请参照&#xf…

C++速通LeetCode中等第11题-除自身以外数组的乘积

方法一&#xff1a;前缀积乘后缀积 class Solution { public:vector<int> productExceptSelf(vector<int>& nums) {int length nums.size();vector<int> answer(length);// answer[i] 表示索引 i 左侧所有元素的乘积// 因为索引为 0 的元素左侧没有元素…

多语言长文本 AI 关键字提取 API 数据接口

多语言长文本 AI 关键字提取 API 数据接口 AI / 文本 专有模型极速提取 多语言长文本 / 实时语料库。 1. 产品功能 支持长文本关键词提取&#xff1b;多语言关键词识别&#xff1b;基于 AI 模型&#xff0c;提取精准关键词&#xff1b;全接口支持 HTTPS&#xff08;TLS v1.0 …

C一语言—动态内存管理

目录 一、为什么要有动态内存管理 二、malloc和free &#xff08;2.1&#xff09;malloc &#xff08;2.2&#xff09;free 三、calloc和realloc &#xff08;3.1&#xff09;calloc &#xff08;3.2&#xff09;realloc 四、常见的动态内存的错误&#xff08;举例均为错…

Java设计模式(工厂模式)——抽象工厂模式(完整详解,附有代码+案例)

文章目录 5.4 抽象工厂模式5.4.1 概述5.4.2 结构5.4.3 实现5.4.4 优缺点5.4.5 使用场景 5.4 抽象工厂模式 5.4.1 概述 是一种为访问类提供一个创建一组相关或相互依赖对象的接口&#xff0c;且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。 同族的…

【保奖思路】2024年华为杯研赛F题保奖思路分享(后续会更新)

您的点赞收藏是我继续更新的最大动力&#xff01; 一定要点击如下的卡片&#xff0c;那是获取资料的入口&#xff01; 点击链接加入【2024华为杯研赛资料汇总】&#xff1a;https://qm.qq.com/q/TPBRkrVoQyhttps://qm.qq.com/q/TPBRkrVoQy F题X射线脉冲星光子到达时间建模 问…

STM32 通过 SPI 驱动 W25Q128

目录 一、STM32 SPI 框图1、通讯引脚2、时钟控制3、数据控制逻辑4、整体控制逻辑5、主模式收发流程及事件说明如下&#xff1a; 二、程序编写1、SPI 初始化2、W25Q128 驱动代码2.1 读写厂商 ID 和设备 ID2.2 读数据2.3 写使能/写禁止2.4 读/写状态寄存器2.5 擦除扇区2.6 擦除整…

【论文笔记】BEVNeXt: Reviving Dense BEV Frameworks for 3D Object Detection

原文链接&#xff1a;https://arxiv.org/pdf/2312.01696 简介&#xff1a;最近&#xff0c;在摄像头3D目标检测任务中&#xff0c;基于查询的Transformer解码器正在超越传统密集BEV方法。但密集BEV框架有着更好的深度估计和目标定位能力&#xff0c;能全面精确地描绘3D场景。本…

AI自动直播app盘点:2024超级实用十款应用平台,终结假AI时代!

AI自动直播app盘点&#xff1a;2024超级实用十款应用平台&#xff0c;终结假AI时代&#xff01; 在2024年的科技浪潮中&#xff0c;AI自动直播技术迎来了前所未有的飞跃&#xff0c;终结了虚假AI的阴霾&#xff0c;为直播行业注入了全新的活力与可能。本文将为您盘点十款超级实…

el-table的树形结构结合多选框使用,实现单选父子联动,全选,反选功能

<template><div><el-table:data"tableData":row-key"rowKey":default-expand-all"defaultExpandAll":tree-props"treeProps"><!-- 开启树形多选 --><el-table-column v-if"showSelection" width…

无人机+自组网:中继通信增强技术详解

无人机与自组网技术的结合&#xff0c;特别是通过中继通信增强技术&#xff0c;为无人机在复杂环境中的通信提供了稳定、高效、可靠的解决方案。以下是对该技术的详细解析&#xff1a; 一、无人机自组网技术概述 无人机自组网技术是一种利用无人机作为节点&#xff0c;通过无…

【可测试性实践】C++单元测试:gtest gmock

引言 google test是目前C主流的单元测试框架&#xff0c;本文介绍如何在工程引入gtest和gmock&#xff0c;并提供入门参考示例。根据黄金圈思维我们先思考Why&#xff08;为什么做&#xff09;&#xff0c;为什么我们要进行单元测试&#xff0c;为什么要引入mock手段来测试代码…

Linux:路径末尾加/和不加/的区别

相关阅读 Linuxhttps://blog.csdn.net/weixin_45791458/category_12234591.html?spm1001.2014.3001.5482 普通文件操作 首先说明这个问题只会出现在目录和符号链接中&#xff0c;因为如果想要索引普通文件但却在路径末尾加/则会出现错误&#xff0c;如例1所示。 # 例1 zhang…

Django SQL注入-漏洞分析

1.进入项目界面 图1 项目主界面 2.访问任意不存在的目录路径报错&#xff0c;提示存在demo接口 图2 提示存在接口 3.访问/demo/&#xff0c;提示有一个name参数 图3 发现隐藏参数 4.对接口参数进行fuzz&#xff08;实战思路&#xff09;&#xff0c;vulfocus已经给出了/demo?…

Innodb存储架构

Innodb整体存储架构 Innodb是一款兼顾性能及可靠性的存储引擎&#xff0c;主要分为内存存储结构和磁盘存储结构&#xff0c;二者分别扮演着提高性能和数据持久化的工作 内存结构中定义了缓冲池、变更缓冲区、日志缓冲区、自适应哈希四个缓冲区&#xff0c;它们均是为提升查询…

docker技术(上)

一、docker简介 Docker 是一个开源的应用容器引擎&#xff0c;于 2013 年由 Solomon Hykes 推出并开源。它基于 Go 语言开发&#xff0c;遵从 Apache2.0 协议。Docker 可以让开发者将应用及其依赖包打包到一个可移植的容器中&#xff0c;然后发布到任何流行的 Linux 或 Windows…