Loading

C++ 基础系列——内联函数

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 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. 总结

  • 内联函数可以避免函数调用开销。
  • inline 需在函数定义指出,编译器会忽略声明处的内联关键字 inline。
  • 内联函数不应该有声明,应该将函数定义放在本应该出现函数声明的地方
  • 内联函数在编译时会将函数调用处用函数体替换,编译完成后函数就不存在了
  • 如果将内联函数的声明放在头文件中,定义放在源文件中,这时会出错,能正常编译,但无法链接。
posted @ 2021-08-21 16:00  锦瑟,无端  阅读(370)  评论(0编辑  收藏  举报