🔷 第六章 函数
📄 一句话概括

内联函数 = 建议编译器把函数体直接铺到调用点,省掉"跳进去、跳回来"的开销。写法就是在定义前加关键字 inline。代价是代码变胖(每处调用都复制一份),所以它只适合短小、且调用频繁的函数。

📝 本节和前面几节的关系

6.3 讲调用时提过一句"调用是有代价的"(记返回地址、复制实参、建栈帧),这一节就是针对那份代价的优化。1.1.3 说 C++ 的优点是"高效",内联就是其中一个小零件——它是第一次把"效率"摆上台面的知识点。

一、写法与效果

inline int add(int a, int b) {        // 加一个 inline
    return a + b;
}

概念上,编译器的处理是:

// 你写的
int s = add(3, 5);

// 编译器大致变成
int s = 3 + 5;                        // 函数体被"摊"到了这里

"内联"这个名字的意思就是"就地铺开"——像把信封里的内容直接抄到信纸上,而不是每次去抽屉里翻。

二、为什么要内联(省的是什么)

回看 6.3 的调用五步:记位置 → 复制实参 → 跳进去 → 交结果 → 跳回来。如果函数体只有一行 return a + b;,那么这些"跳来跳去的杂事"可能比计算本身还费时间。内联把这一层全免掉:

普通函数调用 内联函数
跳转 有 无(代码直接铺开)
栈帧 要建要拆 不要
代码体积 一份 每处调用各一份(变大)
可调试性 好 差(调用点看不出是函数)

一句话取舍:用空间换时间。

三、inline 是"建议",不是"命令"

这一条最关键:inline 只是一个请求,编译器有权拒绝。

编译器通常拒绝内联的情况: - 函数体太大(铺开会让代码膨胀得离谱) - 递归函数(自己都停不下来,怎么铺) - 复杂控制流、取函数地址等操作

反过来:编译器也可能自动内联一些很短的函数(即使你没写 inline)——现代编译器在这件事上比人算得准。

实用结论:写不写 inline 影响不大,真正的决定权在编译器手里。学它是为了知道"有这么个机制、知道它有体积代价"。

四、动手 Demo:体会"内联不改变结果"

#include <iostream>
using namespace std;

inline int add(int a, int b) {           // 建议内联
    return a + b;
}

inline int max2(int a, int b) {          // 另一个适合内联的例子:短小
    return (a > b) ? a : b;
}

int main() {
    int s = 0;
    for (int i = 1; i <= 10; i++) {
        s = add(s, i);                   // 高频调用的小函数
    }
    cout << "累加结果 = " << s << endl;
    cout << "较大值 = " << max2(3, 9) << endl;
    return 0;
}

看到什么算成功:

累加结果 = 55
较大值 = 9

功能上和去掉 inline 没有任何区别——这正是要记住的重点:内联不改变结果,只影响效率。它是"引擎盖下的优化",你从输出上看不出来。

30 秒实验: 1. 把两处 inline 都删掉,重新编译运行——输出一模一样(功能不受影响)。 2. 想亲眼看"是否内联",可在 Dev-C++ 的编译器选项里加优化参数(如 -O2)再对比程序大小。普通学习阶段不必深究——记住"短小频繁用内联"这条经验规则就够了。

五、什么该内联、什么不该

适合内联 不适合内联
一行返回值的简单函数(add、max2) 函数体很长(几十行)
取/设值的短函数(学"类"时会大量遇到) 递归函数
被高频调用的热点函数 只调用一两次的函数(铺开纯属浪费)

一句话:内联是给"小而热"的函数准备的。

六、常见误区

  1. "写了 inline 就一定会内联"——不一定,只是建议,编译器可能拒绝。
  2. "不写 inline 就一定不内联"——也可能被编译器自动内联。
  3. "内联能加快所有函数"——长函数内联只会让体积暴涨、缓存命中率下降,可能更慢。
  4. "内联函数不能有返回值"——无关。inline 和有没有返回值是两件独立的事。
  5. "inline 函数随便放哪都行"——在多文件工程里,inline 函数的定义通常要放在头文件里,否则链接阶段会出错。本章只写单文件,先记个印象,等学到类和多文件时再兑现。
自测三问(点开对答案) 1. 内联函数省了什么?——函数调用的开销(跳转、建栈帧、复制实参等)。 2. 内联的代价是什么?——代码体积变大(每个调用点都铺一份)。 3. 递归函数能内联吗?——通常不能,编译器会拒绝。

相关笔记