第 9章 编译预处理
*1、宏定义
*2、文件包含
3、条件编译预处理命令
编译预处理,在编译源程序之前根据预处理命令对源程序进行的预加工,由编译系统中的预处理程序完成。
格式,以符号,#,开头
如,#include <math.h>
位置,宏定义与文件包含命令一般放在程序的开头(原则上可以放在程序中的任意位置)
作用域,从定义起直到其所在源程序的末尾。
使用预处理命令的好处,(分别介绍)
改进程序设计环境,提高编程效率,易读、易改
9.1 宏定义一,不带参数的宏定义,
常用于将一些有特殊含义的常量定义成符号常量,使程序易读、易改。
例如 3.14代表,2.71代表 e,用 -1表示文件的结束标志等。如果在程序中直接用 3.14,2.71,-1等这些值,会出现以下问题:
1) 数值的含义不明显,影响程序的可读性。
2)若程序中多次使用某一个常量,要修改时必须一一修改,即麻烦又易出错。
用不带参数的宏定义,可以很好的解决此问题。
不带参数的宏定义如符号常量的定义:
#define PI 3.14
#define E 2.71
#define EOF -1
#define NULL 0
#define NUM_STU 30
若程序中有语句:
for(j=o;j<NUM_STU;j++) sum+=score[j];
则编译预处理时,语句中的 NUM_STU用 30来代替:
for(j=0;j<30;j++) sum+=score[j];
不带参宏定义的一般形式:
#define 宏名 宏体宏名一般用大写,以区别于其他变量;
宏体是字符串,可以是常数、常量表达式或语句。
作用:预处理时,进行宏替换,即用宏体代替宏名。
注意,
1)一行只能写一个宏定义语句
2) 宏名的起名规则同标识符的起名规则。
3) 宏定义命令不是语句,末尾不需加 分号(;)
例求圆的周长、面积中用 3.14代替 PI.(略)
宏定义的嵌套:
#define ONE 1
#define TWO ( ONE+ONE)
#define THREE (TWO+ONE)
若原程序中有如下语句:
printf(“%d,%d,%d”,ONE,TWO,THREE); 则预处理后为:
printf(“%d,%d,%d”,1,(1+1),((1+1)+1));
结论,宏替换只是简单的用宏体字符串代替宏名,并不进行计算 。
因为计算是在执行程序时进行的,而宏替换是编译之前进行的。
特别提示,宏体一般用括号括起来,避免宏替换后改变原来的运算顺序。
如,#define N 10
#define M N+4
语句,M*5 替换后为,10+4*5 (与原运算顺序不符 )
二、带参宏定义
常用于实现简单的操作,与函数功能类似 。
例 9.2 求三树种的最大值。其中,求两数中的较大数用宏定义实现。
#define MAX(a,b) (a>b)?(a),(b)
main()
{int x=10,y=20,z=30;
int t;
t=MAX(x,y); /*预处理后,t=(x>y)?(x),(y); */
printf(“\n%d”,MAX(t,z));
/*预处理后,printf(“\n%d”,(t>z)?(t),(z)) ; */
}
带参宏定义的一般形式:
#define 宏名 (参数表 ) 宏体预处理时进行如下宏替换:
用宏体替换宏名的同时,用实参分别替换形参,
而不是用实参的值,即替换时不进行任何计算。
本例中的语句,t=MAX(x,y);
若理解为,t=(10>20)?(10),(20); 则错。
特别提示,使用带参宏定义时,宏体、宏体中的参数要用括号扩起来,以保证替换后不影响原来的运算顺序。
例,#define SQ(a) a*a
若有语句,y=SQ(m+n);
则于处理后变成,y=m+n*m+n;
明显与原题意不符。
应定义为,#define SQ(a) (a)*(a)
三、取消宏定义:
当程序中已不再需要某个宏定义,或需改变某个宏定义时,
可以用取消宏定义命令取消前面定义的宏。如:
#define LEN 100
……
#undef LEN
……
9.2文件包含
文件包含是实现结构化程序设计的重要手段之一
定义:
文件包含是指用 #include 命令将另一指定的源文件包含进当前源程序文件中 #include 命令所处的位置,共同组成一个程序文件,然后对合并后的源文件进行编译、连接,生成一个目标文件。
文件包含命令的一般形式有两种:
格式 1,#include <文件名 >
格式 2,#include "文件名 "
如,#include <stdio.h>
#include "math.h"
#include "c:\tc\fa.c"
其中,文件名中可以包含文件的路径。
图 9.1给出了文件包含命令的预处理过程。
例 9.4 对任意整数开平方的主程序文件
f_m.c:
#include,math.h”
main()
{ double a=10;
printf(“\n %f”,sqrt(a));
}
预处理后,头文件 math.h中的内容即插入到当前文件的 #include 命令处,实现源文件的组合。
文件包含的应用
用法 1),结构化程序设计中,用 include命令包含标准库函数的头文件例,#include <stdio.h>
#include,string.h” 等
用法 2),结构化程序设计中,用 include命令包含用户自己编写的文件,即将多个源程序文件组合成一个源程序。
例 9.5:下面程序实现将两个源程序组合为一个源程序。
其中主程序文件为 file_m.c,被调函数分别在 A盘的
file1.c文件中和 c盘的 my文件夹中的 file2.c文件中。
例 9.5
主程序文件 file_m.c:
#include,a:\file1.c”
#include,c:\my\file2.c”
main()
{int x;float y;
scanf(“%d”,&x);
if(x>0)y=f1(x);
else y=f2(x);
printf(“\nx=%d,y=%f”,x,y);
}
文件 file1.c:
float f1(int a)
{ float z;
z=a*a+1;
return(z);
}
文件 file2.c:
float f2(int b)
{ float m;
m=2.5*b-1;
return(m);
}
例 9.6 将程序设计中常用的一些符号常量定义、带参宏定义、数据结构定义、通用函数的定义等分别编写成一个个单独的文件,存盘待用。以后编程时用 include 命令将其包含进来即可。
避免重复劳动。
如 count.h文件中包含嵌一节中的符号常量的定义:
#define PI 3.14
#define E 2.71
#define NULL 0
#define EOF -1
编程时若需要其中的符号常量,则只要在文件的开头写上以下命令即可:
#include,count.h”
9.3条件编译
条件编译,就是根据不同的编译条件选择源程序的某些程序段进行编译,从而使同一个源程序在不同的编译条件下可以产生适应不同要求的目标代码程序。
条件编译命令
条件编译的应用,
1)便于程序调试 ( *)
2)便于程序移植,拓展 c的编程环境
3)便于编写通用软件主要条件编译命令
1,#if命令的一般形式:
#if 常量表达式程序段 1
[#else
程序段 2]
#endif
功能,预处理时,先计算常量表达式的值,若该值为真,则编译程序段,否则编译程序段 2。
注意问题:因为该命令是在预处理时执行的,所以
#if后的表达式必须是常量。
2,#ifdef 命令的一般形式:
#ifdef 宏名程序段 1
[#else
程序段 2]
#endif
功能:该命令的预处理过程是,如果宏明在此之前已经定义过,则编译程序段 1;否则,编译程序段 2。
3、与 #ifdef类似的命令:
#ifndef 宏名程序段 1
[#else
程序段 2]
#endif
功能:若宏名未定义,则编译程序段;否则编译程序段 2。
条件编译的应用
1)用于调试程序:
在程序调试时经常需要再程序中插入一些输出语句、
暂停程序执行的语句等(这些语句并非程序功能所要求的,
一般称为调试语句),以便了解程序的大致执行过程,并根据运行的中间结果查找错误的位置,分析其原因。但当程序调试结束后,这些调试语句就不再需要了,怎么办呢?
一般的方法是逐一将其删去。这样做不但麻烦,而且容易出错:因为当程序较长时,很难分清调试语句与正常的输出语句(其格式相同,只是所起的作用不同);而且调试语句一定要删除干净,否则也会出现意外。使用条件编译则可以省去删除调试语句的工作,既方便又准确。
例 9.7在调试较大程序时,使用条件编译命令适当加入一些输出语句,测试中间结果,便于寻找程序中的错误及其所在位置。
程序,l9_3_2.c(见下页)
调试时,由于 DEBUG已经被定义,因此调试语句
(输出数组各元素)被编译,运行结果如下 ()
该调试语句的作用是检查数据的输入操作是否有错。
若输入数据正确,而运行结果不正确,则可进一步确定是后面的数据处理出错了。
调试无误后,该调试语句不再需要,只要将宏定义命令该为 "#define DEBUG 0"即可。
#define DEBUG 1
main()
{ int a[3][4],i,j;
float sum,aver;
printf("\n input 4 scores of each student:\n");
for(i=0; i<3; i++)
{printf("\n student %d:",i);
for(j=0;j<4;j++)
scanf("%d",&a[i][j]);
}
#ifdef DEBUG
for(i=0;i<3;i++)
{printf("\n");
for(j=0;j<4;j++)
printf("%6d",a[i][j]);
}
#endif
for(i=0;i<3;i++)
{ sum=0;
for(j=0;j<4;j++) sum+=a[i][j];
printf("\n %d aver:%f",i,sum/4);
}
}
2、条件编译用于程序移植
不同类型的机器给整型(或其他类型)数据分配的空间并不完全相同。为了提高程序的可以移植性,可以与机器有关的语句从一般语句中隔离出来,放入条件编译命令中。
如,关于整型数据的长度的宏定义放在条件编译结构中:
#define PDP_11 1
#ifdef PDP_11
#define INT_SIZE 16
#else
#define INT_SIZE 32
#endif
预处理时,若 PDP_11被定义,则 "#define INT_SIZE 16"将被编译,否则命令 "#define INT_SIZE 32"将被编译。变换机型时,只要改变确定机型的宏定义即可。
编译预处理小结
本章主要介绍了常用的三种预处理命令:宏定义、
文件包含、条件编译。其中,不带参的宏定义常用来定义符号常量,带参宏定义常用来定义一些简单操作;文件包含主要用于两个方面:包含程序中要调用的库函数的头文件,包含用户编写的文件(程序文件、有关符号常量定义的文件、包有关被调函数声明的文件等);条件编译通常用于程序调试和提高程序的可移植性,方法是将调试语句和与机型有关的语句放在条件编译结构中。
练习
1、写出下面程序的运行结果:
#include <stdio.h>
#define PT 5.5
#define S(x) PT*x*x
main()
{int a=1,b=2;
printf("\n %4.1f",S(a+b));
}
供选答案,A,49.5 B,9.5 C,22 D.45.0
2、写运行结果:
#define F(x) x*x
main()
{int a=6,b=2,c;
c=F(a)/F(b);
printf("\n %d",c);
}
供选答案:
A,9 B,6 C,36 D,18