以符号“#”开头的命令,如:
#define N 10;
#define <stdio.h>
这些命令不是C语言本身的组成部分,而是由C编译系统提供的。在通常的编译(词法和语法分析、代码生成、优化等)之前,编译系统要预先对这些命令进行处理,因此称为“编译预处理”命令。编译预处理可以改善程序设计环境,提高编程效率,有助于编写易移植、易调试的程序,是C语言的特色内容之一。
C编译系统的预处理功能主要包括宏的处理、文件包含的处理、条件编译的处理三种。所有预处理命令均以“#”开始,末尾不加分号,以区别于一般的C语句。
宏定义
#include <stdio.h>
#define PI 3.14159
int main()
{
float r,s;
scanf("%f", &r);
s=PI*r*r;
return s;
}
上述程序中“#define PI 3.14159”就是一个宏定义,PI称为宏名,其作用是用标识符PI代替3.14159,称为宏引用。预处理时,程序中的PI将被所定义的串3.14159替换,称为宏展开或宏替换。
宏可分为两种形式:不带参数的宏和带参数的宏。
不带参数的宏定义
不带参数的宏定义,其一般形式为:
#define 标识符 一串字符
其作用是用标识符代替其后一串字符。标识符习惯上使用大写字母,一串字符为任意字符组成的字符序列,除非是表示一个字符串常量,一般不用双引号括起。
说明:
1.宏定义的前后应有空格,以便准确地识别宏名。
2.宏定义是用宏名代替一个字符序列,是一种简单的替换,不进行正确性检查。
例如:
#define PI 3.l4l59
这里数字1误写成了l预处理时也原样代入,编译时宏展开后的语句是错误的,会提示报错。
3.在一串字符中如果出现运算符,要注意宏展开后的结果。通常可以在所定义字符序列的适当位置加括号来避免一些运算上的错误。
例如:
#define PI 2+1.14159
...
s=PI*r*r;
这里宏展开后“s=2+1.14159rr;”,运算结果显然是错误的。应将宏的定义写为“#define PI (2+1.14159)”,这时的运算结果才是正确的。
4.在宏定义的一串字符中,可以引用已定义的宏名,称为嵌套宏定义。
例如:
#include <stdio.h>
#define PI 3.14159
#define R 3.0
#define AREA PI*R*R
int main()
{
printf("area=%f\n",AREA);
return 0;
}
5.若用双引号引起来的字符串常量中出现了与宏名相同的内容,不能将其理解为宏名,更不能展开。
例如:
#define PI 3.14159
void main()
{
printf("PI=%f\n",PI);
return 0;
}
这里前一个PI是字符串的一部分,后一个PI才是宏名。
6.宏名也有其作用范围,即作用域。宏名的作用域是从定义点开始到本远文件结束或用预处理命令#undef取消定义之前。
带参数的宏定义
带参数的宏定义是对不带参数宏定义的扩充,它不是进行简单的字符串替换,而是还要进行参数替换。带参数宏定义的一般形式为:
#define 标识符(形参表) 一串字符
其作用是用标识符代表一串字符。标识符习惯上是用大写字母。形参表为用逗号分隔的若干个标识符。一串字符为任意字符组成的字符序列,其中包含形参中指定的标识符。
宏引用的一般形式为:
宏名(实参表)
其中,参数一般是程序中已声明的变量或表达式。
预处理时,程序中所有的宏引用“宏名(实参表)”替换成对应的一串字符。
例如:
#include<stdio.h>
#define AREA(R) 3.14159*(R)*(R) //定义带参数的宏,R是形参
void main()
{
float r,s;
scanf("%f",&r);
s=AREA(r);
printf("area=%f\n",s);
return 0;
}
1.带参数的宏展开时,是用宏引用中的实参字符串替换宏定义时一串字符中的形参。
上例中将“s=AREA(r);”改为“s=AREA(r+10);”宏展开后“s=3.14159*(r+10)* (r+10);”
如果将
#define AREA(R) 3.14159*(R)*(R)
改为
#define AREA(R) 3.14159*R*R
则语句
“s=AREA(r+10);” 宏展开后“s=3.14159*r+10 *r+10;”将产生错误。
所以定义带参数的宏时,应将一串字符中的参数加上括号。
2.在定义带参数的宏时,宏名与后面的圆括号之间不能有空格,否则将空格之后的内容都将作为代替字符串的一部分。
例如:
#define M(a,b) ((a)>(b)?(a):(b)) //替代部分((a)>(b)?(a):(b))
#define M (a,b) ((a)>(b)?(a):(b)) //替代部分(a,b) ((a)>(b)?(a):(b))
3.宏引用和函数调用形式上相似,但要注意宏引用与函数调用的区别。
- 函数调用是在程序运行时进行的,多次函数调用就多次执行相同的程序段;而宏展开是在编译预处理阶段完成的,只进行简单的字符串代换,多次宏引用就在多处产生C代码。
- 函数的参数要定义类型,而宏不存在类型的概念,它的参数只是一个符号代表。函数调用时,先求实参表达式的值,传递给形参后执行函数,而使用带参数的宏只是进行简单的字符串替换。
- 函数调用要分配内存单元、保留现场、参数传递等,需占用运行时间;而宏引用没有这些过程,不占用运行时间,只占编译时间。
- 对于简单的表达式计算,为了提高程序的执行效率,应采用宏。如果是繁杂的计算,需要较长的C代码,或者为了节省程序代码所占用的内存,则应采用函数调用。
宏函数
宏函数是另一种宏定义的应用,宏函数定义时要在换行处加“\”,不换就不需要,末尾不需要加“\”。
#define PressEnter\
{\
printf("Press Enter...");\
getchar();\
fflush(stdin);\
}
/*#define PressEnter {printf("Press Enter...");getchar();fflush(stdin);与上面的效果是一样的*/
这是一个实现暂停程序,按回车键继续的宏函数定义大家可以通过这篇文章中C语言实现的代码去具体了解它在程序中的使用
宏函数一般用于定义一个在程序中运用重复次数较多的函数,可以简化代码,减少运行时间。但是由于宏函数是直接引用的,不会对函数进行验证,还有没有对应的返回值。