预处理是指在进行编译的第一遍扫描(语法扫描和语法分析)之前所做的工作。预处理是C语言的一个重要功能,它由预处理器负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对程序中的预处理部分作处理,处理完毕后自动进入对源程序的编译。C语言提供了多种预处理功能,如宏定义、文件包含、条件编译等。
一.宏
C语言允许用一个表示法来表示一个字符串,称为“宏”。被定义为“宏”的表示符称为“宏名”,对程序中所有出现的“宏名”,都用宏定义中的字符串去替换,这称为“宏替换”和“宏展开”。
1.无参数宏
定义的一般形式为: #define 标示符 字符串
例:
#define ADD (a+b) //定义一个宏 不用加分号 如果有分号则一起替换
int main(int argc, char *argv[])
{
int a = 5, b = 3, c;
c = 2*ADD; //宏展开为 c=2*(a+b); 注意括号不能少
printf("c=%d\n",c); //输出 c=16
}
宏定义使用宏明表示一个字符串,在宏展开时又以该字符串取代宏名,这是一种简单的替换,字符串中可以包含任何字符,可以是常数,也可以使表达式,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的程序是发现。
宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束,要终止其作用域可以使用#undef命令
例:
#define PI 3.14
int main(int argc, char *argv[])
{
double pi = PI;
printf("PI=%f\n",PI);
}
#undef PI
void function()
{
double pi = PI; //错误 已经取消宏定义
printf("PI=%f\n",PI);
}
宏明在源程序中若用引号括起来,则预处理程序不对其做宏替换
宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏明。在宏展开时由预处理程序层层替换。
例:
#define PI 3.14
#define N 2*PI*r
int main(int argc, char *argv[])
{
double r = 5;
double n = N; //展开为 n=2*3.14*r
printf("n=%f",n);
}
注意#define和typedef的区别,宏是预处理完成的,typedef是在编译时处理的。typedef不是简单的替换,而是对类型说明符重命名。
例:
#define INTPTR1 int*
typedef int* INTPTR2;
int main(int argc, char *argv[])
{
INTPTR1 a, b; //a是指针变量 b是整型变量
INTPTR2 x, y; //x y都是指针变量
int m = 10;
a = &m;
b = &m; //错误 b是整型变量
x = &m;
y = &m;
}
二.带参数的宏
C语言允许带有参数的宏。在宏定义中的参数称为形式参数,在宏调用中的参数称为形式参数,在宏调用中的参数称为实际参数。对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代替形参。
带参宏定义的一般形式为: #define 宏名(形参表) 字符串 调用形式为: 宏名(实参表)
例:
#define ADD(a,b) a+b
int main(int argc, char *argv[])
{
int a = 4, b = 5, c;
c = ADD(a,b); //宏展开为 c=a+b
printf("c=%d\n",c); //输出 c=9
}
在带参宏定义中,形参不分配内存单元,因此不必作类型定义。而宏调用中的实参有具体的值。要用他们去替换形参因此必须做类型说明。这是与函数中的情况不同的。在函数汇中,形参和实参是两个不同的量,各有自己的作用域,调用时要把实参值赋予形参,进行“值传递”。而带参宏中,只是符合代换,不存在值传递问题。
在宏定义的形参标识符,而宏调用可以使表达式
例
#define MUT(x) (x)*(x)
int main(int argc, char *argv[])
{
int a = 4, b;
b = MUT(a+2); //宏展开 (a+2)*(a+2) 注意宏定义中参数的括号,如果没括号,则展开为 a+2*a+2,结果完全不同
printf("b=%d\n",b); //输出 b=36 如果宏定定字符串中没有括号 则结果为10
}
带参宏和带参函数很相似,但有本质不同,除了上面谈到的几点之外,把同一表达式用函数处理与用宏处理的结果有可能是不同的
例:(函数调用)
int MUT(int x)
{
return (x)*(x);
}
int main(int argc, char *argv[])
{
int i = 1;
while(i<=5)
printf("%d\n",MUT(i++));
}
例(带参宏)
#define MUT(x) ((x)*(x))
int main(int argc, char *argv[])
{
int i = 1;
while(i<=5)
printf("%d\n",MUT(i++));
}
函数调用中,i作为函数参数传入到函数内,计算完返回 i加1进入下次循环,共执行5次循环 带参宏中,宏展开为(i++)*(i++) 计算完i做两次自加操作,只循环三次
宏定义也可以来定义多个语句,在宏调用时,把这些语句又代换到源程序内。
例:
#define DEF int a = 2; int b = 3; int c = a + 2;
int main(int argc, char *argv[])
{
DEF;
printf("a=%d b=%d c=%d",a,b,c); //输出 a=2 b=3 c=40
}
预处理程序提供了条件编译的功能,可以按不同的条件去编译不同的程序部分,因而产品不同的目标代码文件。这对程序的移植和调试是很有用的,条件编译有三种形式。
第一种:
#ifdef 标识符
程序段1
#else
程序段2
#endif
它的功能是,如果标识符已经被#define命令定义过则对程序段1进行编译,否则对程序段2进行编译。如果没有程序段2,本格式中的#else可以没有,即可以写为:
#ifdef 标识符
程序段1
#elseif
第二种:
#ifndef 标识符
程序段1
#else
程序段2
#endif
它的功能和第一种正相反,如果标识符未被#define命令定义过则对程序段1进行编译,否则对程序段2进行编译
此功能多用于避免头文件多重包含
第三种:
#if 常量表达式
程序段1
#else
程序段2
#endif
它的功能是,如果常量表达式的值为真(非0),则对程序段1进行编译,否则对程序段2进行编译。