c++ inline

  • 引入 inline 函数的主要原因是用它替代 C 中复杂易错、不易维护的宏函数

  • inline 关键字有什么作用。

    • inline内联函数,它可以避免相同函数重写多次的麻烦。它减少了运行时间但是增加了空间开销。
    • 具体而言,当编译器遇到内联函数,它不会编译成指令,而是整体的插入到调用处,增加代码可复用性
// 求 0-9 的平方
inline int inlineFunc(int num) {  
  if(num>9||num<0) return -1;  
  return num*num;  
}  

int main(int argc,char* argv[]) {
	int a=8;
	int res=inlineFunc(a);
	cout<<"res:"<<res<<endl;
}


// inline 之后代码为:

int main(int argc,char* argv[]) {
	int a = 8; {  
	    int _temp_b=8;  
	    int _temp;  
	    if (_temp_q >9||_temp_q<0) _temp = -1;  
	    else _temp =_temp*_temp;  
	    b = _temp;  
	}
}
  • 内联函数:内联函数与常规函数的区别在于编译器如何将他们组合到程序中;内联函数相当于将函数块copy过去,而常规是跳转去调用;
  1. 编译器对 inline 函数的处理步骤:
    1. 将 inline 函数体复制到 inline 函数调用点处;如果在5个不同的地方调动内联函数,则该程序将包含5个副本;
    2. 为所用 inline 函数中的局部变量分配内存空间;
    3. 将 inline 函数的的输入参数和返回值映射到调用方法的局部变量空间中;
    4. 如果 inline 函数有多个返回点,将其转变为 inline 函数代码块末尾的分支(使用 GOTO)
  2. 使用
    1. 关键字 inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。最好是在 声明 定义都添加关键字
    2. 把内联函数的定义放在头文件中
      1. 由于inline函数需要展开的缘故,所有调用inline函数的源文件必须包含inline函数的定义,因为仅仅有函数的声明无法做到函数的展开。这可以当成是inline函数和其他函数不同的一个地方:即inline函数可以多重定义
      2. 为了避免一个inline函数在多个源文件(.cpp文件)中出现,我们应该把inline函数的定义放在一个头文件(.h文件)中,所有调用inline函数的源文件(.cpp文件)包含此头文件即可
      3. [至于内联函数是定义在头文件还是源文件的建议]
    3. 类的成员函数为inline的3种情形:
      1. 在类的内部直接定义函数,这是隐式的inline函数,如char get() const;
      2. 在类的声明处用inline关键字,这是显示化成员函数,如inline char get(pos r, pos c) const;
      3. 在类外部定义处用inline关键字,如inline Screen& Screen::move(pos r, pos c) {}
    4. 类的构造函数,析构函数的inline
      1. 在类withMembers里,内联的构造函数和析构函数看起来似乎很直接和简单,但其实很复杂,因为成员对象Q、P和S的构造函数和析构函数被自动调用。
        1. 所以说,表面上类的构造函数和析构函数使用内联更有效。实际上,要当心构造函数和析构函数可能会隐藏一些行为:“偷偷地”执行了基类或成员对象的构造函数和析构函数,所以不要随便地将构造函数和析构函数的定义体放在类声明中
  3. 优点
    1. 内联能提高函数效率,但并不是所有的函数都定义成内联函数!内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。
      1. 如果执行函数体内代码的时间相比于函数调用的开销较大,那么效率的收获会更少
      2. 每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间
    2. 以下情况不宜用内联:
      1. 如果函数体内的代码比较长,使得内联将导致内存消耗代价比较高。
      2. 如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。 inline 机制一般用于规模较小(可理解为行数少)、流程直接(可理解为没有for循环)、频繁调用的函数。
  4. 缺点
    1. 代码膨胀
    2. inline 函数无法随着函数库升级而升级。inline函数的改变需要重新编译,不像 non-inline 可以直接链接。
    3. 是否内联,程序员不可控。内联函数只是对编译器的建议,决定权在于编译器
  5. 虚函数(virtual)可以是内联函数(inline)吗?
    1. 虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。
    2. 内联是在编译期建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。
    3. inline virtual 唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如 Base::who()),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。
class Base {
public:
    inline virtual void who() {
        cout << "I am Base\n";
    }
    virtual ~Base() {}
};

class Derived : public Base {
public:
    // 不写inline时隐式内联
    inline void who() {
        cout << "I am Derived\n";
    }
};

int main() {
    // 此处的虚函数 who(),是通过类(Base)的具体对象(b)来调用的,编译期间就能确定了,所以它可以是内联的,但最终是否内联取决于编译器。
    Base b;
    b.who();

    // 此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,所以不能为内联。
    Base *ptr = new Derived();
    ptr->who();

    // 因为Base有虚析构函数(virtual ~Base() {}),所以 delete 时,会先调用派生类(Derived)析构函数,再调用基类(Base)析构函数,防止内存泄漏。
    delete ptr;
    ptr = nullptr;
    return 0;
}
至于内联函数是定义在头文件还是源文件的建议
  • 内联展开是在编译时进行的,只有链接的时候源文件之间才有关系。所以内联要想跨源文件必须把实现写在头文件里。如果一个 inline 函数会在多个源文件中被用到,那么必须把它定义在头文件中。
// base.h
class Base{protected:void fun();};

// base.cpp
#include base.h
inline void Base::fun(){}

// derived.h
#include base.h
class Derived: public Base{public:void g();};

// derived.cpp
void Derived::g(){fun();} // VC2010: error LNK2019: unresolved external symbol
  • 上面这种错误,就是因为内联函数 fun() 定义在编译单元 base.cpp 中,那么其他编译单元中调用fun()的地方将无法解析该符号,因为在编译单元 base.cpp 生成目标文件 base.obj 后,内联函数fun()已经被替换掉,编译器不会为 fun() 生成函数实体,链接器自然无法解析。所以如果一个 inline 函数会在多个源文件中被用到,那么必须把它定义在头文件中。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值