概述
宏函数,内联函数和static函数是c++中不同于普通函数定义的使用其他方式定义的函数,这三种函数有各自的特点和不同的适用场景,我们将在这篇文章中一一说明。先来看三者的定义。
定义
宏函数:一般说来,宏是一种规则或模式,或称语法替换 ,用于说明某一特定输入(通常是字符串)如何根据预定义的规则转换成对应的输出(通常也是字符串)。这种替换在预编译时进行,称作宏展开。宏函数归根结底还是一种宏定义,比普通的宏定义要复杂一些,通常是一组实现特定功能的语句的组合。
内联函数:内联函数(有时称作在线函数或编译时期展开函数)是一种编程语言结构,用来建议编译器对一些特殊函数进行内联扩展(有时称作在线扩展);也就是说建议编译器将指定的函数体插入并取代每一处调用该函数的地方(上下文),从而节省了每次调用函数带来的额外时间开支。但注意这里是建议,并非确定。
static函数:声明了static关键字的函数被称为静态函数,它的作用域为本文件可见,不参与链接的过程,产生一个local的符号,调用static函数不依赖于对象,static函数中不能访问非static的成员变量。
来看一下三者在代码中不同的写法:
#include<iostream>
using namespace std;
//宏函数
#define MAX(a,b){cout<<(a > b ? a : b)<<endl;}
//内联函数
inline void Max(int a, int b)
{
cout<< (a > b ? a : b) <<endl;
}
//静态函数
static void max(int a, int b)
{
cout << (a > b ? a : b) << endl;
}
int main()
{
int a = 10;
int b = 20;
cout << (a > b ? a : b)<<endl;
MAX(10, 20);
Max(10, 20);
max(10, 20);
return 0;
}
三者的区别
宏函数和内联函数的区别:1、宏函数在预编译时进行替换,内联函数在编译时进行替换;2、宏函数没有类型检查,只进行简单的字符串替换工作,而内联函数有类型检查,比宏函数更加安全;3、内联函数是对编译器的一种建议,具体是否替换要根据两种处理方式的效率来定;4、宏函数用#define来定义,而内联函数用inline关键字定义;5、两者都没有函数堆栈的开辟和回退。
内联函数和static函数的区别:1、内联函数在编译时进行替换,没有栈帧的开辟和回退,static函数有正常的函数栈帧开辟和回退的过程;2、static函数产生一个local的符号,而内联函数不产生符号;3、类内定义的static函数的调用不依赖于对象,而内联函数的调用依赖于对象;4内联函数用inline关键字定义,static函数使用static关键字定义。
我们查看一下上面代码块程序的反汇编代码,来确定它们每个函数的使用情况是否和我们所说的相符合,面这两段汇编代码分别是直接使用语句cout<<(a>b?a:b)<<endl;和使用宏函数时的汇编,大家会发现这两段汇编几乎相同,也证实了宏函数是直接进行字符串的简单替换这一说法。而在下一个代码块中的状况就有所不同了。
//直接使用语句cout << (a > b ? a : b)<<endl;
00BD1942 mov esi,esp
00BD1944 push offset std::endl<char,std::char_traits<char> > (0BD12ADh)
00BD1949 mov edi,esp
00BD194B mov eax,dword ptr [ebp-0DCh]
00BD1951 push eax
00BD1952 mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0C000D8h)]
00BD1958 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0C000A0h)]
00BD195E cmp edi,esp
cout << (a > b ? a : b)<<endl;
00BD1960 call __RTC_CheckEsp (0BD128Ah)
00BD1965 mov ecx,eax
00BD1967 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0C000A4h)]
00BD196D cmp esi,esp
00BD196F call __RTC_CheckEsp (0BD128Ah)
//调用宏函数
MAX(10, 20);
00BD1974 mov esi,esp
00BD1976 push offset std::endl<char,std::char_traits<char> > (0BD12ADh)
00BD197B mov edi,esp
00BD197D push 14h
00BD197F mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0C000D8h)]
00BD1985 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0C000A0h)]
00BD198B cmp edi,esp
00BD198D call __RTC_CheckEsp (0BD128Ah)
00BD1992 mov ecx,eax
00BD1994 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0C000A4h)]
00BD199A cmp esi,esp
00BD199C call __RTC_CheckEsp (0BD128Ah)
下面是内联函数和static函数的反汇编,细心的同学可能会发出疑问:不是说内联函数会在调用点展开代码吗,为什么还会去使用call指令调用函数?
这其实和内联函数的性质有关,我们在使用vs或者其他编译器时,程序分为release版本(发行版本)和debug版本(调试版本),而默认情况下都是在debug版本。那么为了方便程序员对于程序的查错和优化,debug版本下的内联函数也会有正常的堆栈帧开辟和回退的过程。
//内联函数
Max(10, 20);
00BD19A1 push 14h
00BD19A3 push 0Ah
00BD19A5 call Max (0BD144Ch)
00BD19AA add esp,8
//static函数
max(10, 20);
00BD19AD push 14h
00BD19AF push 0Ah
00BD19B1 call max (0BD2140h)
00BD19B6 add esp,8
应用场景
这三种函数由于其自身不同的特性,产生了不同的应用场景,只有在各自适用的场景下每个函数才能发挥它的最大功能,提高代码的效率。下面我们就来看一下各自的应用场景。
宏函数的应用场景:宏函数多用于执行简单的计算,比如上文的例子中求两者最大值。那为什么不用函数来完成这个任务呢?首先,用于调用和从函数返回的代码很可能比实际这个小型计算工作的代码更大,因此使用宏比使用函数在程序的规模和速度方面都更胜一筹;更为重要的一点是,函数的参数必须声明为一种特定的类型(不考虑重载),所以它只能在类型合适的表达式上使用。而宏可以用于整型,长整型,单浮点型,双浮点型等的比较。宏是类型与类型无关的。
内联函数的应用场景:它和宏函数的应用场景相似,适用于代码逻辑简单,而且对执行速度有严格要求的地方。但是注意:有循环和递归的时候不宜适用内联函数,因为往往在这种时候如果将代码展开那么将会是非常庞大的一段代码,消耗更大的内存空间;另外当一个函数被调用多次的时候也不宜将其处理成内联函数。
static函数的应用场景:static函数最主要的特点就是仅本文件可见和调用不依赖对象。那么基于这两个特点我们可以得出它的应用场景大致如下:作为内部使用的函数,即仅在本文件内使用,不需要将接口暴露出去的时候,可以将其定义为static函数,增加代码的安全性;当在类内定义时,所有类共享该函数,可以进行对象数值的统计或者其他公共的一些操作时使用。