1. 为什么设计内联函数
函数调用是有时间和空间开销的:调用一个函数之前通常要将实参、局部变量、返回地址以及若干寄存器都压入栈中,然后才能执行函数体中的代码,函数体中的代码执行完毕后还要恢复。
为了消除函数调用的时空开销,C++ 提供一种提高效率的方法,即在编译时将函数调用处用函数体替换,这种在函数调用处直接嵌入函数体的函数称为内联函数(Inline Function)。内联函数可以避免函数调用开销。
2. 定义内联函数
指定内联函数的方法很简单,只需要在函数定义处增加 inline 关键字。
void swap(int *, int *); // 函数原型,也可以添加 inline,但编译器会忽略
int main(){
int m = 10;
int n = 11;
swap(&m, &n); //编译器可能会将函数代码直接嵌入此处
}
inline void swap(int *a, int *b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
注意:要在函数定义处添加 inline 关键字
在函数声明处添加 inline 关键字虽然没有错,但这种做法是无效的,编译器会忽略函数声明处的 inline 关键字。
由于内联函数比较短小,我们通常的做法是省略函数原型,将整个函数定义(包括函数头和函数体)放在本应该提供函数原型的地方。
inline void swap(int *a, int *b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
int main(){
int m = 10;
int n = 11;
swap(&m, &n); //编译器可能会将函数代码直接嵌入此处
}
对函数作 inline 声明只是程序员对编译器提出的一个建议,而不是强制性的,并非一经指定为 inline 编译器就必须这样做。编译器有自己的判断能力,它会根据具体情况决定是否这样做。
3. 内联函数代替宏
宏是可以带参数的,它在形式上和函数非常相似。不过不像函数,宏仅仅是字符串替换,不是按值传递,所以在编写宏时要特别注意,一不小心可能就会踩坑。如下所示:
#include <iostream>
using namespace std;
#define SQ(y) y *y
#define SQ2(y) (y)*(y)
#define SQ3(y) ((y)*(y))
int main()
{
int n = 10;
int sq = SQ(n); // 此时宏调用没问题
int sq2 = SQ(n+1); // 结果错的:n + 1 * n + 1,可以改进成 SQ2 的形式
int sq3 = 200 / SQ2(n) // 200 / 10 * 10,结果错的,此时可以改进成 SQ3 的形式
cout << sq << endl;
return 0;
}
宏定义是一项“细思极密”的工作,一不小心就会踩坑,而且不一定在编译和运行时发现,给程序埋下隐患。
可以将宏换位内联函数。
#include <iostream>
using namespace std;
inline int SQ(int y) { return y * y; }
int main()
{
int n, sq;
cin >> n;
sq = SQ(n);
cout << sq << endl;
sq = SQ(n + 1);
cout << sq << endl;
sq = 200 / SQ(n + 1);
cout << sq << endl;
return 0;
}
和宏一样,内联函数可以定义在头文件中,并且头文件被多次#include
后也不会引发重复定义错误。这一点和非内联函数不同,非内联函数是禁止定义在头文件中的,它所在的头文件被多次#include
后会引发重复定义错误。
可以看到内联函数主要有两个作用,一是消除函数调用时的开销,二是取代带参数的宏。
4. 如何规范地使用 C++内联函数
inline 在函数声明处是无效的,编译器会忽略函数声明处的 inline 关键字。
内联函数不应该有声明,应该将函数定义放在本应该出现函数声明的地方,这是一种良好的编程风格。
内联函数在编译时会将函数调用处用函数体替换,编译完成后函数就不存在了。如果将内联函数的声明放在头文件中,定义放在源文件中,这时会出错,能正常编译,但无法链接。