实用经验 47 内联函数会像宏一样替换吗?

本文探讨了C++中的内联函数和宏,两者都能提高代码执行效率,但内联函数更安全,能避免宏的二义性问题并进行参数检查。内联函数适合代码量小、不含循环或递归的函数,而宏可能引发二义性且不进行参数检查。建议通常使用内联函数代替宏,但在某些情况下,如assert断言,仍需使用宏。内联函数的使用应适度,避免过度使用导致内存开销增大。
摘要由CSDN通过智能技术生成

内联函数是C++语言新引入的概念。其目的是为了提高函数的执行效率。宏是C++从它的前辈C继承而来的,宏同样可以提高代码的执行效率,减少函数执行过程中的调用开销。两者既有相同点也有不同点。

内联函数和宏的功能非常类似,在讲述内联函数之前,我们有必要回顾一下宏。笔者实现了一个求两个整数和的宏。

#define Sum(nNoA, nNoB)  ((nNoA)+(nNoB))

然而,C++语言为什么要引入宏呢?这主要是因为函数在调用过程中,在函数调用之前要保存当前函数执行的现场(即上下文环境)。而函数执行结束时又需恢复执行前的现场。函数执行时有一定的时间和空间开销。这将在一定的情况下影响函数的执行效率。而宏只是在预处理时把宏代码展开,不需要保存现场和恢复现场。所以从执行效率而言宏比函数的执行效率要高。这也正是C语言中大量使用宏的原因。

同其他事物一样,宏也有两面性。宏也有不尽如人意的地方。有时还让程序员痛苦不堪,例如宏所展现的二义性。

注意:宏不能访问对象的私有成员;宏很容易引起二义性。

内联函数是C++语言为提高函数执行效率而引进的一种特殊函数。使用宏遇到的负面效果,可完全通过使用内联函数加以解决。内联函数具备宏的效果,同时具备函数的功能。必须要说的是内联函数是真正的函数,但它使用时会像宏一样展开。减小了普通函数执行时保存现场和恢复现场的时刻开销。因此你可以像使用普通函数一样使用内联函数,而不必担心像宏处理时产生的那些问题。例如宏不进行参数检查而内联函数会进行参数检查。

我们首先看一下普通内联函数的实现方式。下面是笔者实现的求取两整数和的内联函数。

int Sum(int nNoA, int nNoB);       // 两整数求和内联函数声明
inline int Sum(int nNoA, int nNoB)
{
      return (nNoA+nNoB);
}

注意:内联函数inline关键字必须和函数定义在一起,才会有效。同时内联函数的代码行数不能太多。

除了普通内联函数,类亦可定义内联函数,且类中的内联函数远比普通内联函数更常用。类的内联函数有两种形式:隐式内联和显式内联。

(1)我们首先看一下CTime类的隐式内联实现。

// CTime类隐式内联函数实现。  
Class CTime
{
public:
     CTime()
     ~CTime();
     void Show() { cout << m_nTime;}
private:
     time_t m_nTime;
}

(2)了解了类的隐式内联函数实现后,我们在看一下Time类的显式内联实现。

// Time.h   CTime类实现头文件 CTime类显式内联函数实现。  
Class CTime
{
public:
     CTime()
     ~CTime();
     void Show();
private:
     time_t m_nTime;
}
// Time.cpp   CTime类实现cpp文件 CTime类显式内联函数实现。  
#include<Time.h>
CTime::CTime() {}
CTime::~CTime() {}
inline void CTime::Show()
{
    cout << m_nTime;
}

注意:在类声明中实现的成员函数自动成为inline函数这种方式为隐式内联。也可以通过inline关键字将内联函数的函数体放到类声明之外,这种方式为显式内联。我个人更倾向于第二种内联实现方式。因为把成员函数的实现放到类的声明中虽可带来书写上的方便。但不是一种良好的编程风格。

宏和内联函数的基本实现我们已讲述完了。下面我们主要讲述两者之间的共同点和不同点及使用技巧。

宏和内联的异同点

宏和内联函数的相同之处:

  • 宏和内联函数都可以提升代码执行的效率。
  • 宏和内联函数都以代码展开的形式,进行代码替换。减少函数调用的开销。
  • 两者均适用于代码量较小的函数。如果代码量过多。宏展开会占用大量内存,会降低程序执行速度;而对于内联函数,编译器可能会将之视为非内联函数使用。
  • 使用宏和内联函数的地方都会发生代码拷贝和复制,过多的使用宏和内联函数会浪费内存空间。

宏和内联函数的不同之处:

  • 宏使用时很容易出现二义性,内联函数可以很好的解决宏使用过程中出现的二义性。
  • 内联函数是函数,使用内联函数时会进行函数的参数检查。而宏不会。
  • 宏是由预处理器对宏进行文本替换,而内联函数通过编译器进行替换,且它是真正的函数,会对参数类型进行检查,更加安全。
  • 内联函数可以调试,而宏定义是不可以调试的。
  • 通过内联函数和宏的相同点和不同点的总结,我们可得出下面的结论。一般情况下建议通过内联函数来替换宏实现。这样可以进行参数检查,消除宏的二义性问题,同时也不会降低宏的效率。

注意:不是在所有情况下使用内联函数都是好的。assert断言就是一个例外。assert是仅在Debug版本起作用的宏,它用于检查“不应该”发生的情况。为了不在程序的Debug版本和Release版本引起差别,assert不应该产生任何副作用。如果assert是函数,由于函数调用会引起内存、代码的变动,那么将导致Debug版本与Release版本存在差异。所以assert不是函数,而是宏。

内联函数有这么多优点,是不是可以无限制的使用内联函数呢?答案当然是不可能的。在程序设计过程中其主要作用的函数还是以普通函数为主,内联函数只是在一些特殊情况下为提升效率而使用的辅助策略。

宏和内联的使用技巧

(1)当出现下列情形时,函数不适合声明为内联函数:

  • 函数体代码太长。一般如果函数体代码行数在5行一下,可声明为内联函数。如果函数体代码太长,使用内联函数导致内存开销代价过高。
  • 函数体包含循环语句。执行包含循环语句的函数体代码的时间要比函数调用的开销大。
  • 函数为递归函数。递归函数涉及到嵌套,内联函数是无法嵌套展开的。一般情况下内联递归函数会转化为普通函数执行。

(2)构造函数和析构函数的内联。

类的构造函数和析构函数一般会让人错误的认为内联函数更高效。要当心构造函数和析构函数可能会隐藏一些行为,如“偷偷地”执行了基类或成员对象的构造函数和析构函数。所以不要随便地将构造函数和析构函数的定义体放在类声明中。

(3)函数是否inline由编译器决定。

无论是显式内联还是隐式内联,都只仅仅向编译器提一个inline建议。编译器最终是否进行inline由编译器自己决定;任何编译器都可以在优化程序时对程序进行inline处理。编译器会通过启发式算法决定是否值得对一个函数进行inline,同时要保证不会对生成文件的大小产生较大影响。

请谨记

  • 不是所有函数都适合声明为内联函数。即使声明为内联函数也不一定编译器最终就会对此函数进行inline。
  • 只有那些代码行数1-5行,不包含循环,递归的函数才有可能最终inline。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值