欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++内联函数inline

程序员文章站 2024-03-20 23:10:52
...

内联函数

定义与使用

常规函数的调用过程

  编译过程的最终产品是可执行程序–由一组机器语言指令组成。运行程序时,操作系统将这些指令载入到计算机内存中,因此每一条指令都有特定的内存地址。有时,比如说遇到循环或者判断语句,会跳过一些指令,向前或向后跳到特定的地址。常规函数调用也使得程序跳转到下一个地址–函数的地址,并在函数结束时返回。

  更具体的过程是执行到函数调用的指令的时候,程序会立即把这个函数调用的指令的地址存储起来,将函数的参数存到堆栈中,然后跳转到标记函数起点的内存单元,逐步执行函数体,如果函数有返回值的话,还会将返回值放到寄存器里。随后调回到之前存储的指令地址,继续执行下一个指令。这就好比是正在阅读一本书,阅读的过程中突然遇到了脚注,然后标记一下,转而去阅读脚注,阅读完之后再回到之前标记的地方继续阅读。

  对于比较大型的程序,函数的调用往往存在千百万甚至上亿次,这每一次的跳转都需要造成一些系统的开销,最终影响整个系统的性能。当然,对于平常个人工程实践里的小型代码来说,由于现在PC的性能普遍还可以,所以这些开销基本上可以忽略不计。当然,对于C语言来说,一些经常用到的函数,往往采用宏定义进行预编译来减少这种跳转的开销。比如编写一个程序做一些数学运算,往往类似乘方,开根号这些函数被经常用到,在程序最开头加上“#define SQUARE1(x) xx”,这样程序在遇到SQUARE(x)的时候,就会自动转换成xx,然而这样做本质上只是一种文本替换,从下面的分析中你能看到它所面临的困境,而使用内联函数却可以避免这些问题。

内联函数的使用方法

  C++内联函数提供了另外一种选择,顾名思义,内联函数的编译代码与其它程序代码“内联”起来了,编译器将使用相应的函数代码替换函数调用。因此函数在执行到内联函数时,程序无需再跳到另外一个位置处执行代码,执行完再调回来,很明显其运行速度会比常规函数要快一些。但是这么做的代价,就是程序会占用更多的内存。如果程序在10个不同的地方调用了同一个内联函数,那么这个内联函数就要创造10个副本。

  所以,我们在看待内联函数的时候,应该看到其两面性,根据实际的应用场景去有选择地使用内联函数。这就好比是,如果你担心整型溢出,你可以把所有的整型变量都声明为long long,这样固然是很难溢出了,但是也会带来很多的内存消耗。因此,如果执行函数代码的时间比处理函数调用机制的时间长,则节省的时间将只占整个过程的一小部分。如果代码执行的时间很短,则内敛调用就可以节省非内联调用使用的大部分时间。

  刚才只是从宏观上讲了为什么要有内联函数,以及它的利弊。现在来看看如何使用。要使用这项特性,必须采取下述措施之一:

  • 在函数声明前加上关键字inline;
  • 在函数定义前加上关键字inline。

  通常的做法是直接省略原型,将整个定义(即函数头和所有函数代码)放在本应提供原型的地方。
并不是所有的编译器都支持内联函数,有些编译器就没有启用或者实现这种特性。此外,有时候编译器会拒绝程序员将某些函数作为内联函数,它可能认为该函数过大或注意到函数调用了自己,因为内联函数不允许递归。

  下面的代码展示了使用内联函数的一个demo。

//Date: 2020/03/08
//Author: Pengqi
//Reference: C++ Primer Plus P254-255

#include<iostream>

using std::cout;
using std::endl;

inline double square(double x) { return x * x;}

int main() {
    double a, b;
    double c = 13.0;

    a = square(5.0);
    b = square(4.5 + 7.5);
    cout << "a = " << a << ", b = " << b <<endl;
    cout << "c = " << c;
    cout << ", c square = " << square(c++) << endl;
    cout << "Now c = " << c <<endl;

    return 0;
}

  程序的运行结果是

./inline_test_1
a = 25, b = 144
c = 13, c square = 169
Now c = 14

  输出表明,内联函数和常规函数一样,也是按值传递的。
尽管程序没有提供独立的原型,但C++原型特性仍起作用。这是因为在函数首次使用前出现的整个函数定义充当了原型。这意味着可以给square()传递int或long值,将值传递给函数前,程序自动将这个值强制转换为double类型。

内联函数与C宏

  inline工具是C++新增的一个特性,C语言使用预处理器#define来提供宏–内联代码的原始实现。下面一段代码将可以很好地展示二者的区别。

//Date: 2020/03/07
//Author: Pengqi

#include<iostream>

using std::cout;
using std::endl;

#define SQUARE1(x) x*x
#define SQUARE2(x) ((x)*(x))

inline double square(double x) { return x*x; }

int main() {
    int a =3;
    cout << SQUARE1(5.0) << endl;  // 5.0*5.0
    cout << SQUARE1(4.5 + 7.5) << endl;  //4.5 + 7.5*4.5 + 7.5
    cout << SQUARE1(a++) << endl; // c++*c++

    cout << SQUARE2(5.0) << endl;
    cout << SQUARE2(4.5 + 7.5) << endl;
    cout << SQUARE2(a++) << endl;

    cout << square(5.0) << endl;
    cout << square(4.5 + 7.5) << endl;
    cout << square(a++) << endl;
}

  编译

$ g++ inline_test.cpp -o inline_test
inline_test.cpp:15:22: warning: multiple unsequenced modifications to 'a'
      [-Wunsequenced]
    cout << SQUARE1(a++) << endl; // c++*c++
                     ^~
inline_test.cpp:6:20: note: expanded from macro 'SQUARE1'
#define SQUARE1(x) x*x
                   ^ ~
inline_test.cpp:19:22: warning: multiple unsequenced modifications to 'a'
      [-Wunsequenced]
    cout << SQUARE2(a++) << endl;
                     ^~
inline_test.cpp:7:22: note: expanded from macro 'SQUARE2'
#define SQUARE2(x) ((x)*(x))
                     ^   ~
2 warnings generated.

  运行结果:

./inline_test
25
45.75
12
25
144
30
25
144
49

  从上面的运行结果可以看出,SQUARE1()无法处理(4.5 + 7.5)和(c++)这两种情况,因为宏定义仅仅只是简单的文本替换。
SQUARE2()虽然在(4.5+7.5)能得到正确的结果, 但是其本身接收参数的方式并不是值传递,以至于传入c++的时候,仍然将c递增了两次。而对于内联函数square(),则是先传递c进入到函数中,再递增。

相关标签: C++学习笔记