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;}
};为什么不能分开定义?
- 内联的本质:编译器需要在编译时将函数体直接插入调用点
- 编译单元:每个
.cpp文件是独立的编译单元- 内联要求:编译器必须在编译当前文件时能看到函数定义
- 分开定义:当编译
main.cpp时,看不到add的定义,无法内联
总结以及几个小知识
C++中内联函数(inline)是安全、类型安全、可维护地替代宏函数(#define)的最佳方式:
- 保留了宏的效率优势:消除函数调用开销
- 解决了宏的缺陷:类型安全、可调试、避免多次求值
- 符合C++语言特性:类型系统、作用域、OOP思想
在vs中,内联函数在debug模式下默认为普通函数(为了方便调试),但在release下则会正常替换。
内联函数与宏函数的核心区别
| 特性 | 内联函数 | 宏函数 |
|---|---|---|
| 处理阶段 | 编译阶段 | 预处理阶段 |
| 类型检查 | 有,严格类型检查 | 无 |
| 参数求值 | 每个参数只求值一次 | 可能多次求值 |
| 调试支持 | 有,可设置断点 | 无 |
| 作用域 | 有作用域,遵循C++规则 | 全局替换,无作用域 |
| 代码可读性 | 更好,符合函数语义 | 通常较差 |
| 能否访问类成员 | 可以 | 不能(无法处理this指针) |
彩妙的博客可能不全或者出错误,欢迎大家来前来指正错误以及做出相对应的补充(博主也是刚开始学习,有很多不懂的地方,多多包含)。
这里是彩妙,关注我,获取更多优质好文吧!

