C++内联函数学习总结

来源:互联网 发布:weixin js sdk 编辑:程序博客网 时间:2024/06/12 17:07

C++中的内联函数inline总结

http://blog.csdn.net/coder_xia/article/details/6723387


      突然看到C++Primer中讲到,对于vector的一个循环,调用语句:(示例代码i=v.begin()不是很规范,虽然不会出错,客官请将就着看)


for (int i=v.begin() ; i<v.size() ; i++)  
{  
    ....  
}  
       对于size()的调用,其实是内联。想到以前貌似是看《高质量C/C++编程》时,提过,在循环时,可以采用变量保存v.size()的值,以减少每个循环的调用开支。于是决定一搜,顺便总结之。


1、inline的引出


考虑下列min()函数(例子来自C++Primer第三版303页)


int min( int v1, int v2 )  
{  
    return( v1 < v2 << v1 : v2 );  
}  
      为这样的小操作定义一个函数的好处是:
     a.如果一段代码包含min()的调用,那阅读这样的代码并解释其含义比读一个条件操作符的实例,可读性会强很多。


     b.改变一个局部化的实现比更改一个应用中的300个出现要容易得多


     c.语义是统一的,每个测试都能保证相同的方式实现


     d.函数可以被重用,不必为其他的应用重写代码


     不过,将min()写成函数有一个严重的缺点:调用函数比直接计算条件操作符要慢很多。那怎么能兼顾以上优点和效率呢?C++提供的解决方案为inline(内联)函数


2、inline的原理:代码替代


       在程序编译时,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体来进行替代。


       例如,如果一个函数被指定为inline 函数则它将在程序中每个调用点上被内联地展开例如


int minVal2 = min( i, j );  
在编译时被展开为


int minVal2 = i < j << i : j;  
 则把min()写成函数的额外执行开销从而被消除了。
3、inline的使用


       让一个函数成为内联函数,隐式的为在类里定义函数,显式的则是在函数前加上inline关键字说明。


4、使用inline的一些注意事项


      a.从inline的原理,我们可以看出,inline的原理,是用空间换取时间的做法,是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。所以,如果函数体代码过长或者函数体重有循环语句,if语句或switch语句或递归时,不宜用内联


      b.关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。内联函数调用前必须声明。《高质量C/C++编程》里一个例子。


inline void Foo(int x, int y); // inline 仅与函数声明放在一起  
void Foo(int x, int y)  
{  
    ...  
}  
以上代码不能成为内联函数,而以下则可以


void Foo(int x, int y);  
inline void Foo(int x, int y) // inline 与函数定义体放在一起  
{  
    ...  
}  
       所以说,inline 是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。对于以上例子,林锐还建议,只在定义前加上inline,而不是在声明和定义前都加,因为这能体现高质量C++/C 程序设计风格的一个基本原则:声明与定义不可混为一谈。
       c.inline对于编译器来说只是一个建议,编译器可以选择忽略该建议。换句话说,哪怕真的写成了inline,也没有任何错误的情况下,编译器会自动进行优化。所以当inline中出现了递归,循环,或过多代码时,编译器自动无视inline声明,同样作为普通函数调用。


总结下:


       楼主觉得可以将内联理解为C++中对于函数专有的宏,对于C的函数宏的一种改进。对于常量宏,C++提供const替代;而对于函数宏,C++提供的方案则是inline。在C中,大家都知道宏的优势,编译器通过复制宏代码的方式,省去了参数压栈,生成汇编的call调用,返回参数等操作,虽然存在一些安全隐患,但在效率上,还是很可取的。
       不过函数宏还是有不少缺陷的,主要有以下:


       a.在复制代码时,容易出现一想不到的边际效应,比如经典的


#define MAX(a, b) (a) > (b) ? (a) : (b)  
在执行语句:


result = MAX(i, j) + 2 ;  
时,会被解释为


result = (i) > (j) ? (i) : (j) + 2 ;  
     b.使用宏,无法进行调试,虽然windows提供了ASSERT宏
     c.使用宏,无法访问类的私有成员
      所以,C++ 通过内联机制,既具备宏代码的效率,又增加了安全性,还可以自由操作类的数据成员,算是一个比较完美的解决方案。


      关于宏,大家还可以参考下:http://dev.yesky.com/260/2095260.shtml,更原创,内容丰富,可取,http://wenku.baidu.com/view/1247bc22192e45361066f564.html总结了各家观点,稍微整理了下,不过还是蛮清楚的。
========

c++内联函数(inline)及内联函数的使用及注意点

http://blog.csdn.net/wyq_tc25/article/details/51721636  


介绍内联函数之前,有必要介绍一下预处理宏。内联函数的功能和预处理宏的功能相似。相信大家都用过预处理宏,我们会经常定义一些宏,如


#define TABLE_COMP(x) ((x)>0?(x):0)


就定义了一个宏。


为什么要使用宏呢?因为函数的调用必须要将程序执行的顺序转移到函数所存放在内存中的某个地址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方。这种转移操作要求在转去执行前要保存现场并记忆执行的地址,转回后要恢复现场,并按原来保存地址继续执行。因此,函数调用要有一定的时间和空间方面的开销,于是将影响其效率。而宏只是在预处理的地方把代码展开,不需要额外的空间和时间方面的开销,所以调用一个宏比调用一个函数更有效率。
但是宏也有很多的不尽人意的地方。
1、宏不能访问对象的私有成员。
2、宏的定义很容易产生二意性。
我们举个例子:
复制代码 代码如下:


#define TABLE_MULTI(x) (x*x)
我们用一个数字去调用它,TABLE_MULTI(10),这样看上去没有什么错误,结果返回100,是正确的,但是如果我们用TABLE_MULTI(10+10)去调用的话,我们期望的结果是400,而宏的调用结果是(10+10*10+10),结果是120,这显然不是我们要得到的结果。避免这些错误的方法,一是给宏的参数都加上括号。


#define TABLE_MULTI(x) ((x)*(x))
这样可以确保不会出错,但是,即使使用了这种定义,这个宏依然有可能出错,例如使用TABLE_MULTI(a++)调用它,他们本意是希望得到(a+1)*(a+1)的结果,而实际上呢?我们可以看看宏的展开结果: (a++)*(a++),如果a的值是4,我们得到的结果是5*6=30。而我们期望的结果是5*5=25,这又出现了问题。事实上,在一些C的库函数中也有这些问题。例如: Toupper(*pChar++)就会对pChar执行两次++操作,因为Toupper实际上也是一个宏。
我们可以看到宏有一些难以避免的问题,怎么解决呢?
下面就是用我要介绍的内联函数来解决这些问题,我们可以使用内联函数来取代宏的定义。而且事实上我们可以用内联函数完全取代预处理宏。
内联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。
我们可以用Inline来定义内联函数,不过,任何在类的说明部分定义的函数都会被自动的认为是内联函数。
下面我们来介绍一下内联函数的用法。
内联函数必须是和函数体申明在一起,才有效。像这样的申明Inline Tablefunction(int I)是没有效果的,编译器只是把函数作为普通的函数申明,我们必须定义函数体。


Inline tablefunction(int I) {return I*I};
这样我们才算定义了一个内联函数。我们可以把它作为一般的函数一样调用。但是执行速度确比一般函数的执行速度要快。
我们也可以将定义在类的外部的函数定义为内联函数,比如:


Class TableClass{
 Private:
Int I,j;
 Public:
Int add() { return I+j;};
Inline int dec() { return I-j;}
Int GetNum();
}
inline int tableclass::GetNum(){
return I;
}
上面申明的三个函数都是内联函数。在C++中,在类的内部定义了函数体的函数,被默认为是内联函数。而不管你是否有inline关键字。
内联函数在C++类中,应用最广的,应该是用来定义存取函数。我们定义的类中一般会把数据成员定义成私有的或者保护的,这样,外界就不能直接读写我们类成员的数据了。对于私有或者保护成员的读写就必须使用成员接口函数来进行。如果我们把这些读写成员函数定义成内联函数的话,将会获得比较好的效率。


Class sample{
 Private:
Int nTest;
 Public:
Int readtest(){ return nTest;}
 Void settest(int I) {nTest=I;}
}


当然,内联函数也有一定的局限性。就是函数中的执行代码不能太多了,如果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。这样,内联函数就和普通函数执行效率一样了。
以下转自:http://www.cnblogs.com/xkfz007/archive/2012/03/27/2420166.html
内联函数并不总是内联 Inline function是在C++中引入的一种机制,它可以拓展函数代码,避免调用函数的额外开销。在Linux环境下,gcc编译选项必须加上优化选项才能使inline有效。
inline与static的关系
在这儿有一个比较详细的分析:http://www.cnblogs.com/xkfz007/articles/2370640.html
内联函数(inline)机制与陷阱
内联机制被引入C++作为对宏(Macro)机制的改进和补充(不是取代)。内联函数的参数传递机制与普通函数相同。但是编译器会在每处调用内联函数的地方将内联函数的内容展开。这样既避免了函数调用的开销又没有宏机制的前三个缺陷。
但是程序代码中的关键字"inline"只是对编译器的建议:被"inline"修饰的函数不一定被内联(但是无"inline"修饰的函数一定不是)。
许多书上都会提到这是因为编译器比绝大多数程序员都更清楚函数调用的开销有多大,所以如果编译器认为调用某函数的开销相对该函数本身的开销而言微不足道或者不足以为之承担代码膨胀的后果则没必要内联该函数。这当然有一定道理,但是按照C、C++一脉相承的赋予程序员充分自由与决定权的风格来看,理由还不够充分。我猜想最主要的原因是为了避免编译器陷入无穷递归。如果内联函数之间存在递归调用则可能导致编译器展开内联函数时陷入无穷递归。有时候函数的递归调用十分隐蔽,程序员并不容易发现,所以简单起见,将内联与否的决定权交给编译器。
 
另一种不被内联的情况是使用函数指针来调用内联函数。
对于C++中内联机制的一个常见误解是:关键字"inline"只是对编译器的建议,如果编译器发现指定的函数不适合内联就不会内联;所以即使内联使用的不恰当也不会有任何副作用。这句话只对了一半,内联使用不恰当是会有副作用的:会带来代码膨胀,还有可能引入难以发现的程序臭虫。
根据规范,当编译器认为希望被内联的函数不适合内联的时候,编译器可以不内联该函数。但是不内联该函数不代表该函数就是一个普通函数了,从编译器的实际实现上来讲,内联失败的函数与普通函数是有区别的:
(1)普通的函数在编译时被单独编译一个对象,包含在相应的目标文件中。目标文件链接时,函数调用被链接到该对象上。
(2)若一个函数被声明成内联函数,编译器即使遇到该函数的声明也不会为该函数编译出一个对象,因为内联函数是在用到的地方展开的。可是若在调用该内联函数的地方发现该内联函数的不适合展开时怎么办?一种选择是在调用该内联函数的目标文件中为该内联函数编译一个对象。这么做的直接后果是:若在多个文件调用了内联失败的函数,其中每个文件对应的目标文件中都会包含一份该内联函数的目标代码。
如果编译器真的选择了上面的做法对待内联失败的函数,那么最好的情况是:没吃到羊肉,反惹了一身骚。即内联的好处没享受到,缺点却承担了:目标代码的体积膨胀得与成功内联的目标代码一样,但目标代码的效率确和没内联一样。
更糟的是由于存在多份函数目标代码带来一些程序臭虫。最明显的例子是:内联失败的函数内的静态变量实际上就不在只有一份,而是有若干份。这显然是个错误,但是如果不了解内幕就很难找到原因。
========

C++内联函数(inline)的工作原理与例子

http://blog.csdn.net/buptzhengchaojie/article/details/50568789)  


       内联函数(inline function与一般的函数不同,它不是在调用时发生控制转移,而是在编译阶段将函数体嵌入到每一个调用该函数的语句块中。内联函数(inline function)与编译器的工作息息相关。编译器会将程序中出现内联函数的调用表达式用内联函数的函数体来替换。
内联函数的优点:
       内联函数是将程序执行转移到被调用函数所存放的内存地址,将函数执行完后,在返回到执行此函数前的地方。这种转移操作需要保护现场、包括进栈等操作,在被调用函数代码执行完后,再恢复现场。但是保护现场和恢复现场需要较大的资源开销。对于一些较小的调用函数来说,若是频繁调用,函数调用过程甚至可能比函数执行过程需要的系统资源更多。所以引入内联函数,可以让程序执行效率更高。
内联函数的缺点:
       如果调用内联函数的地方过多,也可能造成代码膨胀。因为编译器会把内联函数的函数体嵌入到每一个调用了它的地方,重复地嵌入。
以下是几种含有内联函数的情况:
一、 在类中使用内联函数和不使用内联函数
1、


/** 
*在类里定义的成员函数会被默认的认为是指定为内置函数 
*/  
#include <iostream>    
#include <string>    
  
using namespace std;  
  
class Person  
{  
public:  
    void display()  
    {  
        cout << "name:" << name << endl;  
    }  
    string name;  
};  
  
int main(int argc, char* argv[])  
{  
    Person person;  
    person.name = "Erin";  
    person.display();  
    return 0;  
}  


2、


/** 
*类外定义的函数用inline指定为内置函数 
*/  
#include <iostream>    
#include <string>    
  
using namespace std;  
  
class Person  
{  
public:  
    inline void display();  
    string name;  
};  
  
inline void Person::display()  
{  
    cout << "name:" << name << endl;  
}  
  
int main(int argc, char* argv[])  
{  
    Person person;  
    person.name = "Erin";  
    person.display();  
    return 0;  
}  
3、


/** 
*无内置函数 
*既没有在类内定义函数,也没有用inline在类外定义函数 
*/  
  
#include <iostream>    
#include <string>    
  
using namespace std;  
  
class Person  
{  
public:  
    void display();  
    string name;  
};  
  
void Person::display()  
{  
    cout << "name:" << name << endl;  
}  
  
int main(int argc, char* argv[])  
{  
    Person person;  
    person.name = "Erin";  
    person.display();  
    return 0;  
}  

二、普通函数的调用内联函数
 
#include<iostream>  
using namespace std;  
inline int max(int i, int j, int k) {  
    if (i<j)  
    {  
        i = j;  
    }  
    if (i < k) {  
        i = k;  
    }  
    return i;  
}  
int main() {  
    int i = 1, j = 2, k = 3;  
    int bigest = max(i, j, k);  
    cout << bigest << endl;  
    return 0;  

========

C++如何处理内联虚函数

http://www.cnblogs.com/jingzhishen/p/4199931.html


当一个函数是内联和虚函数时,会发生代码替换或使用虚表调用吗? 为了弄清楚内联和虚函数,让我们将它们分开来考虑。通常,一个内联函数是被展开的。


        class CFoo {
        private:
            int val;
        public:
        int GetVal() { return val; }
        int SetVal(int v) { return val=v; }
        };        
这里,如果使用下列代码:


        CFoo x;
        x.SetVal(17);
        int y = x.GetVal();  
那么编译器产生的目标代码将与下面的代码段一样:


        CFoo x;
        x.val = 17;
        int y = x.val;  
    你当然不能这么做,因为val是个私有变量。内联函数的优点是不用函数调用就能隐藏数据,仅此而已。


    虚函数有多态性,意味着派生的类能实现相同的函数,但功能却不同。假设 GetVal 被声明为虚函数,并且你有第二个 以不同方法实现的类 CFoo2:


        class CFoo2 : public CFoo {
        public: 
        // virtual in base class too!
        virtual int CFoo2::GetVal() { return someOtherVal; } 
        };
    如果 pFoo是一个 CFoo 或 CFoo2 指针,那么,无论 pFoo 指向哪个类 CFoo 或 CFoo2,成员函数 pFoo->GetVal 都能调用成功。


    如果一个函数既是虚拟函数,又是内联函数,会是什么情况呢?记住,有两种方式建立内联函数,


第一种是在函数定义中使用关键字 inline,如:


        inline CFoo::GetVal() { return val; }        
第二种是在类的声明中编写函数体,就象前面的 CFoo2::GetVal 一样。所以如果将虚函数体包含在类的声明中,如:


        class CFoo {
        public:
        virtual int GetVal() { return val; }
        };        
    编译器便认为这个函数 GetVal 是内联的,同时也是虚拟的。那么,多态性和内联特性如何同时工作呢?


    编译器遵循的第一个规则是无论发生什么事情,多态性必须起作用。如果有一个指向 CFoo 对象的指针,pFoo->GetVal 被保证去调用正确的函数。一般情况下,这就是说函数 GetVal 将被实例化为非内联函数,并有vtable(虚表)入口指向它们。但这并不意味着这个函数不能被扩展!再看看下面的代码:


        CFoo x; 
        x.SetVal(17)
        int y = x.GetVal()  
    编译器知道x是 CFoo,而不是CFoo2,因为这个堆对象是被显式声明的。x肯定不会是CFoo2。所以展开 SetVal/GetVal 内联是安全的。如果要写更多的复杂代码:


CFoo x; 
CFoo* pfoo=&x; 
pfoo->SetVal(17); 
int y = pfoo->GetVal(); 
...
CFoo2 x2; 
pfoo = &x2; 
pfoo->SetVal(17); //etc. 
编译器知道 pfoo 第一次指向x,第二次指向x2,所以展开虚拟函数也是安全的。


    你还可以编写更复杂的代码,其中,pfoo 所指的对象类型总是透明的,但是大多数编译器不会做任何更多的分析。即使在前面的例子中,某些编译器将会安全运行,实例化并通过一个虚表来调用。实际上, 编译器总是忽略内联需要并总是使用虚表。唯一绝对的规则是代码必须工作;也就是说,虚函数必须有多态行为。
    通常,无论是显式还是隐式内联,它只是一个提示而已,并非是必须的,就象寄存器一样。编译器完全能拒绝展开一个非虚内联函数,C++编译器常常首先会报 错:“内联中断-函数太大”。如果内联函数调用自身,或者你在某处传递其地址,编译器必须产生一个正常(外联?)函数。内联函数在DEBUG BUILDS中不被展开,可设置编译选项来预防。
    要想知道编译器正在做什么,唯一的方法是看它产生的代码。对于微软的编译器来说,你可以用-FA编译选项产生汇编清单。你不必知道汇编程序如何做。我鼓励 你完成这个实验;这对于了解机器实际所做的事情机器有益,同时你可学习许多汇编列表中的内容。
    有关内联函数的东西比你第一次接触它时要复杂得多。有许多种情况强迫编译器产生正常函数:递归,获取函数地址,太大的那些函数和虚函数。但是如果编译器决定实例化你的内联函数,就要考虑把函数放在什么地方?它进入哪个模块?
    通常类在头文件中声明,所以如果某个cpp包含foo.h,并且编译器决定实例化CFoo::GetVal,则在cpp文件中将它实例化成一个静态函数。 如果十个模块包含foo.h,编译器产生的虚函数拷贝就有十个。实际上,可以用虚表指向不同类型的GetVal拷贝,从而是相同类型的对象只产生拷贝。一 些链接器能巧妙地在链接时排除冗余,但一般你是不能指望他来保证的。
    我们得出的结论是:最好不要使用内联虚函数,因为它们几乎不会被展开,即便你的函数只有一行,你最好还是将它与其它的类函数一起放在模块(cpp文件) 中。当然,开发者常常将简短的虚函数放在类声明中-不是因为他们希望这个函数被展开为内联,而是因为这样做更方便和可读性更强。
=======

C++内联函数跟普通函数的区别以及实现机制

http://blog.csdn.net/xiaofei2010/article/details/7609355


内联函数定义:
将函数定义为内联函数,一般就是将他在程序中每个调用点上“内联地”展开。在函数返回类型前加上inline关键字。
定义在类声明之中的成员函数将自动地成为内联函数。


内联函数适用情况:
1.一个函数被重复调用;
2.函数只有几行,且不包含for,while,switch语句。
内联函数应该放在头文件中定义,这一点不同于其他函数。


内联函数可能在程序中定义不止一次,只要内联函数的定义在某个源文件中只出现一次,而且在所有的源文件中,其定义必须是相同的。如果inline函数的定义和声明是分开的,而在另外一个文件中需要调用这些inline函数得时候,内联是无法在这些调用函数内展开的。这样内联函数在全局范围内就失去了作用。解决的办法就是把内联函数得定义放在头文件中,当其它文件要调用这些内联函数的时候,只要包含这个头文件就可以了。把内联函数的定义放在头文件中,可以确保在调用函数时所使用的定义是相同的,并保证在调用点该函数的定义对调用点可见。


内联函数具有一般函数的特性,它与一般函数所不同之处只在于函数调用的处理:
一般函数进行调用时,要将程序执行权转到被调用函数中,然后再返回到调用它的函数中;而内联函数在调用时,是将调用表达式用内联函数体来替换。
========
0 0
原创粉丝点击