第九章编译预处理课题:第九章编译预处理教学目的:1、了解预处理的概念及特点2、掌握有参宏与无参宏的定义及使用,领会文件包含的使用及效果教学重点:教学难点:掌握宏的使用,文件包含有参宏与无参宏的使用步骤一复习引导ANSI C标准规定可以在C源程序中加入一些“预处理命令”,以改进程序设计环境,提高编程效率。
这些预处理命令是由ANSI C统一规定的,但它不是C语言本身的组成部分,不能直接对它们进行编译。
必须在对程序进行通常的编译之前,先对程序中这些特殊的命令进行“预处理”,即根据预处理命令对程序作相应的处理。
经过预处理后程序不再包括预处理命令了,最后再由编译程序对预处理后的源程序进行通常的编译处理,得到可供执行的目标代码。
步骤二讲授新课C语言与其他高级语言的一个重要区别是可以使用预处理命令和具有预处理的功能。
C 提供的预处理功能主要有以下三种:宏定义、文件包含、条件编译。
分别用宏定义命令、文件包含命令、条件编译命令来实现。
为了与一般C语句相区别,这些命令以符号“ #” 开头。
§9.1宏定义宏:代表一个字符串的标识符。
宏名:被定义为“宏”的标识符。
宏代换(展开):在编译预处理时,对程序中所有出现的“宏名”,用宏定义中的字符串去代换的过程。
一、不带参数的宏定义一般形式:#define 标识符字符串#define PI 3.1415926main(){ float l, s, r, v;printf( “input radius:” );scanf( “%f”, &r );l = 2.0*PI*r;s = PI*r*r;v = 3.0/4*PI*r*r*r;printf(“%10.4f,%10.4f,%10.4\n”, l, s, v);}例如:由键盘输入y值,求表达式:3(y2+3y)+ 4(y2+3y)+ y(y2+3y)#define M (y*y+3*y)main(){ int s, y;printf( “Input a number :”); scanf (“%d”,&y);s=3*M+4*M+y*M; p rintf(“s=%d\n”,s);}先宏展开:s=3*(y*y+3*y) +4*( y*y+3*y) + y*(y*y+3*y)再与源程序合并说明:⑴宏名一般用大写表示,以便与变量名区分。
⑵使用宏名使程序易读,易修改。
⑶只作简单的置换,不作正确性检查。
⑷宏定义不是C语句,不必在行末加分号。
⑸宏名的作用域一般从自定义命令到本源文件结束。
⑹可以用# undef命令终止宏定义的作用域。
⑺宏定义允许嵌套,允许层层置换。
⑻宏名在源程序中用双引号括起来,则TC中预处理不对其作宏代换。
例:printf(“L=%f”, L); 中双引号内L不替换。
⑼宏定义与定义的变量不同,宏只作字符替换,不分配内存空间。
⑽对“输出格式”进行宏定义,可以减少书写麻烦例如:#define P printf#define D “%d,%d,%d\n”#define F “%6.2f,%6.2f,%6.2f\n”main(){ int a=5,c=8,e=11;float b=3.8,d=9.7; f=21.08;P(D,a,c,e);P(F,b,d,f);P(F,a+b,c+d,e+f);}二、带参数的宏定义格式:#define 宏名(参数表) 字符串例:#define s(a,b) a*b{……area =s(3,2);……}对带参的宏展开后,为area=3*2;例:#define M(y) y*y+3*y{……k=M(5);……}对其展开后,为k=5*5+3*5;说明:⑴对带参数的宏展开只是将宏名后括号内的实参字符串代替#define命令行中的形参。
⑵宏定义时,在宏名与带参数的括号之间不应加空格,否则将空格以后的字符都作为替代字符串的一部分。
⑶带参宏定义,形参不分配内存单元,因此不必作类型定义。
(与函数的区别之一)⑷带参宏与函数的区别之二:例如:main(){ int i=1;while( i<=5 ) printf(“%d\t”,SQ( i++));}SQ(int y){ return(y)*(y); }其结果为:1 4 9 16 25如:#define SQ(y) ((y)*(y))main(){ int i =1;while( i<=5 )printf(“%d\t”,SQ( i++));}运行结果:2 12 30例:利用宏定义求两个数中的大数。
#define MAX(a,b) (a>b)?a:bmain(){int x, y, max;scanf(“%d%d”, &x, &y);max =MAX(x, y);printf(“max=%d\n”, max);}带参的宏定义和函数不同:1、函数调用时,先求实参表达式值,后代入。
而带参的宏只是进行简单的字符替换。
2、函数调用是在程序运行时处理的,分配临时的内存单元。
而宏展开则是在编译时进行的,不分配内存单元,不进行值的传递,也无“返回值”。
3、对函数中的实参和形参都要定义类型,类型应一致。
而宏不存在类型问题,宏名和参数无类型,只是一个符号代表,展开时代入指定的字符即可。
4、调用函数只可得到一个返回值,而用宏可以设法得到几个结果。
5、使用宏次数多时,宏展开后使源程序增长,而函数调用不使源程序变长。
6、宏替换不占运行时间,只占编译时间。
而函数调用则占用运行时间( 分配单元、保留现场、值传递、返回)一般用宏代表简短的表达式比较合适。
也可利用宏定义实现程序的简化。
例9.5:#define PR printf#define NL “\n”#define D “%d”#define D1 D NL#define D2 D D NL#define D3 D D D NL#define D4 D D D D NL#define S “%s”main(){ int a,b,c,d;char string[]=“CHINA”;a=1;b=2;c=3;d=4;PR(D1,a);PR(D2,a,b);PR(D3,a,b,c);PR(D4,a,b,c,d);PR(S, string);}§9.2 “文件包含”处理“文件包含”处理是指将指定的被包含文件的全部内容插到该控制行的位置处,使其成为源文件的一部分参与编译。
因此,被包含的文件应该是源文件。
通常置于源程序文件的首部,故也称为“头文件”。
C编译系统提供的头文件扩展名为“.h”,但设计者可根据实际情况,自行确定包含文件的后缀、名字及其位置。
一般形式,#include “文件名”或#include <文件名>文件format.h#define PR printf#define NL “\n”#define D “%d”#define D1 D NL#define D2 D D NL#define D3 D D D NL#define D4 D D D D NL#define S “%s”文件file1.c#include “format.h”main(){ int a,b,c,d;char string[]=“CHINA”;a=1;b=2;c=3;d=4;PR(D1,a);PR(D2,a,b);PR(D3,a,b,c);PR(D4,a,b,c,d);PR(S, string);}注:被包含的文件应是源文件,而不应是目标文件。
头文件除了可以包含函数原型和宏定义外,也可以包括结构体类型定义和全局变量定义等。
说明:1、一个include命令只能指定一个被包含文件,如果要包含n个文件,要用n个include命令。
2、如果文件1包含文件2,而文件2中要用到文件3的内容,则可在文件1中用两个include 命令分别包含文件2和文件3,而且文件3应出现在文件2之前,即在“file1.c”中定义:#include “file3.h”#include “file2.h”3、在一个被包含文件中又可以包含另一个被包含文件,即文件包含是可以嵌套的。
4、被包含文件(file2.h)与其所在的文件(file1.c),在预编译后已成为同一个文件。
5、在#include 命令中,文件名可以用双引号或尖括号括起来。
如:#include <file2.h>或#include “file2.h”二者的区别:用尖括号时称为标准方式,系统到存放C库头文件所在的目录中寻找要包含的文件。
用双引号时,系统先在用户当前目录中寻找要包含的文件,若找不到,再按标准方式查找。
#include “c:\tc\include\myfile.h” /*正确*/#include <c:\test\file.c> /*正确*/#include <c:\tc\include\stdio.h> /*错误*/用尖括号:带路径:按指定路径去寻找被包含文件,但此时被包含文件不能以“.h”结尾,否则错误。
不带路径:仅从指定标准目录下找。
用引号:带路径:按指定路径去寻找被包含文件,不再从当前目录和指定目录下找。
不带路径:先在当前目录下找,找不到再在系统指定的标准目录下找。
步骤三课堂小结本课主要讲解了宏定义、“文件包含”处理。
对带参数的宏的使用,及与函数的使用的区别。
搞清经常使用的头文件。
步骤四布置作业上机作业:(第九章课后练习)9.4书面作业:(第九章课后练习)9.7、9.8。