一、为什么要学习内联函数
我们在回答这个问题之前先自己独立写一个简单的宏函数吧,写一个让两个数相加的宏Add()
写完之后我们来对一下答案
正确的写法是:
#define Add(x,y) ((x)+(y))
别小看这么简单的一个问题,有许多人都写不出一个真正可用的宏,就算写出来了也不能详细的回答出:“为什么要加第一个第二个括号?”
- 第一个括号是为了防止因为操作符优先级造成bug
例如:
Add(1,2)*3;
在下面这种代码下就会产生bug
#define Add(x,y) x + y
在下面这种两种代码下就不会产生bug
#define Add(x,y) (x + y)
#define Add(x,y) ((x)+(y))
- 第二个括号是为了防止因为参数是表达式造成bug
例如:
Add(a|b,a&b);// a|b+a&b //加号的优先级比位运算符高
在下面这种代码下就会产生bug
#define Add(x,y) (x + y)
在下面这种代码下就不会产生bug
#define Add(x,y) ((x)+(y))
所以宏其实是很难写好的,而且也不利于调试,但是宏的优点又是运行时很快,不用建立栈帧节省空间与时间,我们想要继承宏的优点,同时改进宏的缺点那么我们应该怎么办呢?没错,C++里面的内联函数就是用来解决这样的问题而出现的。
二、内联函数的使用
内联函数的使用非常简单,在函数的声明或定义、函数的返回类型前加上关键字inline
,就可以把函数指定为内联函数从而提升程序运行的效率。
例如:
#include<iostream> using namespace std; inline int Add(int x, int y) { int z = x + y; return z; } int main() { int ret = Add(1,2); cout << ret << endl; return 0; }
我们来看一下汇编代码,我们发现在我们调用Add函数时并没有出现call指令(call指令是函数调用指令),这说明我们使用Add函数时并没有建立栈帧,说明inline
确实可以向宏一样,不建立栈帧从而提升函数运行的效率。
注意:因为debug模式下,编译器默认不会对代码进行优化,内联函数不会起作用,你可能想看到debug模式下内联函数起作用后的的汇编代码,以下给出vs2022的设置方式)
点击项目右键
点击属性
设置为程序数据库(/Zi)
点击优化选择只适应于_inline(/Ob1)
三、内联函数的特性
inline
是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用。
缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。- inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用
inline
修饰,否则编译器会忽略inline
特性。下图为《C++prime》第五版关于inline的建议:
一般来说,内联机制用于优化规模较小、流程直接、频繁调用的函数。很多编译器都不支持内联递归函数,而且一个太多行的函数也不大可能在调用点内联地展开.
- inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
// test.h #include <iostream> using namespace std; inline void f(int i); // test.cpp #include "test.h" void f(int i) { cout << i << endl; } // main.cpp #include "test.h" int main() { f(10); return 0; } //报错误 C3861 “f”: 找不到标识符
内联函数是在编译阶段展开的,不像函数那样会形成符号表,在链接阶段进行链接。
正确写法
// test.h #include <iostream> using namespace std; inline void f(int i) { cout << i << endl; } // main.cpp #include "test.h" int main() { f(10); return 0; }