kj-第8章预处理命令课件.ppt
第第8章章 预处理命令预处理命令l本章概述 l本章的学习目标l主要内容l本章概述l本章介绍宏定义的两种形式,介本章介绍宏定义的两种形式,介绍文件包含的使用方法绍文件包含的使用方法,介绍条件介绍条件编译的概念。编译的概念。第第8章章 预处理命令预处理命令本章的学习目标本章的学习目标: 本章教学目的:本章教学目的: 掌握宏定义的两种形式,掌握文掌握宏定义的两种形式,掌握文件包含的使用方法了解条件编译的概念。件包含的使用方法了解条件编译的概念。 本章教学重点:本章教学重点: 宏定义的两种形式,文件包含的宏定义的两种形式,文件包含的使用方法。使用方法。 本章教学难点:本章教学难点: 带参数的宏定义。带参数的宏定义。第第8章章 预处理命令预处理命令第第8章章 预处理命令预处理命令l8.1 宏定义宏定义 l8.2 “文件包含文件包含”处理处理 l8.3 条件编译条件编译 l8.4 本章小结本章小结主要内容主要内容: 编译预处理是指一些行首以编译预处理是指一些行首以# #开头的特殊语句,必须在对开头的特殊语句,必须在对程序进行通常的编译之前,先对程序中这些特殊的命令进行程序进行通常的编译之前,先对程序中这些特殊的命令进行“预处理预处理”,即根据预处理命令对程序作相应的处理(例如,即根据预处理命令对程序作相应的处理(例如,若程序中用若程序中用# define# define命令定义了一个符号常量命令定义了一个符号常量A A,则在预处理,则在预处理时将程序中所有的时将程序中所有的A A都置换为指定的字符串)。经过预处理后都置换为指定的字符串)。经过预处理后程序不再包括预处理命令了,最后再由编译程序对预处理后程序不再包括预处理命令了,最后再由编译程序对预处理后的源程序进行通常的编译处理,得到可供执行的目标代码。的源程序进行通常的编译处理,得到可供执行的目标代码。C C语言与其它高级语言的一个重要区别是可以使用预处理命令语言与其它高级语言的一个重要区别是可以使用预处理命令和具有预处理的功能。和具有预处理的功能。第8章 预处理命令C提供的预处理功能主要有以下三种:提供的预处理功能主要有以下三种: 宏定义、文件包含和条件编译宏定义、文件包含和条件编译它们分别用宏定义命令、文件包含命令和条件编译命令来实现。为了与一般C语句相区别,这些命令以符号“”开头。 8.1 宏定义 宏定义指的是用宏定义指的是用#define定义的命令行,有不带定义的命令行,有不带参数和带参数两种形式。参数和带参数两种形式。 8.1.1 不带参数的宏定义不带参数的宏定义不带参数的宏定义的一般形式为:不带参数的宏定义的一般形式为: # define # define 标识符标识符 字符串字符串含义是用指定的宏名(即标识符)来代表其后字符串。含义是用指定的宏名(即标识符)来代表其后字符串。 例如例如: #define SIZE 10000#define SIZE 10000 #define PI 3.1415926 #define PI 3.1415926 #define FORMAT “%d#define FORMAT “%d,%d%d,%dn”%dn”作用是指用标识符作用是指用标识符SIZESIZE来代替字符串来代替字符串“1000010000”,用标识符用标识符PIPI来代替字符串来代替字符串“3.14159263.1415926”,用标识符用标识符FORMATFORMAT来代替字符串来代替字符串“ “%d “%d,%d%d,%dn” %dn” ”, 在编译预处理时,将程序中在该命令以后出现的所有的在编译预处理时,将程序中在该命令以后出现的所有的SIZESIZE用用1000010000代替、代替、PIPI用用3.14159263.1415926代替、代替、FORMATFORMAT用用“%d“%d,%d%d,%dn”%dn”代代替。这种方法使用户能以一个简单的名字代替一个长的字符串,可替。这种方法使用户能以一个简单的名字代替一个长的字符串,可以减小重复编程工作量,而且不容易出错。以减小重复编程工作量,而且不容易出错。 把定义时所用的标识符称为把定义时所用的标识符称为“宏名宏名”,即,即SIZESIZE、PIPI和和FORMATFORMAT都是都是宏名。在预编译时将宏名替换成字符串的过程称为宏名。在预编译时将宏名替换成字符串的过程称为“宏展开宏展开”。注意:宏名习惯用大写字母表示。定义宏与定义变量含义不同,宏定注意:宏名习惯用大写字母表示。定义宏与定义变量含义不同,宏定义只是作字符替换,并不给宏名分配内存空间。义只是作字符替换,并不给宏名分配内存空间。 例例8.1 # include #define SIZE 5int main() int i,sum=0; int dataSIZE; for(i=0;iSIZE;i+) scanf(“%d”,&datai); sum=sum+datai; printf(“sum=%dn”,sum); return 0; 运行此程序可计算运行此程序可计算5个数组元素值的总和。个数组元素值的总和。对宏定义的说明:对宏定义的说明: (1)(1)定义宏的目的是提高程序的可读性和通用性,便于程序的修定义宏的目的是提高程序的可读性和通用性,便于程序的修改。例如若要把例改。例如若要把例8.18.1中数组中数组datadata的元素个数改变为的元素个数改变为1010,则只要将,则只要将“#define SIZE 5#define SIZE 5”改为改为“#define SIZE 10#define SIZE 10”即可,程序中的其它语即可,程序中的其它语句均不用修改。句均不用修改。 (2) (2)不要在宏定义的行末加分号,因为宏定义不是不要在宏定义的行末加分号,因为宏定义不是C C语句,加分号语句,加分号后,会将分号也作为字符串的组成部分,宏展开后可能出现错误。后,会将分号也作为字符串的组成部分,宏展开后可能出现错误。 (3) (3)宏定义可以出现在程序的任何位置,一般位于文件开头,写在宏定义可以出现在程序的任何位置,一般位于文件开头,写在函数的外面。宏名的有效范围是从定义处到本源文件结束。可以用函数的外面。宏名的有效范围是从定义处到本源文件结束。可以用#undef#undef命令终止宏定义的作用域。例如:命令终止宏定义的作用域。例如: #define PI 3.1415926#define PI 3.1415926 int main()int main() #undef PI#undef PI 由于由于#undef#undef的作用,使的作用,使PIPI的作用范围在的作用范围在#undef#undef行处终止。若在行处终止。若在#undef PI#undef PI之后再出现之后再出现PIPI,则是无效的。,则是无效的。 (4)(4)宏定义是用宏名代替一个字符串,凡在宏定义有效范围内的宏名都宏定义是用宏名代替一个字符串,凡在宏定义有效范围内的宏名都用该字符串代替,但要注意:双引号内的与宏名相同的字符串不认为是宏用该字符串代替,但要注意:双引号内的与宏名相同的字符串不认为是宏名,不进行替换。名,不进行替换。 例如:例如: #define YES 1#define YES 1 printf(“YES”);printf(“YES”); 程序将显示程序将显示YESYES,而不是,而不是1 1。 (5)(5)可以引用前面已经定义的宏名来定义新的宏,例如:可以引用前面已经定义的宏名来定义新的宏,例如: define I1 30define I1 30 define I2 60define I2 60 define J I1+I2define J I1+I2 define K Jdefine K J* *2+J/2+I22+J/2+I2 这里这里J J引用了引用了I1I1和和I2I2,K K引用了引用了J J和和I2I2。 注意注意K K展开是:展开是:30+6030+60* *2+30+60/2+602+30+60/2+60, 不要以为是:不要以为是:(30+60)(30+60)* *2+(30+60)/2+602+(30+60)/2+60。 除非前面的定义是:除非前面的定义是:define J (I1+I2)define J (I1+I2)。8.1.2 带参数的宏定义带参数的宏定义 带参数的宏定义的一般形式为:带参数的宏定义的一般形式为: #define#define标识符标识符( (形参表形参表) ) 字符串字符串 带参数的宏展开时,还要进行参数替换。宏定义中形参表中的形带参数的宏展开时,还要进行参数替换。宏定义中形参表中的形参,在程序中将用实参替换。参,在程序中将用实参替换。例如:例如: #define PI 3.14159 #define PI 3.14159 #define V(r) 4 #define V(r) 4* *PIPI* *r r* *r r* *r/3r/3 V(r)V(r)为带参数的宏,例如在程序中使用为带参数的宏,例如在程序中使用V(6)V(6)时,是用时,是用6 6代替宏定代替宏定义中的形式参数义中的形式参数r r,V(6)V(6)展开为:展开为:4 4* *3.141593.14159* *6 6* *6 6* *6/36/3,这是用来计,这是用来计算半径为算半径为6 6的球的体积。的球的体积。 l例8.2l # include l #define S(a, b, h) (a+b)*h/2l int main()l int c1=6,c2=8,c3=10;l printf(“S=%dn”,S(c1, c2, c3);l return 0;l S(c1,c2,c3)展开为:展开为:(c1+c2)*c3/2。程序实际执行的是下面的。程序实际执行的是下面的输出语句:输出语句: printf(“S=%dn”,(c1+c2)*c3/2); 如果将上面的如果将上面的S(c1,c2,c3)换成换成S(6, c2, 2+8),运行程序,运行程序后,输出结果还是后,输出结果还是70吗?吗? 在使用带参数的宏定义时,宏名和括号之间不能有空格,否则系统会把在使用带参数的宏定义时,宏名和括号之间不能有空格,否则系统会把括号、形参和字符串认为是一个字符串。括号、形参和字符串认为是一个字符串。 例如,如果有例如,如果有 define S (x,y) xdefine S (x,y) x* *y y 会被认为:会被认为: S S是符号常量(不带参的宏名),它代表字符串是符号常量(不带参的宏名),它代表字符串“(x,y) x(x,y) x* *y y”。 上面介绍的用带参数的宏求球的体积和梯形的面积等问题显然也可以用函上面介绍的用带参数的宏求球的体积和梯形的面积等问题显然也可以用函数解决。数解决。 带参数的宏和函数在形式上有相似的地方,但是它们有许多不同点:带参数的宏和函数在形式上有相似的地方,但是它们有许多不同点: (1)(1)宏展开是在编译时进行的,不占用程序运行时间,在展开时并不分配宏展开是在编译时进行的,不占用程序运行时间,在展开时并不分配内存单元,即使是带参数的宏也不分配内存单元;而函数调用则是在程序运内存单元,即使是带参数的宏也不分配内存单元;而函数调用则是在程序运行时进行处理的,占用程序运行时间,要为形参分配临时的内存单元。行时进行处理的,占用程序运行时间,要为形参分配临时的内存单元。 (2) (2) 宏展开只是替换;而函数调用时,要计算实参表达式的值后传递给形宏展开只是替换;而函数调用时,要计算实参表达式的值后传递给形参,不是替换。函数调用时存在着从实参向形参传递数据的过程,而使用带参,不是替换。函数调用时存在着从实参向形参传递数据的过程,而使用带参数的宏,也不存在传递数据的过程。参数的宏,也不存在传递数据的过程。 (3) (3)宏名以及它的参数都不存在类型问题,展开时用指定的字符串替换即宏名以及它的参数都不存在类型问题,展开时用指定的字符串替换即可。而函数中的实参和形参都要定义类型。可。而函数中的实参和形参都要定义类型。 (4)宏展开后对源程序长度有影响,而函数调用对源程序长度无影响。宏展开后对源程序长度有影响,而函数调用对源程序长度无影响。 有些问题,用宏和函数都可以,如下例:有些问题,用宏和函数都可以,如下例:用函数:用函数:max(int x,int y) return(xy)?x:y; main( ) int a,b,c,d,t; t =max(a+b,c+d); 用宏用宏 : # define MAX(x,y) (x)(y)?(x):(y)main( )int a,b,c,d,t; t = MAX(a+b,c+d); 赋值语句展开后为赋值语句展开后为 t = (a+b)(c+d)?(a+b):(c+d);8.2 “文件包含”处理 l C语言提供了语言提供了# include命令命令用来实现用来实现“文件包含文件包含”的操作。的操作。作用是将一个源文件的全部内容包含进另一个源文件中来。作用是将一个源文件的全部内容包含进另一个源文件中来。l 被包含的文件可以是被包含的文件可以是C语言源文件、库函数头文件等。因为语言源文件、库函数头文件等。因为# include命令行通常都放在文件的开头,所以这些被包含的文命令行通常都放在文件的开头,所以这些被包含的文件通常被称为件通常被称为“标题文件标题文件”或或“头文件头文件”,常以,常以“.h”(h为为head的缩写)为文件的扩展名。当然也可以用其他文件扩展名,的缩写)为文件的扩展名。当然也可以用其他文件扩展名,但无论用什么扩展名,这个被包含文件必须是文本文件。但无论用什么扩展名,这个被包含文件必须是文本文件。l C集成环境为用户提供了很多集成环境为用户提供了很多库函数库函数,每一个库函数都有,每一个库函数都有自己对应的自己对应的头文件头文件,在,在C语言库函数与用户程序之间进行信息语言库函数与用户程序之间进行信息通信时,要使用一些库函数中定义的数据和变量,在使用某一通信时,要使用一些库函数中定义的数据和变量,在使用某一库函数时,都要在程序中库函数时,都要在程序中使用使用# include命令将该函数所对应的命令将该函数所对应的头文件包含进来头文件包含进来,否则,程序在编译时报错。,否则,程序在编译时报错。 文件包含的使用格式为:文件包含的使用格式为: include include “文件名文件名” 或或 include include 文件名文件名 其中的其中的“文件名文件名”和和的区别是:当使的区别是:当使用用“文件名文件名” 形式时,预处理程序首先检索当前形式时,预处理程序首先检索当前文件目录是否有该文件,如果没有,再检索文件目录是否有该文件,如果没有,再检索C编编译系统中指定的目录;而使用译系统中指定的目录;而使用形式时,形式时,预处理程序直接检索预处理程序直接检索C编译系统指定的目录。使编译系统指定的目录。使用用“”“”时,格式文件包含名可加路径。时,格式文件包含名可加路径。 例如:例如:#include “d:tcincludestdio.h” 常用的标准库头文件的扩展名都是常用的标准库头文件的扩展名都是h h,如:,如:# include /# include /* *标准输入输出函数文件标准输入输出函数文件* */ /# include /# include /* *字符串函数文件字符串函数文件* */ /# include /# include /* *字符函数文件字符函数文件* */ /# include # include /*数学函数库文件数学函数库文件*/ “文件包含文件包含”命令可以节省程序设计人员的劳动。例如,可以命令可以节省程序设计人员的劳动。例如,可以将经常使用一组固定的符号常量将经常使用一组固定的符号常量(g=9.81 , pi=3.1415926 , (g=9.81 , pi=3.1415926 , e=2.718e=2.718等等等等) )用宏定义命令组成一个文件,只要用用宏定义命令组成一个文件,只要用#include#include命令命令这个文件包含到自己所写的源文件中即可。这个文件包含到自己所写的源文件中即可。 正确的使用正确的使用#include#include语句,将会减少不必要的重复工作,提语句,将会减少不必要的重复工作,提高编程效率。特别是在一个软件开发小组共同协作开发大型软件高编程效率。特别是在一个软件开发小组共同协作开发大型软件时,时,includeinclude文件十分有用,利用它可以定义程序中共同的常量、文件十分有用,利用它可以定义程序中共同的常量、函数原型、宏等,这样可以便于修改且不易出错。函数原型、宏等,这样可以便于修改且不易出错。例例8.5 编制如下内容的被包含文件,将其拷贝到编制如下内容的被包含文件,将其拷贝到C C语言目录中。该文件名为语言目录中。该文件名为bj.hbj.h。 #define START #define START #define OK #define OK #define MAX(x,y) xy ? x : y #define MAX(x,y) xy ? x : y编写另一程序编写另一程序file.cfile.c,内容如下:,内容如下: # include # include # include “bj.h” # include “bj.h” int main() int main() START START double x=500.0, y=100.0; long lx=25, ly=37; double x=500.0, y=100.0; long lx=25, ly=37; printf(“double MAX=%lfn”, MAX(x,y); printf(“long MAX=%ldn”, printf(“double MAX=%lfn”, MAX(x,y); printf(“long MAX=%ldn”, MAX(lx,ly);MAX(lx,ly); return 0; return 0; OK OK 注意:在编译时并不是作为两个文件进行连接的,而注意:在编译时并不是作为两个文件进行连接的,而是作为一个源程序编译,得到一个目标是作为一个源程序编译,得到一个目标(.obj)(.obj)文件。文件。执行程序执行程序file.cfile.c,结果显示如下:,结果显示如下:double MAX=500.000000double MAX=500.000000long MAX=37long MAX=37说明:说明: (1)(1)如果要包含如果要包含n n个文件,必须用个文件,必须用n n个个includeinclude命令。即一个命令。即一个includeinclude命令只能指定一个被包含文件。命令只能指定一个被包含文件。 (2)(2)假设假设“wj1.c”“wj1.c”、“wj2.c”“wj2.c”、“wj3.c”“wj3.c”是三个不同的文是三个不同的文件,若在件,若在“wj1.c”“wj1.c”有如下两行命令:有如下两行命令: # include # include # include # include 则在文件则在文件“wj1.c”“wj1.c”中可以用中可以用“wj2.c”“wj2.c”和和“wj3.c”“wj3.c”的内容,在的内容,在文件文件“wj2.c” “wj2.c” 中可以用中可以用“wj3.c”“wj3.c”的内容,不必在文件的内容,不必在文件“wj2.c” “wj2.c” 中再使用中再使用“#include #include ”命令。命令。 若在若在“wj1.c” “wj1.c” 中只有中只有“#include“#include ” ” 命令,而命令,而“wj1.c”“wj1.c”中又要使用中又要使用“wj3.c”“wj3.c”的内容,也可以让的内容,也可以让“wj2.c”“wj2.c”中中出现出现“#include ” “#include ” 命令。即文件包含可以嵌套使用。命令。即文件包含可以嵌套使用。 l例例8.4l/*file.c*/l#include l#include “myfile.txt”lint main()l fun();l return 0;llmyfile.txt文本文件内容如下文本文件内容如下lvoid fun()lchar c;l if(c=getchar()!=n)l putchar(c);l fun();l 在编译在编译file.c时,预处时,预处理过程中用理过程中用myfile.txt文件文件的文本替换的文本替换file.c中的中的#include “myfile.txt”, 因此本例程序功能是接因此本例程序功能是接受用户的按键,直到按回受用户的按键,直到按回车键为止,然后将字符序车键为止,然后将字符序列显示出来。列显示出来。8.3 条件编译条件编译 有时希望当满足某条件时对一组语句进行编译,有时希望当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句,使得同一个而当条件不满足时则编译另一组语句,使得同一个源程序在不同的编译条件下能够产生不同的目标代源程序在不同的编译条件下能够产生不同的目标代码文件。这就是码文件。这就是“条件编译条件编译”。 条件编译命令有以下条件编译命令有以下3种形式:种形式:1、 # ifdef 标识符标识符 程序段程序段1 # else 程序段程序段2 # endif 作用:当标识符已经被定义过作用:当标识符已经被定义过(一般是用一般是用define命令定义命令定义),则对程则对程序段序段1进行编译;否则编译程序段进行编译;否则编译程序段2。 其中(其中(# else 程序段程序段2 )可)可以没有。以没有。 作用:作用: 若标识符未被若标识符未被定义,则编译程序段定义,则编译程序段1;否则编译程序段否则编译程序段2。2、# ifndef 标识符 程序段1 # else 程序段2 # endif3、 # if 表达式 程序段1 # else 程序段2 # endif 作用:当指定的表达式作用:当指定的表达式值为真(非零)时编译程序值为真(非零)时编译程序段段1;否则编译程序段;否则编译程序段2。# include # define TERM 0 int main() int i, a10; float s=0, t=1 ; for (i=0; i10; i+) scanf(“%d”, &ai); # if TERM for (i=0; i10; i+) t=t*ai; printf (%f, t ); # else for (i=0; i10; i+) s= s+ai; printf (%f, s ); # endif return 0; 执行程序,若输入如下执行程序,若输入如下10个数:个数: 1 2 3 4 5 6 7 8 9 10输出结果为:输出结果为:55.000000若将程序中的若将程序中的“# define TERM 0”改为改为“# define TERM 1”,同样输入上面的同样输入上面的10个数,程序运个数,程序运行的输出结果为:行的输出结果为:3628800.000000 例例8.5 输入输入10个整数,根据需要设置条件编译,能够求个整数,根据需要设置条件编译,能够求10个个整数的和,或求整数的和,或求10个整数的积。个整数的积。8.4 本章小结本章小结l本章介绍了宏定义的两种形式,本章介绍了宏定义的两种形式,介绍了文件包含的使用方法,介介绍了文件包含的使用方法,介绍了条件编译的概念。绍了条件编译的概念。