一、函数调用开销
函数调用会涉及:
参数压栈(或寄存器传参)
跳转到函数体
返回值处理
栈帧销毁
这个过程对小函数来说可能非常浪费,因此,宏函数和内联函数的目的就是避免“函数调用的开销”,通过代码展开(替换)来实现“零调用成本”。
二、C语言的宏函数
1、 定义方式
宏函数使用 #define 宏定义语法:
#define SQUARE(x) ((x) * (x))
2、实现原理
预处理器阶段完成替换
所有使用 SQUARE(x) 的地方,都会被文本替换为 ((x)*(x))(简单地字符串替换)
不是函数调用,也没有类型检查
优点
快!展开是直接替换,没有函数调用的成本
支持各种类型(无类型限制)
缺点
不安全:可能造成副作用
SQUARE(i++) → ((++i) * (++i)) // i 被加了两次!
#define SQUARE(x) ((x)*(x))int main(){int i = 5;cout << "6*6 = " << SQUARE(++i); }
输出:
6*6 = 42 ❌;因为++i加了两次,所以是6*7;
调试困难:宏不是函数,没法打断点进去
没有作用域控制:容易污染命名空间
三、C++ 的内联函数(inline function)
1、定义方式
inline int square(int x) {return x * x; }
只是在常规的函数定义之前加个 “inline”。
2、实现原理
编译器在 编译阶段 判断是否将调用处用函数体替代(不保证一定 inline,只是建议)
真实函数、具有类型检查、作用域、安全。
利用空间换时间的原理 。
优点
安全、类型检查完整
可以用调试器调试
支持递归(编译器可能不展开)
支持模板和泛型
可以与 constexpr、template 配合
缺点
内联函数不建议声明和定义分离,分离会导致连接错误。因为inline被展开了,就没有了函数地址,链接就找不到了。
滥用内联会导致代码膨胀(code bloat),生成的可执行文件变大
只适合小函数,复杂函数不一定 inline,(代码小于二十行,递归)。
编译器有最终决定权,inline 是一种建议而非强制
四、对比总结表
特性 | 宏函数(C) | 内联函数(C++) |
---|---|---|
执行阶段 | 预处理阶段 | 编译阶段 |
类型检查 | ❌ 无类型检查 | ✅ 有完整类型检查 |
安全性 | ❌ 易出错,有副作用 | ✅ 安全,作用域清晰 |
调试性 | ❌ 不可调试 | ✅ 可调试 |
递归支持 | ❌ 不支持 | ✅ 支持(是否内联由编译器决定) |
用法建议 | 仅限非常简单的表达式 | 小函数、频繁调用的函数 |
性能提升 | ✅(暴力替换) | ✅(编译器优化,智能展开) |
五、【面试题】
1、宏的优缺点?
2、c++有哪些技术可以替代宏?
利用常量定义换用const。
#define N 10;
替换为
const int N = 10;
短小函数定义,换用内联函数。
宏函数定义 -->利用内联函数替换
#define MAX(a, b) ((a) > (b) ? (a) : (b))// 改写为模板内联函数 template <typename T> inline T Max(T a, T b) {return a > b ? a : b; }
3、 宏和内联函数的本质区别是什么?
考点: 宏是预处理阶段处理,内联函数是编译阶段处理。
宏在预处理阶段展开,是文本替换,没有类型检查,也没有作用域限制;
内联函数是真正的函数,有类型检查,有作用域,并能调试。
4、 什么是内联函数?什么时候应该使用?
考点: 减少函数调用开销,提升性能。
适合小函数,如 getters/setters;
不建议用于递归、虚函数、复杂函数;
编译器可以不采纳 inline 建议(非强制)。
5、内联函数会影响编译时间和可执行文件大小吗?
考点:
增加编译时间(编译器要展开多次);
增加可执行文件大小(代码膨胀);
但可以减少函数调用栈的开销。
好了本期有关于c语言宏定义知识回顾和c++内联函数的分享就到这里结束了,谢谢大家的支持和点赞收藏👍。