C++里的inline、#define和其他

作者: HelloGeekBand | 来源:发表于2016-02-25 15:00 被阅读181次

    转载自:http://www.cnblogs.com/iloveyoucc/archive/2012/03/18/2404658.html
    感谢原作者的辛勤付出!

    复习:

    define:定义预编译时处理的宏;
    只进行简单的字符替换,无类型检测
    inline: 内联函数对编译器提出建议,是否进行宏替换,编译器有权拒绝
    既为提出申请,不一定会成功

    一、#define和inline

    inline,一个神奇的关键字。有了它,你同时就可以获取函数和宏的优点。inline定义的函数,比起没有inline的函数来说,没有执行函数调用所带来的负担(对此可参见《C++程序的内存布局》),因此它是高效率的;比起宏来,它具有函数的可预期行为和参数类型检验。宏的行为难于预期,我们看看下面这个宏定义

     #define max(a, b) ( (a) > (b) ? (a) : (b) )   
    int a = 5, b = 0;  
    max(++a, b); // a = a + 2  
    max(++a, b+10); // a = a + 1  
    

    如果这样:

    inline int max(int a, int b)  
    {  
         return a > b ? a : b;  
    }  
    int a = 5, b = 0;  
    max(++a, b); // a = a + 1  
    max(++a, b+10); // a = a + 1  
    

    一切都很美好!但是会这么简单吗?
    C++最初引入inline的原因是不想破坏类的封装,同时保持高效率。例如:

    class stack {  
    private:  
      int i;  
        
    public:  
      int get() {return i;} // inline函数  
    };   
    

    想访问stack的成员变量i,想保持stack的封装,同时还想调用时高效率,那么请inline。
    inline对于编译器而言,意味着“在编译阶段,将调用动作以被调用函数的本体替换之”。但是它只是一种建议,编译器可以去做,也可以不去做。从逻辑上来说,编译器将函数inline的步骤如下:

    1、将inline函数体复制到inline函数调用点处;

    2、为所用inline函数中的局部变量分配内存;

    3、将inline函数的的输入参数和返回值映射到调用方法的局部变量空间中;

    4、如果inline函数有多个返回点,将其转变为inline函数代码块末尾的分支(使用GOTO)。

    经过以上处理,可消除所有与调用相关的痕迹以及性能的损失。inline通过消除调用开销来提升性能,并且允许进行调用间优化。我们看下面这段代码:

    int test(){  
      int a = 6;  
      ...... // 此处省略代码未对a经行修改  
      int b = inline_func(b);  
      ...... // 此处省略代码未对b经行修改  
      int c = b + 1;  
      ......  
    }  
      
    inline int inline_func(int q) {  
      if (q > 10) return -1;  
      else if (q > 0) return (1 << q) - 1;  
      else return 0;  
    }  
    

    inline后

    int test() {  
      int a = 6;  
      ...... // 此处省略代码未对a经行修改  
      int b;  
      {  
        int _temp_q = 6;  
        int _temp;  
        if (_temp_q > 10) _temp = -1;  
        else if (_temp_q > 0) _temp = (1 << q) - 1;  
        else _temp = 0;  
        b = _temp;  
      }  
      ...... // 此处省略代码未对b经行修改  
      int c = b + 1;  
      ......  
    }  
    

    优化后

    int test(){  
      int a = 6;  
      ...... // 此处省略代码未对a经行修改  
      int b = 0x3f;  
      ...... // 此处省略代码未对b经行修改  
      int c = 0x40;  
      ......  
    }  
    

    上面我们主要说了inline函数的优点,那么inline函数的缺点有哪些呢?我们来看看:
    1、代码膨胀。如果inline函数体过大且编译器还让它inline成功,那么你最终的程序会代码膨胀,从而造成设备缓冲命中率低,引起较多的页面错误,读写硬盘的次数增多,这样程序的性能就下降了!建议:inline函数体一般不要超过5行,不包括循环,不包括递归调用。

    2、inline函数内部不要有static变量。inline函数的定义几乎总是放在头文件(.h)里,这允许多个实现文件(.cpp)得以引用。我们知道编译器是分别编译的,所以这个时候,在多个实现文件里就会有多个inline函数的展开,也就是说有个多个static变量,这恐怕不是我们期望的!

    3、inline函数无法随着函数库升级而升级。如果f是函数库中的一个inline函数,使用它的用户会将f函数实体编译到他们的程序中。一旦函数库实现者改变f,所有用到f的程序都必须重新编译。如果f是non-inline的,用户程序只需重新连接即可。如果函数库采用的是动态连接,那这一升级的f函数可以不知不觉的被程序使用。

    4、不要获取inline函数的地址。如果要取得一个inline函数的地址,编译器就必须为此函数产生一个函数实体,无论如何,编译器无法交出一个“不存在函数”的指针。注意,有些编译器可能会使用类的constructors和destructors的函数指针,用以构造和析构一个class对象的数组。另外类的constructors和destructors可能简单,但是其父类的类的constructors和destructors可能是复杂的,所以类的constructors和destructors往往不是inline函数的最佳选择!

    5、inline虚函数往往是无效的。虚函数往往是运行时确定的,而inline是在编译时进行的,所以inline虚函数往往无效。当然如果直接用类的对象来使用虚函数,那么对有的编译器而言,也可起到优化的作用。

    6、inline函数无法调试。原因请参见上面编译器将函数inline的步骤。所以请在项目后期,对程序进行profile后,再决定将那些函数inline化。

    二、#define和其他相关

    static
    一、产生背景
    引出原因:函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,大家知道,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现?
    最容易想到的方法是定义一个全局的变量,但定义为一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅受此函数控制)。类的静态成员也是这个道理。
    解决方案:因此C++ 中引入了static,用它来修饰变量,它能够指示编译
    器将此变量在程序的静态存储区分配空间保存,这样即实现了目的,又使得此变量的存取范围不变。
    二、具体作用
    Static作用分析总结:static总是使得变量或对象的存储形式变成静态存储,连接方式变成内部连接,对于局部变量(已经是内部连接了),它仅改变其存储方式;对于全局变量(已经是静态存储了),它仅改变其连接类型。(1 连接方式:成为内部连接;2 存储形式:存放在静态全局存储区)


    const
    一、产生背景
    a C++有一个类型严格的编译系统,这使得C++程序的错误在编译阶段即可发现许多,从而使得出错率大为减少,因此,也成为了C++与C相比,有着突出优点的一个方面。
    b C中很常见的预处理指令 #define VariableName VariableValue 可以很方便地进行值替代,这种值替代至少在三个方面优点突出:
    一是避免了意义模糊的数字出现,使得程序语义流畅清晰,如下例:

      #define USER_NUM_MAX 107 
    这样就避免了直接使用107带来的困惑。
    

    二是可以很方便地进行参数的调整与修改,如上例,当人数由107变为201时,改动此处即可;
    三是提高了程序的执行效率,由于使用了预编译器进行值替代,并不需要为这些常量分配存储空间,所以执行的效率较高。

    然而,预处理语句虽然有以上的许多优点,但它有个比较致命的缺点,即,预处理语
    句仅仅只是简单值替代,缺乏类型的检测机制。这样预处理语句就不能享受C++严
    格类型检查的好处,从而可能成为引发一系列错误的隐患。
    Const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时
    继承它的优点。
    现在它的形式变成了:

    Const DataType VariableName = VariableValue ;
    
    1. 具体作用
      1.const 用于指针的两种情况分析:
         int const *A;  //A可变,*A不可变
         int *const A;  //A不可变,*A可变
    

    分析:const 是一个左结合的类型修饰符,它与其左侧的类型修饰符和为一个
    类型修饰符,所以,int const 限定 *A,不限定A。int const 限定A,不限定A。
    2.const 限定函数的传递值参数:
      void Fun(const int Var);
      分析:上述写法限定参数在函数体中不可被改变。
    3.const 限定函数的值型返回值:

    const int Fun1();
    const MyClass Fun2();
    

    分析:上述写法限定函数的返回值不可被更新,当函数返回内部的类型时(如Fun1),已经是一个数值,当然不可被赋值更新,所以,此时const无意义,最好去掉,以免困惑。当函数返回自定义的类型时(如Fun2),这个类型仍然包含可以被赋值的变量成员,所以,此时有意义。
    4 传递与返回地址: 此种情况最为常见,由地址变量的特点可知,适当使用const,意义昭然。
    5. const 限定类的成员函数:

    class ClassName {
     public:
      int Fun() const;
     .....
    }
    

    注意:采用此种const 后置的形式是一种规定,亦为了不引起混淆。在此函数的声明中和定义中均要使用const,因为const已经成为类型信息的一部分。
    获得能力:可以操作常量对象。
    失去能力:不能修改类的数据成员,不能在函数中调用其他不是const的函数。

    相关文章

      网友评论

        本文标题:C++里的inline、#define和其他

        本文链接:https://www.haomeiwen.com/subject/iowikttx.html