内联函数是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。