前言:我们在
C中学习宏的时候,就体会到了宏在编写的时候需要注意非常多的细 节,否则会导致出错,而C++在设计的时候便为解决C语言在一些情况下不方便而提出了inline(内联函数)这一新特性
1 ADD宏函数
ADD函数宏的写法有以下四种:
- 1
#define ADD(int a, int b) return a + b;- 2
#define ADD(a, b) a + b;- 3
#define ADD(a, b) (a + b)- 4
#define ADD(a, b) ((a) + (b))
你觉得上面哪种是对的?我们一个一个的进行分析
1.1 #define ADD(int a, int b) return a + b;
我们首先要说明的是宏函数本质就是一种替换而不是函数,这样写在语法的层面 上就是错误的

1.2 #define ADD(a, b) a + b;

所以我们一般定义宏函数的时候不在后面加;
并且注意在这个例子里面就算不加;也是一个错误的写法

按理来说我们希望得到的是(1+2)*(2+3)=15,那为什么结果是8呢?
还是要抓住宏函数就是一个替换的规则 ,ADD(1,2)*ADD(2,3)被替换为1+2\*2+3=8,我们可以看出我们没有保证每一个ADD宏替换后的独立。
1.3 #define ADD(a, b) (a + b)

所以这个写法也是错误的,假如a和b其中是一个表达式的话,这个宏函数的替换就会违背我们预想的逻辑。
1.4 #define ADD(a, b) ((a) + (b))
这样的写不仅保证了ADD宏函数的独立性而且保证了ADD宏函数参数a和b的独立性,是一个正确的宏函数。
小结:我们可以看出写对一个宏函数要注意的细节还是很多的,一不留神就容易出现问题。但是宏函数能够在预处理阶段替换,不用建立函数栈帧,可以提效(提高效率),而我们
C++的inline就可以继承这些优点并且屏蔽实现细节。
在回顾宏后,接下来我们正式进入inline函数的学习
2 inline(内联函数)
2.1 liline的基本概念和特征
- 用
inline修饰的函数就是内联函数 - 在编译阶段,编译器会将内联函数展开(类似于宏替换),这样函数就不会创建栈帧,从而提高效率
inline修饰的内联函数是否展开取决于编译器,inline修饰函数这是给编译器一个建议,并且不同编译器对内联函数展开情况各不相同,因为这是C++标准未定义的。
关于内联函数展开的说明:内联函数的展开就是在编译阶段,编译器会在这个位置直接插入函数代码(并不是原封不动的插入 ,要根据上下文逻辑进行必要的转换和适配 ,以保证代码语义的正确性)
宏的实现需要我们自己去实现,而
liline内联函数编译器帮助我们处理 ,但编译器又不是特别智能,它只能处理一些短小函数 ,对于代码较多的,递归函数即使加上inline也会被编译器忽略。
vs编译器下debug版本默认不展开inline,我们想要实现liline需要设置
2.2 vs设置inline选项


设置好后我们就可以通过汇编看内联函数 和非内联函数 的区别了
普通add函数:

内联add函数:

我们可以明显看出在内联函数中inline没有创建函数栈帧,并且都被展开在main函数的cout << add(x, y) << endl;的这里,我们可以看到内联函数的这一块汇编代码 明显比普通函数多。
2.3 inline声明和定义不能分离
inline声明和定义分离会产生链接错误,inline被展开其就没有函数地址,会在链接的时候出错
我们要理解这个,必须先简单的回顾下编译和链接的过程

在F.h中有f1和f2函数的声明,而在F.cpp中有这两个函数的实现,那我们要在Test.cpp文件中使用这两个函数涉及到什么过程呢?
- 预处理:首先预处理将头文件
F.h内容添加到两个cpp文件中。(预处理阶段还做了其它事) - 编译:检查语法生成汇编代码,这时候编译器已经将
F.cpp中的f1和f2函数编译并有其函数地址并记录在符号表中,而由于Test.cpp函数中只有两个函数的声明,所以使用这两个函数的位置编译的时候两个函数的地址并未填入(等待编译的时候补充) - 汇编:将汇编代码转化为二进制机器指令
- 链接:将两个
cpp的目标文件合并为.exe的可执行文件,这个时候Test.o(linux下)目标文件便去F.o符号表中找f1和f2函数地址,以填补在汇编指令中call指定函数地址空缺。
而被
inline修饰的内联函数具有内部链接属性,它不会出现在符号表中,如果f1或者f2是内联函数那么Test.o就无法从符号表中获取函数地址从而找到函数完成调用,从而导致链接错误。
与inline的内联函数一样,static修饰的函数也具有内部链接属性(函数只能在该文件使用,其它文件找不到)

extern可以声明函数的存在,从而让编译器不报错,并在后面的链接阶段从其它文件的符号表中去寻找函数地址,我们可以看到函数被正确使用。
被static修饰后:

发生了链接错误,
static将函数有外部链接属性变成内部链接属性。
总结:符号表就是各个文件调用其它文件的说明书,而inline和static会将函数从这个说明书(符号表)去除,从而让文件无法使用其它文件的函数(就是不知道这个函数的地址)。
那内联函数这么好,我们是不是凡是个函数都给前面加
inline变成内联函数呢?
答案当然是不可以,我们可以通过一个场景来看

inline的func函数被频繁的调用的话,每个位置都被展开,那么整个程序的代码量便会大大增加,会导致程序运行变慢。
所以我们一般将调用频繁且短小函数设置为内联函数。
