250 likes | 352 Views
第 18 讲 编译预处理 和 数据类型再命名. 第 18 讲 编译预处理和数据类型再命名. 本讲主要内容 宏定义 不带参数的宏 带参数的宏 文件包含 一般形式 文件包含的有关说明 条件编译 ifdef-else-endif 形式 ifndef-else-endif 形式 if-else-endif 形式 数据类型再命名 typedef 的一般形式 typedef 应用举例. 第 18 讲 编译预处理和数据类型再命名. 教学目标 了解编译预处理的概念,了解宏的概念。 了解文件包含的概念,熟练掌握文件包含命令 include 的用法。
E N D
第18讲 编译预处理和 数据类型再命名
第18讲 编译预处理和数据类型再命名 本讲主要内容 • 宏定义 • 不带参数的宏 • 带参数的宏 • 文件包含 • 一般形式 • 文件包含的有关说明 • 条件编译 • ifdef-else-endif形式 • ifndef-else-endif形式 • if-else-endif形式 • 数据类型再命名 • typedef的一般形式 • typedef应用举例
第18讲 编译预处理和数据类型再命名 • 教学目标 • 了解编译预处理的概念,了解宏的概念。 • 了解文件包含的概念,熟练掌握文件包含命令include的用法。 • 了解条件编译的概念,熟悉常用的条件编译命令及其用法。 • 了解文件重名的意义,掌握typedef命令的使用方法。
一、宏定义 • 不带参数的宏 不带参数的宏是用一个标识符代替一个字符串,其一般形式如下: #define 宏名宏体 其中的“#”表示这是一条预处理命令。 凡是以“#”开头的均为预处理命令。 “define”为宏定义命令。 宏名是一个标识符,宏体可以是一个字符串,也可以是一个数值,也可以是一个表达式等。 例如:#define PI 3.1415926
一、宏定义 • 例17-1 宏定义应用举例。 #define TWO 2 #define MSG "I love c language." #define FMT " number is %d\n" main() { int number=TWO; printf(FMT, number); printf("%s\n",MSG); }
一、宏定义 • 例17-2 计算圆面积。 #define PI 3.14 main() { float R=16.99; printf("S=PI*R*R=%f\n",PI*R*R); }
一、宏定义 • 例17-3 计算圆的周长和面积 。 #define R 3.0 #define PI 3.1415926 #deinfe L 2*PI*R /* 使用宏R和PI定义宏L */ #define S PI*R*R /* 使用宏R和PI定义宏S */ main() { printf("L=%f\nS=%f\n",L,S); }
一、宏定义 • 例17-4输出格式定义为宏。 #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[]="This is a string."; 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); }
一、宏定义 • 关于宏定义的说明: ⑴宏名一般使用大写形式。 ⑵使用宏可以提高程序的可读性和可移植性。 ⑶宏定义是用宏名来表示一个字符串,在宏替换时又以该字符串取代宏名,这只是一种简单的代换。 ⑷宏定义不是C语句,后面不能有分号。如果加入分号,则加入的分号将作为宏体的一部分。 ⑸通常把#define命令放在一个文件的开头,使其在本文件全部有效。 ⑹宏定义终止命令#undef结束先前定义的宏名。 ⑺程序中用双引号括起来的字符串,即使与宏名相同,也不替换。 ⑻宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名。在宏展开时由预处理程序层层替换。 ⑼宏定义名仅用于编译预处理,不是程序变量,因此系统不会为它分配内存空间。
一、宏定义 • 带参数的宏 带参数宏定义的一般形式如下: #define 宏名(参数表) 字符串 其中:⑴宏名后的参数表可以是一个参数,也可以是多个参数,当有多个参数时,各参数之间用“,”分割。 ⑵用于宏体的字符串部分要包括参数表中的参数。 例如:#define max(x,y) x>y?x:y 宏替换按如下过程进行:对于程序中带实参的宏,按照宏定义中的字符串从左到右进行替换,当遇到宏中的形参时,就用程序语句中的宏实参取代它,对于宏定义字符串(宏体)中的非参数字符,则予以保留。
一、宏定义 • 例17-5 利用宏定义求三个数的最大数。 #include "stdio.h" #define max(x,y) x>y?x:y main() { int a,b,c,m; scanf("%d%d%d",&a,&b,&c); m=max(a,b); /* 使用宏max,a、b为宏实参 */ printf("max=%d\n",max(m,c));/* m、c为宏实参 */ }
一、宏定义 • 使用带参数的宏时应注意如下问题: ⑴在带参宏定义中,形式参数不分配内存单元,也不存在类型定义问题。而宏调用中的实参有具体的值,要用它们去替换形参,因此必须首先进行类型说明。 ⑵如果程序中有使用表达式作为宏实参的情况,那么在进行宏定义时,出现在宏替换字符串中的宏参数应使用括号()进行限定,否则宏替换后有可能产生错误的结果。 ⑶带参宏定义中,宏名和形参表之间不能有空格出现,一旦出现了空格,就变成了不带参数的宏定义。
一、宏定义 • 带参数的宏与函数调用的区别: ⑴函数调用时要先计算实参表达式的值,然后传给形参。而带参的宏只是进行简单的符号替换,不存在计算表达式的值的问题。 ⑵函数调用在程序运行过程中发生作用,而宏在程序的编译预处理阶段发生作用。 ⑶函数调用只能得到一个返回值,使用宏能够得到多个结果。 ⑷宏替换不占运行时间,只占编译时间,而函数调用则占用运行时间。 ⑸在函数中,形参和实参是两个不同的量,各有自己的作用域,调用时要把实参值赋予形参,进行“值传递”。而在带参宏中,只是符号替换,不存在值传递的问题。
一、宏定义 • 例17-6 计算圆的周长和面积。 #define PI 3.1415926 #define CIRCLE(R,L,S) L=2*PI*R;S=PI*R*R; main() { float r,l,s; printf("Input r:"); scanf("%f",&r); CIRCLE(r,l,s); /* 源程序中使用带参数的宏 */ printf("L=%f,S=%f\n",l,s); }
二、文件包含 为了在一个源程序中使用另一个源程序的代码,C语言提供了文件包含命令include, 它的一般使用形式如下: #include "文件名" 文件包含命令的功能是把指定的文件插入该命令行位置取代该命令行,从而把指定的文件和当前的源程序文件连成一个源文件。
二、文件包含 • 例17-7 创建源文件userdef.h并在程序e1707.c中使用它。 如下是userdef.h文件的内容: #define PRINT printf /* 定义符号常量PRINT */ #define INPUT scanf /* 定义符号常量INPUT */ #define PI 3.1415926 /* 定义符号常量PI */ 如下是e1707.c文件的内容: #include "userdef.h" main() {float s,r; PRINT("r="); INPUT("%f",&r); s=PI*r*r; PRINT("AREA=%f\n",s); }
二、文件包含 • 对文件包含命令还要说明以下几点: ⑴包含命令中的文件名可以用双引号括起来,也可以用尖括号括起来,以下写法都是允许的: #include "stdio.h" #include <math.h> 但是这两种形式是有区别的: 使用双引号则表示首先在当前的源文件目录中查找,若未找到才到包含目录中去查找;使用尖括号表示在包含文件目录中去查找, 而不在源文件目录去查找。 ⑵ 一个include命令只能指定一个被包含文件, 若有多个文件要包含,则需用多个include命令。 ⑶文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件。
三、条件编译 • ifdef-else-endif形式 一般形式如下: #ifdef标识符 程序段1 #else 程序段2 #endif 该形式的条件编译的过程:如果“标识符”已被“#define”命令定义过,则对“程序段1”进行编译;否则对“程序段2”进行编译。如果没有“程序段2”,上述格式中的“#else”即不再使用,条件编译成为如下形式: #ifdef标识符 程序段 #endif
三、条件编译 • 例17-8 条件编译举例1。 #define NUM ok struct stu { int num; char *name; char sex; float score; }*ps; main() {ps=(struct stu*)malloc(sizeof(struct stu)); ps->num=102; ps->name="Zhang ping"; ps->sex='M'; ps->score=62.5; #ifdef NUM printf("Number=%d\nScore=%f\n",ps->num,ps->score); #else printf("Name=%s\nSex=%c\n",ps->name,ps->sex); #endif free(ps); }
三、条件编译 • ifndef-else-endif形式 一般形式如下: #ifndef标识符 程序段1 #else 程序段2 #endif 它与第一种形式的区别是将“ifdef”改为了“ifndef”。它的功能是,如果标识符未被#define命令定义过则对程序段1进行编译,否则对程序段2进行编译。这与第一种形式的功能正相反。
三、条件编译 • if-else-endif形式 一般形式如下: #if常量表达式 程序段1 #else 程序段2 #endif 它的功能是,如常量表达式的值为真(非0),则对程序段1进行编译,否则对程序段2进行编译。因此可以使程序在不同条件下,完成不同的功能。
三、条件编译 • 例17-9 条件编译举例2。 #define R 1 main() {float x,s; printf ("input a number: "); scanf("%f",&x); #if R s=3.14159*x*x; printf("Area of round is: %f\n",s); #else s=x*x; printf("Area of square is: %f\n",s); #endif }
四、数据类型再命名 所谓数据类型再命名就是为已经存在的数据类型再定义一个另外的名字,目的是为了增强程序的可读性,使用的命令是typedef,它以C语句的形式出现在程序中。 typedef 的一般形式如下: typedef 已定义的类型名新类型名; 例如: typedef int INTEGER; typedef float REAL;
四、数据类型再命名 例17-10 用typedef命令改写例13-3简单的密码加密程序。 #include "stdio.h" struct table {char input; char output; }; typedef struct table TABLE; main( ) {char ch; int length, i; TABLE translate[]={'a','f','b','g','w','d','f', '9','y','*','x','s','m','3','h','k','p','f','u','?' }; length=sizeof(translate)/sizeof(struct table); while((ch=getchar())!= '\n') { for(i=0;translate[i].input!=ch&&i<length;i++) ; if (i<length) putchar(translate[i].output); else putchar (ch); } }
编译预处理和数据类型再命名-小结 1.使用预处理功能便于程序的修改、阅读、移植和调试,也便于实现模块化程序设计。 2.宏定义是用一个标识符来表示一个字符串,这个字符串可以是常量、变量或表达式。在宏调用中将用该字符串代换宏名。 3.宏定义可以带有参数,宏调用时是以实参代换形参,而不是“值传送”。 4.为了避免宏替换时发生错误,宏定义中的字符串应加括号,字符串中出现的形式参数两边也应加括号。 5.文件包含是预处理的一个重要功能,它可用来把多个源文件连接成一个源文件进行编译,结果将生成一个目标文件。 6.条件编译允许只编译源程序中满足条件的程序段,使生成的目标程序较短,从而减少了内存的开销并提高了程序的效率。 7.数据类型再命名是为已经存在的数据类型再定义一个另外的名字,目的是为了增强程序的可读性,使用的命令是typedef。