第 12 章 编 译 预 处 理第 12章 编译预处理
12.1 宏定义
12.2 文件包含
12.3 条件编译第 12 章 编 译 预 处 理
12.1 宏定义
12.1.1 不带参数的宏定义定义形式:
# define 宏名宏名是一自定义标识符,宏体是一字符串,在程序中可用宏名代表宏体 。
第 12 章 编 译 预 处 理例如,用 PI代表 3.1415926,用 PR代表 printf,宏定义如下:
(1) # define PI 3.1415926
(2) # definee PR printf
程序中凡是出现 3.1415926的地方,都可以以 PI出现,凡是出现 printf的地方,都可以以 PR出现。
第 12 章 编 译 预 处 理例 12-1 利用宏定义求圆的周长和面积 。
/*程序 12 - 1,利用宏定义求圆的周长和面积 */
# define PI 3.1415926
# define R 1.0
main( )
{float l,s;
l=2.0*PI*R;
s=PI*R*R;
printf( ″周长 =%f,面积 =%f\n″,l,s) ;
}
第 12 章 编 译 预 处 理上面程序在编译前将进行宏展开,宏展开以后变为:
main( )
{float l,s;
l=2.0*3.1415926*1.0;
s=3.1415926*1.0*1.0;
printf( ″周长 =%f,面积 =%f\n″,l,s) ;
}
第 12 章 编 译 预 处 理
(1) 宏名一般习惯用大写字母表示,以便与变量名相区别 。 当然可以使用小写字母 。
(2) 宏定义是用宏名简单替换宏体,也就是作简单的置换,不作语法检查,出现错误也不会报告,只有在宏展开后编译时才会报告错误 。
(3) 程序中双引号中与宏名相同的内容不被替换 。
(4) 宏展开后源程序将变长 。
(5) 使用宏名,可以减少程序中重复书写某些宏体的工作量 。
(6) 宏定义一般放在程序的开头,宏名的有效范围为定义位置到文件结束 。
(7) 宏定义可以嵌套,后定义的宏可使用已定义的宏。
第 12 章 编 译 预 处 理
12.1.2 带参数的宏定义定义形式:
# define 宏名 ( 参数表 )
带参数的宏展开时要从左至右进行参数的简单替换,
使用起来较无参数宏定义复杂 。
例如:
(1) # define S( n) ( n) *( n) *( n)
(2) # define T( n) 1/( n)
第一个式子代表 n的立方,第二个式子代表 n的倒数。
第 12 章 编 译 预 处 理说明:
(1) 列出的参数必须在宏体中用到 。
(2) 带参数的宏展开只是将实参简单替换形参 。 当实参中含有运算,宏展开可能会出问题 。
如有宏定义:
# define S( n) n*n*n
若将形参 n 用实参 a+b 替换,S ( a+b ) 会变成
a+b*a+b*a+b,显然与原意不相符 。
如将宏定义改成:
# define S( n) ( n) *( n) *( n)
S( a+b) 为 ( a+b) *( a+b) *( a+b),与原意相符 。
因此,在带参的宏定义中,参数应加括号来描述。
第 12 章 编 译 预 处 理
(3) 宏定义时,在宏名与带参数的括号之间不应加空格,否则将空格以后的部分都作为宏体 。 例如:
# define S (n)(n)*(n)*(n)
S被认为是不带参数的宏名,它代表
( n) ( n) *( n) *( n) 这样的宏体 。
(4) 如在宏体中的参数前加上,#,,则在宏展开后该实参前后会加上双引号,变成字符串 。
第 12 章 编 译 预 处 理例 12 – 2
/*程序 12 [CD*2] 2,利用带参数的宏定义求圆的周长和面积 */
# define R 1.0
# define PI 3.14159
# define C( r) 2*PI*( r)
# define S( r) PI*( r) *( r)
main( )
{printf( ″周长 =%f,面积 =%f\n″,C( R),S( R)); }
第 12 章 编 译 预 处 理上面程序宏展开后如下:
main( )
{printf( ″周长 =%f,面积 =%f\n″,2*3.14159* 1.0,
3.14159* 1.0* 1.0); }
第 12 章 编 译 预 处 理带参数的宏又称为函数宏,函数宏不是函数:
(1) 函数调用要求形参和实参类型一致,如实参是表达式,必须先计算出值;宏名无类型,宏体也无类型,宏展开只进行参数的简单替换 。
(2) 函数调用是在程序运行时处理,分配临时的内存单元,有返回值;而宏展开是在编译前进行的,展开时并不分配内存单元,不进行值的传递,没有返回值的概念 。
(3) 调用函数得到一个返回值,而用宏则可以设法得到几个值 。
(4) 宏展开将使源程序变长,而函数调用不使源程序变长
(5) 宏替换不占运行时间,只占编译时间,而函数调用则占运行时间 ( 分配单元,保留现场,值传递,返回 ) 。
第 12 章 编 译 预 处 理
12.1.3 预定义宏预定义宏,由系统提供,宏名开始和结尾均为下划线 。
-TURBOC-,当前 TURBOC的版本号 。
-LINE-,程序行号,第一行定义为 1。
-FILE-,源程序文件名 。
-DATE-,当前编译日期 。
-TIME-,当前编译时间。
第 12 章 编 译 预 处 理
12.1.4 取消宏定义形式,# undef
取消前面定义的宏名,使宏名局部化,取消以后不能再使用。
第 12 章 编 译 预 处 理
12.2 文件包含文件包含是指一个 C语言源程序中将另一个 C语言源程序包含进来,通过 include预处理指令实现 。
一般形式:
# include″被包含文件名 ″
或 # include <被包含文件名 >
第 12 章 编 译 预 处 理
(1) 被包含的文件一般指定为头文件 ( *.h),也可为 C程序等文件 。
(2) 一个 include指令只能指定一个被包含文件,如果要包含 n个文件,则要用到 n条 include指令 。
(3) 不能包含 OBJ文件。 文件包含是在编译前进行处理,不是在连接时进行处理。
第 12 章 编 译 预 处 理
(4) 当文件名用双引号括起来时,系统先在当前目录中寻找包含的文件,若找不到,再在系统指定的标准方式检索其它目录 。 而用尖括号时,系统直接按指定的标准方式检索 。
一般系统提供的头文件,用尖括号 。 自定义的文件,用双引号 。
(5) 被包含文件与当前文件,在预编译后变成同一个文件,而非两个文件 。
(6) 文件包含可以嵌套,但必须按顺序包含。
第 12 章 编 译 预 处 理
12.3 条件编译
1,# ifdef
程序段 1
# else
程序段 2
# endif
第 12 章 编 译 预 处 理当标识符已经被定义过,则对程序段 1 进行编译,
否则对程序段 2进行编译。
和 if-else语句一样,# else子句可以缺省,缺省后的形式为:
# ifdef
程序段 1
# endif
另可增加# elif子句构成嵌套。
第 12 章 编 译 预 处 理
2,# ifndef 标识符程序段 1
# else
程序段 2
# endif
与第一种形式正好相反,当标识符未被定义过时,
则对程序段 1进行编译,否则,对程序段 2进行编译 。
第 12 章 编 译 预 处 理
3,# if
程序段 1
# else
程序段 2
# endif
当表达式之值为真 ( 非零 ) 时,编译程序段 1,否则编译程序段 2。
例如:
# if DEBUG==1
# endif
第 12 章 编 译 预 处 理例 12 — 3 根据给定的条件编译,使给定的字符串以小写字母或大写字母形式输出 。
/*程序 12— 3,条件编译实例 */
# define LETTER 1
main( )
{int i=0;
char *str=″HuNanComputer″;
char c;
while(( c=str[ i])! =′\0′)
第 12 章 编 译 预 处 理
{# if LETTER
if( c>=′a′&& c<=′z′
c-=32;
# else
if( c>=′A′&& c<=′Z′
c+=32;
# endif;
printf( ″c%″,c);
}
}
运行结果,HUNANCOMPUTER
如将 LETTER定义为 0,将编译另一条 if语句,运行结果将变为,hunancomputer。
第 12 章 编 译 预 处 理例 12 — 4 假设某程序使用与国别有关的信息,每个国家的信息存于一头文件中,采用条件编译识别与国别有关的信息 。
# if CHINA==1
# include ″china.h″
# elif USA==1
# include ″usa.h″
# elif ENGLAND==1
# include ″england.h″
# elif FRANCE==1
# include ″france.h″
# else
# include ″italy.h″
# endif