一、预处理指令的概念及分类
1.基本概念
以“#”号开头指令都称为【预处理指令】。如包含命令#include等。在源程序中这些命令都放在函数之外。而且一般都放在源文件的前面,他们称为【预处理部分】
所谓预处理是指在进行编译的第一道扫描(词法扫描和语法扫描)之前所做的工作,预处理是C语言的一个重要功能,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分做处理,处理完毕自动进入对源程序的编译。
C语言提供了多种预处理功能。如宏定义,文件包含,条件编译等。合理地使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。
二、宏的概念及无参宏定义方法
1.宏的概念
被定义为“宏”的标识符称为“宏名”,在编译预处理时,都用宏定义中的字符串取代换,这称为“宏代换”或“宏展开”
例如 #define Row 10 //此处Row就是一个宏
宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。在C语言中,“宏”分为【有参宏】和【无参宏】
无参宏的宏名不带参数,其定义一般形式为:
#define标识符字符串
其中"#"表示这是一掉预处理命令。凡是以“#”开头的均为预处理命令。“define”为宏定义命令。“标识符”位所定义的宏名。“字符串”可以使常熟,表达式、格式串等
例如:
//定义一个宏
#define PI 3.1415926
#define Area PI*4
#include
int main(int argc, const char * argv[])
{
printf("area = %0.4f",Area);//预处理时,Area会被替换成3.1415926*4
return 0;
}
【注意】
1、宏定义式一个预处理指令,结尾不需要加“;”
2、宏定义替换问题
例如:
//定义一个宏
#define PI 3.1415926
#define Sum a*b+2*b
#include
int main(int argc, const char * argv[])
{
int a = 2,b =3;
printf("a2*Sum+ 3*Sum =%0d\n",2*Sum+ Sum);//预处理时,2*Sum+ Sum会被替换成2*a*b+2*b+a*b+2*b = 30而不是2*(a*b+2*b) +(a*b+2*b) = 36.
printf("2*a*b+2*b+a*b+2*b = %d\n", 2*a*b+2*b+a*b+2*b);
printf("2*(a*b+2*b) +(a*b+2*b) = %d", 2*(a*b+2*b) +(a*b+2*b));
return 0;
}
打印结果:
a2*Sum+ 3*Sum = 30
2*a*b+2*b+a*b+2*b = 30
2*(a*b+2*b) +(a*b+2*b) = 36
三、有参宏的定义和使用方法
1、有参宏的定义方法
C语言允许宏带有参数,在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数,对带参数的宏,在调用中,不仅要宏展开,而且要用实参取代替形参。
带参宏定义的一般形式为:
define 宏名(参数表) 字符串
例如:
//定义一个带参宏
#define Add(x,y) x+y
#include
int main(int argc, const char * argv[])
{
printf("x + y = %d", Add(10, 20));
return 0;
}
打印结果:
x + y = 30
2、注意事项
1)宏的形参之间可以出现空格,但是【宏名】和【形参】之间不能出现空格
例如:
#define Add (x,y) x+y //报错
#define Sub(x, y) x + y //不报错
2)在带参宏定义中,形式蚕食不分配内存单元,因此不必做类型定义,而宏调用中的实参有具体值,要用他们去代换形参,因此必须做类型说明。这是与函数中的情况不同的。在函数中,形参和实参是两个不同的量,各有自己的作用域,调用时要把实参值赋予形参,进行“值传递”,而带参宏中,只是符号代表,不存在值传递的问题。
3)在宏定义中的形参是标识符,而宏调用的实参可以是表达式
4)在宏定义中,【字符串内的形参通常要用括号括起来】以避免出错,
例如:
#define Mul(x,y) x*y
#include
int main(int argc, const char * argv[])
{
int a = 2, b = 3;
printf("x + y = %d", Mul(a + b, a*b));
return 0;
}
打印结果:
x + y = 20
期待结果:(2+3)*(2*3) =30,但是宏展开的时候替换成了2+3*2*3 = 20
因此【字符串内的形参通常要用括号括起来】以避免出错,
如下:
#define Mul(x,y) (x)*(y)
#include
int main(int argc, const char * argv[])
{
int a = 2, b = 3;
printf("x + y = %d", Mul(a + b, a*b));
return 0;
}
打印结果:
x + y = 30
四、typedef和#define的区别
1、typedef和#define的区别
例如:
//定义一个宏
#defineMYINT int*
//给int型起一个别名
typedef int* MYINT2;
#include
int main(int argc, const char * argv[])
{
//使用宏定义变量
int num = 20;
MYINT a, b;//a是一个指针,b是一个普通变量,在宏展开的时候替换为int *a,b
a = #
printf("a = %d\n",*a);
//b =#
//printf("b = %d\n",*b);//报错
b = num;
printf("b = %d\n",b);//打印结果b = 20
//使用别名定义变量,不是简单的替换字符串,而是给类型起了个别名
MYINT2 c, d;
c = #
//d = num; //报错
d = # //不会报错
printf("c = %d",*c);
return 0;
}
五、条件编译的概念及优点
1.为什么要使用条件编译?
1)按不同的条件取编译不同的程序部分,因而产生不同的目标代码文件,有利于程序的一直和调试
2)条件编译当然也可以用条件语句来实现,但是用条件语句将会对整个源程序进行编译,生成的目标代码程序很长,而采用了条件编译,则根据条件只编译其中的程序段1或程序段4,生成的目标程序较短。
2.条件编译
发生在预处理阶段,在编译前做的事情
【核心】:根据条件编译指定的代码
条件不同,编译的部分也不同,生成的目标文件(.o)大小也不同
#define score 89
#include
int main(int argc, const char * argv[])
{
/* intscore = 89;
//用if条件语句实现,编译后(.o)文件的大小是1,172 bytes
score /=10;
if (score < 6) {
printf("E");
} else if (score < 7) {
printf("D");
}
else if (score < 8) {
printf("C");
}
else if (score < 9) {
printf("B");
}
else{
printf("A");
}*/
//使用条件编译,(.o)文件 836 bytes
#if score < 6 //此处不能使用普通变量,要用宏定义
printf("E");
#elif score < 7
printf("D");
#elif score < 8
printf("C");
#elif score < 9
printf("B");
#else
printf("A");
#endif //这个必须有,有#if配对
return 0;
}
3、#ifdef用来判断某个宏是否定义
例如:
int a = 0;
#ifdef AMOS//检测宏DEBUG1是否定义,若果定义了,a=10,对应的#ifndef,如果没有定义
a = 10;
#else
a = 100;
#endif
printf("a = %d\n",a);
4、使用条件编译调试程序
#define AMOS 0
#include
//使用条件编译调试程序
#if AMOS == 1
//显示调试信息
#define Log(format,...) printf(format,## __VA_ARGS__);
#else
//不显示调试信息
#define Log(format,...)
#endif
int main(int argc, const char * argv[])
{
int a = 0;
#ifdef DEBUG1 //检测宏DEBUG1是否定义,若果定义了,a=10,对应的#ifndef,如果没有定义
a = 10;
#else
a = 100;
#endif
Log("------------------>a = %d\n", a);
return 0;
}
打印结果:
当AMOS = 1时:
------------------>a= 100
当AMOS = 0时:
没有打印任何信息