当前位置: 首页 > news >正文

C++ :C宏函数的升级:内联函数inline

大家好,这里是彩妙呀~

    今天为大家带来的是C=>C++中比较好用的一个转变:由宏函数到内联函数的转变:linline函数

内联函数的核心概念

    内联函数 : 是C++中一种优化技术,他通过inline关键字声明的函数。

    编译器在编译时会将函数调用处直接替换为函数体的代码,而不是进行常规的函数调用(保存返回地址、参数压栈、跳转到函数体、返回等)

为什么需要内联函数

    学过C语言宏函数(如果忘了,本博客会在下文中简单说一下)说过:我们在调用函数时,需要进行常规的函数调用(保存返回地址、参数压栈、跳转到函数体、返回等)。而函数调用通常情况下都会需要执行入栈参数、保存上下文、跳转执行等操作,会增加额外时间开销(这部分属于函数部分的底层知识,CSAPP中会详细讲解这些知识)。而宏通过预处理直接替换代码,避免了调用开销,但可能导致代码冗余和类型安全隐患。

    所以,为了去解决宏函数所带来的问题,C++引入了inline函数来替代宏函数,即解决了宏函数的缺陷与问题,有会保留宏函数的效率与优势。

C语言中的宏函数

某某大厂面试题:

  • 什么是宏函数?它有什么作用?

  • 在什么场景下应该使用宏函数而不是函数?

  • 宏定义和普通函数的主要区别是什么?

  • 宏函数的缺陷有哪些?

  • 为什么C++引入了内联函数来替代宏函数?

  • 宏函数与内联函数的区别是什么?

所以,综合看来,这部分是HR考察你基本功的好问题。

    看完本博客,你会逐一解决以上问题:

什么是宏函数?它有什么作用?

    宏函数是C/C++中使用#define预处理指令定义的代码片段,通过文本替换的方式在编译前替换到源代码中。

作用:提高代码效率(避免函数调用开销)、简化重复代码、实现条件编译等。

    在项目中,经常把一些短小而又频繁使用的函数写成宏函数(函数体非常短小或者需要频繁调用的函数:如MAX,MIN等,),这是由于宏函数没有普通函数参数压栈、跳转、返回等的开销,可以提高程序的效率。

    宏函数与普通函数的区别(文件在linux环境下,采用gcc来编译):

预处理(进⾏宏替换) : gcc –E hello.c –o hello.i     ==>    选项“-E”,该选项的作⽤是让 gcc 在预处理结束后停⽌编译过程。选项“-o”是指⽬标⽂件,“.i”⽂件为已经过预处理的C原始程序。

  • 宏在预处理阶段展开,函数在运行时调用

    宏的本质是文本替换(在预处理时,所有的#define 都会进行文本替换),而函数则需要函数调用才能执行函数体。

  •     宏无类型检查,函数有类型检查

    基于文本替换原理,例如:#define SQUARE(x) ((x) * (x)),可以用于任何类型

    而函数则需要明确返回值以及传参值:double SQUARE(int x , int x),只能通过传特定类型的值才能使用。

  •     宏可能增加代码体积,函数不会

    前者本质是文本替换,如果多次使用宏函数,那么代码就会在预处理时进行多次替换。从而不可避免造成代码体积的增加。

    后者则需要函数调用,不会使代码体积增加,但是效率却不如前者。

  •     宏参数可能被多次求值,函数参数仅求值一次。

宏函数的缺陷

那么:库函数为何会容易出错呢:

  • 宏函数无法进行调试
  • 宏函数没有类型检查,容易发生类型不匹配错误
  • 有些程序员对宏函数了解不深,容易写错宏函数

前两个是宏函数本身的缺陷,而第三个则是程序员的缺陷:

//各位可以尝试想一下一下库函数在执行后会有什么错误?#define MUL(int x, int y) { return x * y; }#define MUL(x, y) x * y#define MUL(x, y) (x * y)#define MUL(x, y) (x) * (y)#define MUL(x, y) ((x) * (y))

接下来。彩妙会为大家逐一讲解:

这里为了方便让大家观看,使用vs2023:

宏函数本质上不是函数,所以不能写参数类型(int x)

#define MUL(int x, int y) { return x * y; }

    宏函数本质上不是函数,所以不能写参数类型:有些小伙伴在定义宏函数时,错误的认为宏函数就是平常所看到的函数,所以出现了上述代码。

MUL(x , y ): , x y) { return x * y };

    我们发现:展开内容与我们所期望的严重不符,甚至编译器都无法编译过去。所以,按照函数定义来写宏函数是一个非常严重的错误。

宏函数在展开时运算错误问题

    以下宏函数基本上犯的都是这个错误。但是,如果只是调用这个函数(例如:int x = MUL(2 , 3)),则问题不大,但是还是非常不推荐这么写
#define MUL(x, y) x * y

#define MUL(x, y) (x * y)
#define MUL(x, y) (x) * (y)

#define MUL(x, y)    x * y

    当函数传值时,存在算术式传值,则会出现下面情况:

int result = MUL(3 + 2, 4);  // 展开为:3 + 2 * 4 → 3 + 8 = 11(不是期望的 20)
int sum = 12 + 12&MUL(3 + 2 , 4); //展开为:12 + 12 & 3 + 2 * 4; 与预期不符

#define MUL(x, y) (x * y)

同上,但是这么写则规避了第二个情况

#define MUL(x, y) (x) * (y)

同上,但是这么写则规避了第一个情况

所以,综上所述,最合适的情况则是:#define MUL(x, y) ((x) * (y))。

技巧:参数加括号,整体加括号这能最大程度避免:

  • 运算符优先级错误
  • 表达式结合性问题
  • 意外的宏展开行为

!!!但是:

如果传值存在自增或自减,则会有副作用的风险:

MUL(a++, b)          → ((a++) * (b))          = 正确(但仍有副作用风险)⚠️  

    即使写对了,MUL(a++, b) 仍会让 a++ 被计算一次(因为只出现一次),但如果是 MAX(a++, b++) 这类宏,副作用问题依然存在。

C++中,内联函数inline替换宏函数

    C++引入内联函数inline安全地替代宏函数,既保留了宏的效率优势,又解决了宏的缺陷。

//inline函数定义// 声明(通常不写inline)
inline int add(int a, int b);// 定义(必须包含inline关键字)
inline int add(int a, int b) {return a + b;
}

    内联函数解决了宏函数大多出的缺点与问题:

  • 无类型检查:宏在预处理阶段进行文本替换,没有类型安全检查
  • 多次求值问题:参数可能被多次计算(如MAX(++x, 10)
  • 难以调试:预处理后的代码难以调试
  • 无作用域:全局替换,易导致命名冲突

    用inline关键字声明,但编译器可能根据情况决定是否真正内联(如果内联函数是个递归函数或者函数体积过大,则会按照正常的函数进行常规的函数调用)。而在性能提升方面,内联函数采用了空间换时间的策略:采用文本替换的方式来避免函数调用的开销。这样做可以避免代码膨胀:

    左侧是100条指令,如果每次调用都要展开内联函数需要展开),则会有代码膨胀的风险:10000个位置调用,每次调用都要展开(加载进内存)100行,算下来(1w * 100)代码量就非常大;相比之下,如果不展开(即普通调用)则不会出现这种情况。但与之相对应的则是会出现函数调用,这样则会损失一些代码的效率。

    但编译器在优化过程中,会有一条界限来区分这两个函数,以便达到代码效率最高化。

注意:内联函数不可以分开定义

  内联函数必须在所有使用它的编译单元中都能看到定义,所以不能在头文件中声明,在源文件中定义。

// math.h
inline int add(int a, int b); // 声明// math.cpp
#include "math.h"
inline int add(int a, int b) { // 定义return a + b;
}//当其他源文件(如main.cpp)包含math.h时,它们只看到声明,看不到定义。
//编译器会尝试在main.cpp中内联add,但找不到函数体,导致链接错误。//正确做法//在头文件中直接定义// math.h
inline int add(int a, int b) {return a + b;
}//在类内部定义(但是,在类里面的函数默认是内联函数)
// math.h
class MathUtils {
public:int add(int a, int b) {return a + b;}
};

为什么不能分开定义?

  1. 内联的本质:编译器需要在编译时将函数体直接插入调用点
  2. 编译单元:每个.cpp文件是独立的编译单元
  3. 内联要求:编译器必须在编译当前文件时能看到函数定义
  4. 分开定义:当编译main.cpp时,看不到add的定义,无法内联

总结以及几个小知识

C++中内联函数(inline)是安全、类型安全、可维护地替代宏函数(#define)的最佳方式

  1. 保留了宏的效率优势:消除函数调用开销
  2. 解决了宏的缺陷:类型安全、可调试、避免多次求值
  3. 符合C++语言特性:类型系统、作用域、OOP思想

    在vs中,内联函数在debug模式下默认为普通函数(为了方便调试),但在release下则会正常替换。

内联函数与宏函数的核心区别

特性内联函数宏函数
处理阶段编译阶段预处理阶段
类型检查有,严格类型检查
参数求值每个参数只求值一次可能多次求值
调试支持有,可设置断点
作用域有作用域,遵循C++规则全局替换,无作用域
代码可读性更好,符合函数语义通常较差
能否访问类成员可以不能(无法处理this指针)


    彩妙的博客可能不全或者出错误,欢迎大家来前来指正错误以及做出相对应的补充(博主也是刚开始学习,有很多不懂的地方,多多包含)。

    这里是彩妙,关注我,获取更多优质好文吧!

http://www.dtcms.com/a/568639.html

相关文章:

  • 青海网站建设费用织梦后台怎么建设网站
  • [特殊字符] Gudu SQL Omni 在数据治理体系中的落地实践指南
  • arm寄存器虚拟化分析
  • Linux网络传输层TCP协议
  • 做企业网站备案收费吗怎么修改网站标题
  • 机器视觉---Intel RealSense SDK 2.0 开发流程
  • 【AI基础篇】Transformer架构深度解析与前沿应用
  • QuickNacos
  • 用Python来学微积分30-微分方程初步
  • Opencv(七) : 图像颜色替换
  • Skywalking运维之路(Skywalking服务搭建)
  • 网站开发及建设赔偿条款中国最牛的十大企业
  • 广州全运会即将开幕,获得文远知行自动驾驶技术支持
  • 在智能制造语境下理解ISA-95、IIoT和UNS
  • 网站建设 服务器 预算报价清单企业展厅设计公司虎
  • 算法学习入门---前缀和(C++)
  • 一键生成系统架构图
  • 2025国产MOM系统全景透视:谁在领跑智能制造新赛道?
  • 系统架构设计师备考第64天——网络构建关键技术
  • 使用ZYNQ芯片和LVGL框架实现用户高刷新UI设计系列教程(第三十五讲)
  • 网站备案个人可以做吗thinkphp cms开源系统
  • 一般电脑网站建设及运营多少钱中国最新军事新闻报道
  • Elasticsearch+Logstash+Filebeat+Kibana部署[7.17.3版本]
  • Elasticsearch单机部署全指南
  • 前端实战开发(三):Vue+Pinia中三大核心问题解决方案!!!
  • 从零开始:开发一个仓颉三方库的完整实战
  • 本机 MongoDB 注册系统服务、启用security认证
  • Nginx代理配置的“双斜杠陷阱“:从IP到域名的完整排查与解决指南
  • 三水容桂网站制作天眼查企业信息查询平台
  • HarmonyOS鸿蒙开发:Swiper组件实现精美轮播图