前言
在程序设计中为什么要使用函数,首先来看下函数的定义:许多程序设计语言中,可以将一段经常需要使用的代码封装起来,在需要使用时可以直接调用,所以,函数也可以说是许多代码的集合。也就是说把实现某一功能的代码进行封装起来形成函数。
函数的类型有很多,这里主要比较普通函数,宏定义函数以及内联函数。
1. 普通函数
普通函数是相对常见的函数,将实现某一功能的代码封装成普通函数,同时也会付出相应的代价。下面代价的分析引自这篇文章C语言宏定义和宏定义函数。
1.函数调用会带来额外的开销,它需要开辟一片栈空间,记录返回地址,将形参压栈,从函数返回还要释放堆栈(一个程序被分配的堆栈空间是有限的,否则堆栈空间会耗竭)。这种开销不仅会降低代码效率,而且代码量也会大大增加;
2.函数的参数必须被声明为一种特定的类型,所以它只能在类型合适的表达式上使用,我们如果要比较两个浮点型的大小,就不得不再写一个专门针对浮点型的比较函数。还有一些任务根本无法用函数实现,比如参数类型没法作为参数传递给函数,但是用宏定义却很好实现。
2. 宏定义函数
首先看宏定义函数长什么样:
#include<iostream>
using namespace std;
#define MAX( a, b) ( (a) > (b)?(a) : (b) )
int main()
{
int a=MAX(2,3);
cout<<a<<endl;//3
return 0;
}
使用宏定义函数则在代码规模和速度方面都比普通函数更胜一筹,在宏函数中并没有定义变量的类型,所以这个宏定义可以用于整形、长整形、单浮点型、双浮点型以及其他任何可以用“>”操作符比较值大小的类型,也就是说,宏是与类型无关的。和使用函数相比,使用宏的不利之处在于每次使用宏时,一份宏定义代码的拷贝都会插入到程序中,除非宏非常短,否则使用宏会大幅度增加程序的长度。(内联函数也有这个缺点)
需要注意的是:宏是通过文本替换方式实现的,定义体里的参数被调用的实际参数替换,得到的结果再作为文本去替换原来的调用段。这种做法与函数不同,这种傻傻的替换方式,常给人带来一些麻烦。假如square是个函数,表达式:1/ square(x)的结果复合我们的意图,将1除以square(x)的结果。
如果采用宏定义函数的方法:
#define square(x) (x)*(x)
1/ square(x)的结果是:1/(x)*(x),这显然不符合我们的意图。
所以需要这样定义:
#define square(x) ((x)*(x))
当表达式的运算相对复杂时,从这里也可以看出来使用宏函数的缺点。
#define REGS_FOREACH(_) _(X) _(Y)
#define RUN_LOGIC X1 = !X && Y; \
Y1 = !X && !Y;
#define DEFINE(X) static int X, X##1;
#define UPDATE(X) X = X##1;
#define PRINT(X) printf(#X"=%d;",X);
int main() {
REGS_FOREACH(DEFINE);
while (1) { // clock
RUN_LOGIC;
REGS_FOREACH(PRINT);
REGS_FOREACH (UPDATE);
putchar('\n'); sleep(1);
}
return 0;
}
替换后的结果:
int main() {
static int X, X1; static int Y, Y1;;
while (1) {
X1 = !X && Y; Y1 = !X && !Y;;
printf("X" "%d;",X); printf("Y" " = %d;", Y);;
X = X1; Y = Y1;;
putchar('\n'); sleep(1);
}
return 0;
}
可以看到宏函数就是简单的宏替换策略,但确实又有函数传参的效果,用宏去解决问题还是一种函数的思想。
可以通过gcc的预编译宏展开gcc -E main.c
来查看宏展开后的代码。
3. 内联函数
内联函数由inline关键字定义,内联函数的作用其实和宏函数的作用差不多,都是在函数调用的位置,将函数的内容展开,避免普通函数调用效率低的缺点,同时也具有普通函数实现的优点,但如果执行函数体内代码的时间,相比于函数调用的时间较大,那么效率的收获也会很少。
关于内联函数的说明参见这篇文章。
关于内联函数为什么在头文件定义,这篇文章也给出了很好的解释。
inline函数的定义一般放在头文件中,其他源文件要用的时候,直接把定义了inline函数的头文件include进来即可。如果在源文件中定义,在头文件中声明,则会变成为非内联函数的调用,因为每个源文件都是独立编译的,编译完之后只能按照普通函数一样调用。
也就是说如果在源文件中定义,在头文件中声明,那么其他源文件要调用这个函数,只有在链接的时候实现,那么相当于普通函数的调用。