【C++ inline】保姆级内联函数介绍、优缺点、使用场景

一、定义:

        如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline。

        内联函数是C++的增强特性之一,用来降低程序的运行时间。当内联函数收到编译器的指示时,即可发生内联:把内联函数的函数体在编译器预处理的时候替换到函数调用处(加副本),这样代码运行到这里时候就不需要花时间去调用函数(减少了函数调用过程的入栈出栈等开销),注意这种替代行为发生在编译阶段而非程序运行阶段,且对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。

        值得注意的是,内联函数仅仅是对编译器的内联建议,编译器是否觉得采取你的建议取决于函数是否符合内联的有利条件。如果函数体非常大,那么编译器将忽略函数的内联声明,而将内联函数作为普通函数处理。

二、例子:

        有时候我们会写一些功能专一的函数,这些函数的函数体不大,包含了很少的执行语句。例如在计算1~1000以内的素数时,我们经常会使用开方操作使运算范围缩小,这时我们会写一个函数:

int root(int n)
{
  return (int)sqrt((float)n);
}

        然后我们的求范围内素数的函数可以这样写:

int prime(int n)
{
    int i;
    for (i = 2; i <= root(n); i++)
    {
        if (n%i == 0)
      return 0;
        return 1;
    }
}

        当然,把root函数放在循环中不是个不明智的选择,但想象一下,在某个程序上下文内必须频繁地调用某个类似root的函数,其调用函数的花销会有多大:当遇到普通函数的调用指令时,程序会保存当前函数的执行现场,将函数中的局部变量以及函数地址压入堆栈,然后再将即将调用的新函数加载到内存中,这要经历复制参数值、跳转到所调用函数的内存位置、执行函数代码、存储函数返回值等过程,当函数执行完后,再获取之前正在调用的函数的地址,回去继续执行那个函数,运行时间开销简直太多了。

(本例子参考了https://www.cnblogs.com/qg-whz/p/4641479.html)

三、优缺点

1、优点

  1. 通过避免函数调用所带来保存现场、变量弹栈压栈、跳转新函数、存储函数返回值、执行完返回原现场等开销,提高了程序的运行速度
  2. 通过将函数声明为内联,你可以把函数定义放在头文件内。编译器需要把inline函数体替换到函数调用处,所以编译器必须要知道inline函数的函数体是啥,所以要将inline函数的函数定义和函数声明一起写在头文件中,便与编译器查找替换。

2、缺点

  1. 因为代码的替换扩展,内联函数会增大可执行程序的体积,进而导致程序变得更慢
  2. C++内联函数的展开是中编译阶段,这就意味着如果你的内联函数发生了改动,那么就需要重新编译代码
  3. 当你把内联函数放在头文件中时,它将会使你的头文件信息变多,不过头文件的使用者不用在意这些

四、什么时候可以使用内联函数

  1. 对程序执行性能有要求且函数不超过10行
  2. 想宏定义一个函数(待补充)
  3. 在类内部定义的函数会默认声明为inline函数,这有利于类实现细节的隐藏

五、什么函数不适合内联

  1. 函数体过于庞大,超过10行的函数
  2. 内联那些包含循环或 switch 语句的函数常常是得不偿失 (除非在大多数情况下, 这些循环或 switch 语句从不被执行):因为如果内联函数本身就很复杂,那么将导致调用该内联函数的函数更为复杂,内存处理上更麻烦,可能会让程序整体的效率更低下
  3. 构造函数和析构函数也不适合内联:构造函数不适合的原因是即使是看似琐碎或空的构造函数通常也可能包含大量由编译器隐式生成的代码,而实际的构造函数可能会非常大,这可能会导致代码膨胀。析构函数不适合的原因是因为它可能是虚函数
  4. 虚函数(会不会内联要分情况讨论):可能可以内联:当虚函数被调用时它的入口地址是在编译阶段静态确定的,那么就可能会被内联,例子C++ 虚调用及其调用的具体形式_c++调用一个虚函数是如何调用到实函数的_恋喵大鲤鱼的博客-CSDN博客不会内联:当虚函数使用父类的指针或者引用动态的调用子类的虚函数功能时,由于inline是在编译器将函数类容替换到函数调用处,是静态编译的。而此时虚函数是动态调用的,在编译器并不知道需要调用的是父类还是子类的虚函数,所以不能够inline声明展开,所以编译器会忽略
  5. 通常递归函数不应该声明成内联函数,大多数编译器都不支持内联递归函数:因为递归调用堆栈的展开并不像循环那么简单, 比如递归层数在编译时可能是未知的

你可能感兴趣的:(c++,开发语言)