内联函数
【1】为什么需要内联函数?
诸位周知,每一种语言的每一种使用规则的存在都有一定的原委,绝不会是凭空而论的。当然,内联函数也同理。
由于内联函数的功能和预处理宏(以下简称宏定义)的功能相似 。所以,先思考我们为什么使用宏定义呢?
因为函数的调用必须要将程序执行的顺序转移到该函数所存放在内存中的某个地址,且将该函数的程序内容执行完后,再返回到当初转去执行该函数前的地方。
这种转移操作要求在转去执行前要保存现场并记忆执行的地址,转回后要恢复现场,并按原来保存地址继续执行(即就是上下文切换过程)。
因此,函数调用要有一定的时间和空间方面的开销,那么也就将影响程序执行效率。
而宏定义只是在预处理的地方把代码展开,不需要额外的空间和时间方面的开销,所以执行一个宏比调用一个函数更高效。
但是,在学习宏定义的时候,我们发现了宏定义强大的同时也发现了它的不足。
宏定义的缺点表现为以下两点:
<1>容易产生歧义性;
<2>不能访问对象的私有成员。
那么,为了“继承”宏定义的优点,同时“抛弃”它的缺点,也就出现了所谓的内联函数。
【2】如何使用内联函数?
使用内联函数有一个重要的关键字inline。
<1>一般内联函数
示例代码如下:
1 #include<iostream>
2 using namespace std;
3
4 #define MAX(a, b) ((a)>(b)?(a):(b)) //宏定义
5
6 inline int MAXIN(int a, int b) //内联函数
7 {
8 return a > b ? a : b;
9 }
10
11 void main()
12 {
13 int a = 1, b = 0;
14 int max1 = MAX(a++, b); //a被增值2次
15 cout<<"a = "<<a<<" b = "<<b<<" max1 = "<<max1<<endl;
16
17 int c = 10, d = 5;
18 int max2 = MAXIN(c++, d); //c被增值1次
19 cout<<"c = "<<c<<" d = "<<d<<" max1 = "<<max2<<endl;
20 }
21
22 /*
23 a = 3 b = 0 max1 = 2
24 c = 11 d = 5 max1 = 10
25 */
<2>类成员函数作为内联函数
示例代码如下:
1 class TableClass
2 {
3 private:
4 int i, j;
5 public:
6 int add() { return i+j; };
7 inline int dec() { return i-j;}
8 int GetNum();
9 };
10
11 inline int TableClass::GetNum()
12 {
13 return i;
14 }
一般,类的内部定义了函数体的函数,被默认为内联函数,而不管是否有inline关键字。所以,上面其实我们同时定义了三个内联函数。
内联函数在C++类中,应用最广的用途是用来定义存取函数。定义类时我们一般会把数据成员定义成私有的或者保护的,这样,外界就不能直接读写类成员的数据了。
而对于私有或者保护成员的读写就必须使用成员接口函数来进行。如果我们把这些读写成员函数定义成内联函数的话,将会获得比较好的效率。
【3】内联函数使用过程中有那些注意点?
(1)内联函数和宏的区别在于:宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。
(2)内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。
你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。
(3)任何在类的声明部分定义的函数都会被自动的认为是内联函数。
(4)在内联函数中如果有复杂操作将不被内联。如:循环和递归调用。
(5)将简单短小的函数定义为内联函数将会提高效率。
(6)如果想将一个全局函数定义为内联函数可用,inline关键字。
(7)关键字inline必须与函数定义体放在一起才能使函数称为内联函数,仅将inline放在函数声明前面不起作用。
示例代码如下:
1 inline void Foo(int x, int y); // inline仅与函数声明放在一起
2
3 void Foo(int x, int y)
4 {
5 //.....
6 }
7 //而如下风格的函数Foo则成为内联函数:
8 void Foo(int x, int y);
9 inline void Foo(int x, int y) // inline与函数定义体放在一起
10 {
11 //......
12 }
所以说,inline是一种用于实现的关键字,而不是一种用于声明的关键字。
(8)将成员函数的定义体放在类声明之中虽然能带来书写上的方便,但不是一种良好的编程风格。
示例代码如下:
1 //定义在类声明之中的成员函数将自动地成为内联函数,例如
2 //头文件
3 class A
4 {
5 public:
6 // 自动地成为内联函数
7 void Foo(int x, int y) { /*.....*/}
8
9 }
10 //将成员函数的定义体放在类声明之中虽然能带来书写上的方便,但不是一种良好的编程风格,上例应该改成:
11 //头文件
12 class A
13 {
14 public:
15 void Foo(int x, int y);
16 }
17 // 定义文件
18 inline void A::Foo(int x, int y)
19 {
20 //......
21 }
(9)内联关键字inline(内联声明)对于编译器来说只是一个建议,编译器可以选择忽略这个建议。
【4】内联函数可以提高执行效率,为什么不把所有的函数都定义为内联函数?
如果所有的函数都是内联函数,还用得着“内联”这个关键字吗?
内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。
另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。以下情况不宜使用内联:
(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。
类的构造函数和析构函数容易让人误解成使用内联更有效。要当心构造函数和析构函数可能会隐藏一些行为,如“偷偷地”执行了基类或成员对象的构造函数和析构函数。
所以不要随便地将构造函数和析构函数的定义体放在类声明中。
一个好的编译器将会根据函数的定义体,自动的取消不值得的内联(这进一步说明了inline不应该出现在函数的声明中)。
Good Good Study, Day Day Up.
顺序 选择 循环 坚持 总结