内联函数

  影响性能的一个重要因素是内联技巧。内联函数也可称为内嵌函数。

  在C++中,函数调用需要建立栈环境,进行参数的复制,保护调用线程,返回时,还有进行返回值复制,恢复调用现场。这些工作都是与完成特定任务的操作武功的额外开销。程序效率由于改下工作而受到影响,所以,流行的CPU都已经将函数调用的额外工作硬件化了,以此来建减少运行开销。尽管如此,调用工作还是有一些微小的开销的,如果频繁调用很少语句的小函数,则这些开销对性能的影响还不好说。例如,下面代码频繁的调用一个小函数:

View Code
 1 //====================
 2 // f0601.cpp
 3 //频繁调用一个小函数
 4 //====================
 5 #include<iostream>
 6 using namespace std;
 7 //------------------------------------
 8 bool isnumber(char);            // 函数声明
 9 //------------------------------------
10 int main()
11 {
12     char c;
13     while(cin>>c && c != '\n')     // 反复读入字符,若为回车便结束
14        if(isnumber(c))
15             cout<<"you entered a digit.\n";
16          else
17             cout<<"you entered a non-digit.\n";
18 }
19 //----------------------------------
20 bool isnumber(char ch)
21 {
22     return ch>='0' ** ch <= '9' ? 1 : 0;
23 }

  程序不断到输入设备中读取数据,频繁调用isnumber函数。isnumber是个小函数,所以函数调用的开销相对来说占的比重就大了。为了免去调用开销,提高效率,可将程序改写为:

View Code
 1 //====================
 2 // f0602.cpp
 3 // 将小函数“融化”在调用处
 4 //====================
 5 #include<iostream>
 6 using namespace std;
 7 //------------------------------------
 8 int main()
 9 {
10     char c;
11     while(cin>>c && c!= '\n')
12         if(c >= '0' && c <= '9' ?1 : 0) //将调用改为直接判断
13         cout<<"you entered a digit.\n";
14         else
15             cout<<"you entered a non_digit.\n";
16 }
17 //====================

  该程序在if语句中用表达式替换了函数调用。在程序运行上,因为免去了大量的函数调用开销,提高了执行效率。

  由于isnumber函数比相应的表达式可读性好,所以若程序中多处出现isnumber,而又将其替换为复杂的实现语句的话,就会降低程序的可读性。我们既要用函数调用来体现其结构化和可读性,又要是效率尽可能地高。解决办法就是将这种小函数声明为内联(inline):

View Code
 1 //====================
 2 // f0603.cpp
 3 // 内联函数
 4 //====================
 5 #include<iostream>
 6 using namespace std;
 7 //-----------------------------------
 8 inline bool isnumber(char);        // 内联声明
 9 //-----------------------------------
10 int main()
11 {
12     char c;
13     while(cin>>c && c != '\n')
14         if(isnumber(c))
15             cout<<"you entered a digit.\n";
16         else
17             cout<<"you entered a non_digit.\n";
18 }
19 //-----------------------------------
20 bool isnumber(char ch)
21 {
22     return ch >= '0' && ch <= '9' ? 1 : 0;
23 }
24 //====================

  对函数的内联声明必须在调用之前。因为内联函数的代码在程序运行时是直接潜在调用处执行的,他不影响链接,只在编译时确定运行代码。因此编译时,在调用之前看到内联声明就是十分必要。例如下列程序没有内联:

View Code
 1 //====================
 2 // f0604.cpp
 3 // 未声明内联
 4 //====================
 5 #include<iostream>
 6 using namespace std;
 7 //-----------------------------------
 8 bool isnumber(char);               // 此处无inline
 9 //-----------------------------------
10 int main()
11 {
12     char c;
13     while(cin>>c && c != '\n')
14         if(isnumber(c))
15             cout<<"you entered a digit.\n";
16         else
17             cout<<"you entered a non_digit.\n";
18 }
19 //-----------------------------------
20 inline bool isnumber(char ch)          // 此处为 inline
21 {
22     return ch >= '0' && ch <= '9' ? 1 : 0;
23 }

  编译并不认为那是内联函数,对待isnumber函数调用就如调用普通函数那样,产生该函数的调用代码并进行链接。

  内联函数体应该尽可能小,且要结构简单,这样签入的代码才不会影响调用函数的主体结构。所以内联函数中,不能含有复杂的结构控制语句,如switch和while。如果内联函数有这些语句,则编译将无视内联声明,只是视同普通函数那样产生调用代码。

  当然递归函数属于结构复杂的函数,也不能用来做内联函数。

  经验上,内联函数只适合于只有1~5行的小函数。对一个含有许多语句的大函数,函数调用的开销相对来说微不足道,所以也没有必要将函数内联。

  特别是在自定义数据类型的时候,会涉及大量的小函数定义,这些小函数回被频繁使用,所以,内联函数针对它们特别适合。

  另外,内联函数放入头文件。内联函数的定义对编译器而言必须是可见的,以便编译器能够在调用点内联展开该函数的代码。此时,仅有函数原型是不够的。内联函数可能要在程序中只出现一次,而且在所有源文件中,其定义必须是完全相同的。把内联函数的定义放在头文件中,可以确保在调用函数式所使用的定义是相同的,并且保证在调用点该函数的定义对编译器可见。

  内联函数使用的场合一般为:

  (1)函数体适当小,这样就是嵌入工作容易进行,不会破坏原调用主体。

  (2)程序中特别是在循环中反复执行该函数,这样就使嵌入的效率相对较高。

  (3)程序并不多处出现该函数调用,这样就是嵌入工作量相对较少,代码量也不会剧增。

 

 

posted @ 2012-11-16 17:40  飞飞O(∩_∩)  阅读(1563)  评论(1编辑  收藏  举报