第7章 编译预处理 本章要求: 本章重点: 本章难点: 掌握用#define定义无参数宏和带有参数宏定义和调用方法; 第7章 编译预处理 本章要求: 掌握用#define定义无参数宏和带有参数宏定义和调用方法; 掌握#include文件包含的使用方法; 了解条件编译的使用。 本章重点: 带参宏定义和使用方法、文件包含的使用 本章难点: 带参宏定义和使用方法
第7章 编译预处理 在C语言源程序中以“#”号开头的预处理命令都放在函数之外,一般都放在源文件的前面,它们称为预处理部分。 第7章 编译预处理 在C语言源程序中以“#”号开头的预处理命令都放在函数之外,一般都放在源文件的前面,它们称为预处理部分。 C语言提供了多种预处理功能,主要有宏定义、文件包含、条件编译。 宏定义命令(#define)、文件包含命令(#include)、条件编译命令(#if、#ifdef、#else、 # ifndef和#endif) 特点:用#开头,并且预处理命令单独占一行,不用分号。
7.1 宏定义 7.1.1 无参宏定义 例7-1 通过键盘输入50个实数,将其中大于它们平均值的数打印输出。(未使用宏定义) 7.1 宏定义 7.1.1 无参宏定义 例7-1 通过键盘输入50个实数,将其中大于它们平均值的数打印输出。(未使用宏定义) void main() { int i; float x[50], ave=0; for(i=0;i<50;i++) scanf("%f",&x[i]); ave+=x[i]; } ave=ave/50; if(x[i]>ave) printf("%f ",x[i]);
7.1 宏定义 7.1.1 无参宏定义 例7-2 通过键盘输入100个实数,将其中大于它们平均值的数打印输出。 (使用无参宏定义) 7.1 宏定义 7.1.1 无参宏定义 例7-2 通过键盘输入100个实数,将其中大于它们平均值的数打印输出。 (使用无参宏定义) #define NUM 100 void main() { int i;float x[NUM],ave=0; for(i=0;i<NUM;i++) { scanf("%f",&x[i]); ave+=x[i]; } ave=ave/NUM; if(x[i]>ave) printf("%f ",x[i]);
7.1 宏定义 7.1.1 无参宏定义 一般形式: #define 标识符 字符串 C语言源程序中用一个标识符来表示一个字符串, 称为宏。 7.1 宏定义 7.1.1 无参宏定义 一般形式: #define 标识符 字符串 C语言源程序中用一个标识符来表示一个字符串, 称为宏。 被定义为“宏”的标识符称为“宏名”。 在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,称为“宏代换”或“宏展开”。 宏定义是由源程序中的宏定义命令完成的。 宏代换是由预处理程序自动完成的。 C语言中,“宏”分为有参数和无参数两种。
7.1 宏定义 7.1.1 无参宏定义 例7-3 输入一个圆的半径,输出该圆的周长。 #define PI 3.14 7.1 宏定义 7.1.1 无参宏定义 例7-3 输入一个圆的半径,输出该圆的周长。 #define PI 3.14 #define C 2*PI*r /*PI是已定义的宏名*/ void main() { double r; printf("Enter The Radius\n"); scanf("%lf",&r); printf("The Perimeter=%lf\n",C); } printf("The Perimeter=%lf\n",2*PI*r); printf("The Perimeter=%lf\n",2*3.14*r);
7.1 宏定义 无参宏定义的说明: C语言允许宏定义嵌套,在宏定义的字符串中可以使用已经定义的宏名。在宏展开时由预处理器进行层层代换。 7.1 宏定义 无参宏定义的说明: C语言允许宏定义嵌套,在宏定义的字符串中可以使用已经定义的宏名。在宏展开时由预处理器进行层层代换。 本例的宏定义用“C”替换表达式“2*PI*r”,因此宏不仅可以代替常量,宏还可以表示任何字符串,甚至是整个表达式。 预处理器不能理解C语言,预处理时不检查语法,不分配内存空间,仅作简单字符串置换。源程序只有在进行编译时才作语法检查。
7.1.1 无参宏定义 使用无参数宏定义还有以下几点说明: 宏名遵守C语言标识符的命名规则。 宏定义必须写在函数之外,其作用域从定义开始到用#undef取消其定义或到文件结束为止。取消了符号常量和宏名约定以后,可以用命令#define重新定义它。 通常,#define命令写在文件的开头,函数之前,使其在本文件中全部有效。 宏定义的替换文本通常在同一行中,如果替换文本超过一行,必须在该行的最后加上反斜杠“\”,反斜杠表示替换文本继续到下一行。
7.1 宏定义 #define 7.1.2 带参宏定义 无参数宏用于定义符号来代替常量,C语言还允许用符号来定义操作。通过使用参数,可以创建外形和作用都与函数相似的类函数宏。 例7-5 用定义带参数宏来改写例7-3的程序。 #define PI 3.14 #define C(r) 2*PI*r /*PI是已定义的宏名*/ void main() { double radius,perimeter; printf("Enter The Radius\n"); scanf("%lf",&radius); perimeter=C(radius); printf("The Perimeter=%lf\n",perimeter); } 此处宏展开为: perimeter=2*3.14*radius;
7.1 宏定义 #define 7.1.2 带参宏定义 本例宏定义括号内的r称为形式参数,将宏调用时出现的变量名radius称为实际参数,形参和实参可以不止一个。 带参数宏定义的一般形式为: #define 宏名(形参表) 替换文本 带参宏调用的一般形式为: 宏名(实参表) 宏的参数用圆括号括起来,宏名与参数表间不能有空格,否则 将作为无参数宏来处理
7.1 宏定义 #define 说明: 带参宏定义中的形参是标识符,而宏调用中的实参可以是表达式。带参宏展开时,用实参字符串替换形参字符串,由于运算符的优先级问题,可能发生逻辑错误。可以采用给宏定义的形参加括号的方法来解决,用圆括号括住每个参数,并括住宏的整体定义。 例一,宏定义:#define S(r) 3.14*r*r 宏调用语句:area = S(a+b); 宏展开后:area =3.14*a+b*a+b; 例二,宏定义: #define S(r) 3.14*(r)*(r) 宏展开后:area =3.14*(a+b)*(a+b)
7.1.2 有参宏定义 7.1.3 带参宏与函数的比较 例7-7 使用函数来实现例7-5求圆周长的功能。 #define PI 3.14 7.1.3 带参宏与函数的比较 例7-7 使用函数来实现例7-5求圆周长的功能。 #define PI 3.14 double C(double r); void main() { double radius,per; printf("Enter The Radius\n"); scanf("%lf",&radius); per=C(radius); printf("The Perimeter=%lf\n",per); } double C(double r) { return 2*PI*r; 例7-5 用定义带参数宏来改写例7-3的程序。 #define PI 3.14 #define C(r) 2*PI*r void main() { double radius,per; printf("Enter The Radius\n"); scanf("%lf",&radius); per=C(radius); printf("The Perimeter=%lf\n",per); }
7.1.2 有参宏定义 7.1.3 带参宏与函数的比较 在例7-5的带参宏定义中,形式参数r不分配内存单元,因此不必作类型定义。宏调用中的实参radius有具体的值,宏展开时用它去代换形参r,因此必须作类型说明。 在本例的函数中,形参r和实参radius是两个不同的量,有各自的作用域,调用时要把实参值赋予形参,进行“值传递”。而带参宏只是简单的字符代换,不存在值传递的问题。
7.1.2 有参宏定义 函数调用和宏调用二者在形式上相似,在本质上是完全不同的。现将它们的区别归纳如表所示: 区别项目 函数 宏 信息传递 实参的值或地址传送给形参。 用实参的字符串替换形参。 处理时刻、内存分配情况 程序运行时处理,分配临时内存单元。 宏展开在预编译时处理,不存在分配内存的问题。 参数类型 实参和形参类型一致。如不一致,编译器进行类型转换。 作字符串替换,不存在参数类型问题。 返回值 可以有一个返回值。 作字符串替换,不存在返回值问题。 对源程序的影响 无影响。 宏展开后使程序加长。 时间占用 占用程序运行时间。 占用编译时间。
#include "文件名" 或:#include <文件名> 所谓“文件包含”,是指一个原文件可以将另外一个原文件的所有内容包含进来。其使用格式为: #include "文件名" 或:#include <文件名> 例如,在前面我们已多次用此命令包含过库函数的头文件: #include "stdio.h" /* 包含标准输入输出头文件*/ #include "math.h" /* 包含数学函数头文件*/ #include "string.h" /* 包含字符串处理函数头文件*/
7.2 文件包含命令 #include 文件包含命令的作用: 在被包含文件(头文件)中集中书写符号常量定义、宏定义或外部说明等。 在每个需要这些定义或者说的源程序文件开始,用文件包含命令将这个头文件包含到程序中。
7.3 条件编译 条件编译能够让程序员控制预处理命令的执行和程序代码的编译。也就是说,条件编译预处理命令告诉编译器:根据编译时的条件接受或者忽略代码块。 条件编译有三种形式: 1.第一种形式: #ifdef 标识符 程序段1 [#else 程序段2 ] #endif #ifdef命令说明:如果预处理器已经定义了标识符,则编译代码直到下一个#else或者#endif命令出现为止。如果有#else命令,那么,在未定义标识符时会编译#else和#endif之间的代码。
7.3 条件编译命令 例7-9 根据是否定义宏C,决定是计算圆周长还是圆面积。 #define PI 3.14 #define C(r) 2*PI*(r) void main() {double r,c,s; printf("Enter The Radius\n"); scanf("%lf",&r); #ifdef C c=C(r);printf("The perimeter=%lf\n",c); #else s=PI*r*r;printf("The Area=%lf\n",s); #endif } 因为存在第2行的宏定义,因此系统编译求圆周长的那一段程序,而求面积的那段程序就不编译。
7.3 条件编译命令 2.第二种形式: #ifndef 标识符 程序段1 [#else 程序段2 ] #endif 如果标识符未被#define命令定义过则对程序段1进行编译,否则对程序段2进行编译。 3.第三种形式: #if 表达式 程序段1 #else 程序段2 #endif #if命令后跟常量整数表达式,如表达式的值为真(非0),则对程序段1 进行编译,否则对程序段2进行编译。
7.3 条件编译命令 例 使用条件编译的第三种形式改写例7-9。 #define PI 3.14 #define C 1 例 使用条件编译的第三种形式改写例7-9。 #define PI 3.14 #define C 1 void main() { double r,c,s; printf("Enter The Radius\n");scanf("%lf",&r); #if C c=2*PI*r;printf("The perimeter=%lf\n",c); #else s=PI*r*r;printf("The Area=%lf\n",s); #endif } 第2行宏定义中,定义C为1,因此在条件编译时,常量表达式的值为真,故计算并输出圆周长。