360 likes | 491 Views
第六章 编译预处理. 本章学习重点: 掌握不带参数的宏定义的格式及使用方法; 掌握带参数的宏定义的格式及使用方法; 掌握文件包含的格式及使用方法; 掌握条件编译的格式及使用方法。. 第十四讲宏定义、文件包含 和条件编译. 一、不带参数的宏定义 二、带参数的宏定义 三、文件包含处理 四、条件编译 练一练 本章小结. 结束. 一、不带参数的宏定义. 【思考题 6-1 】 输入圆的半径,求圆的周长、面积(要求使用无参宏定义圆周率)。 ( 一 )程序分析 在程序中要用到无参宏定义,要注意例程开头的宏定义语句的书写格式,掌握程序在编译之前怎样替换该宏?.
E N D
第六章 编译预处理 本章学习重点: 掌握不带参数的宏定义的格式及使用方法; 掌握带参数的宏定义的格式及使用方法; 掌握文件包含的格式及使用方法; 掌握条件编译的格式及使用方法。
第十四讲宏定义、文件包含和条件编译 一、不带参数的宏定义 二、带参数的宏定义 三、文件包含处理 四、条件编译 练一练 本章小结 结束
一、不带参数的宏定义 【思考题6-1】输入圆的半径,求圆的周长、面积(要求使用无参宏定义圆周率)。 (一)程序分析 在程序中要用到无参宏定义,要注意例程开头的宏定义语句的书写格式,掌握程序在编译之前怎样替换该宏? 返回到本章目录
(二)编写程序代码如下: #define PI 3.1415926 /*PI是宏名,3.1415926为用来替换宏名的常数*/ main() { float radius,length,area; printf("Input a radius: "); scanf("%f",&radius); length=2*PI*radius; /*引用无参宏求周长*/ area=PI*radius*radius; /*引用无参宏求面积*/ printf("length=%.2f,area=%.2f\n",length,area); } 返回到本章目录
(三)调试程序及运行结果 程序中输入圆的半径值2.4,程序的运行结果如下: 返回到本章目录
1.不带参数的宏定义 (1)不带参数宏定义的一般形式如下: 其中,#define是宏定义的命令,标识符和字符串之间用空格分开。标识符称为“宏名”,通常使用大写英文字母和有直观意义的标识符命名,以区别于源程序中的其他标识符。字符串又称为宏体,一般是常数、关键字、语句、表达式,也可以是空白。 #define 宏名 字符串 返回到本章目录
(2)功能。不带参数的宏定义的功能是将宏体字符串符号化。在程序中凡出现该宏名的位置,经编译预处理的加工,都被替换成对应的宏体字符串,称之为“宏展开”。(2)功能。不带参数的宏定义的功能是将宏体字符串符号化。在程序中凡出现该宏名的位置,经编译预处理的加工,都被替换成对应的宏体字符串,称之为“宏展开”。 由于宏定义不是C语句,不必也不能在行末加分号。 返回到本章目录
2.关于宏的几点说明 (1)使用宏名代替一个字符串,可以减少程序中重复书写某些字符串的工作量,增加程序的可读性,而且不易出错。 (2)编译预处理时,将程序中PI用3.1415926代替,与宏调用的过程相反,这种将宏名替换成字符串的过程称为“宏展开”。 (3)C语言中,用宏名替换一个字符串是简单的转换过程,不作语法检查。若将宏体的字符串中符号写错了,宏展开时照样代入,只有在编译宏展开后的源程序时才会提示语法错误。 返回到本章目录
2.关于宏的几点说明 (4)一个宏名只能被定义一次,否则会出现重复定义的错误。 (5)宏定义可以嵌套。在宏体中,可以出现已定义的宏名,例如: #define PI 3.1415926 #define PIR (PI*r) /*PI为已定义的宏名*/ (6)如果宏定义一行书写不下,可用反斜线“\”和回车键来结束本行,然后在下一行继续书写。 返回到本章目录
2.关于宏的几点说明 (7)程序中出现的由双引号括起来的字符串,即使和宏名相同,也不进行宏替换。例如,在输出函数printf()中如果在双引号内有与宏名相同的字符串,也不认为是宏,只认为是普通字符串原样输出。 (8)宏定义命令行放在源程序的函数外时,宏名的作用域从宏定义命令行开始到本源文件结束。 (9)宏名的作用域可以使用#undef命令终止,形式如下: #undef 标识符 返回到本章目录
二、带参数的宏定义 【思考题6-2】求两个数a和b中的最大值,要求用带参数的宏定义来实现。 (一)程序分析 这个程序可以使用函数来实现,但这里我们要求用带参数的宏定义来实现该功能。要注意带参数的宏定义的格式及在编译前进行替换的方式。 返回到本章目录
(二)编写程序代码 #define MAX(a,b) ((a)>(b)?(a):(b)) /*MAX为带参数的宏定义,a、b为其参数*/ main() { int a,b,max; printf("\nPlease input 2 integer number:"); scanf("%d%d",&a,&b); max=MAX(a,b); /*求a和b中的最大数,MAX为带参数的宏定义*/ printf("the max value is :%d",max); } 返回到本章目录
(三)调试程序及运行结果 程序输入两个整数3和5,程序运行结果如下: 返回到本章目录
3.带参数的宏定义 1)带参数宏定义的一般格式 2)带参数的宏的调用和展开 (1)带参数的宏的调用格式如下 (2)带参数的宏展开。带参数的宏展开是用宏调用提供的实参字符串,直接置换宏定义命令行中相应形参字符串,非形参字符保持不变。 #define宏名(形式参数表) 字符串 宏名(实参表) 返回到本章目录
4.关于带参数的宏定义的几点说明(1)定义有参数的宏时,宏名应当与参数表的左括号紧紧相连。否则,C编译系统将空格以后的所有字符均作为替代字符串,而将该宏视为无参宏。4.关于带参数的宏定义的几点说明(1)定义有参数的宏时,宏名应当与参数表的左括号紧紧相连。否则,C编译系统将空格以后的所有字符均作为替代字符串,而将该宏视为无参宏。 (2)宏定义时,应将整个字符串以及其中的各个参数均用圆括号括起来,以确保宏展开后字符串中各个参数的计算顺序的正确性,避免出现错误。 (3)在宏定义中的形参是标识符,而宏展开的实参可以是表达式。 (4)虽然有参数的宏与有参数的函数确实有相似之处,但不同之处更多,主要有以下几个方面。 返回到本章目录
① 调用有参函数时,是先求出实参的值,然后再复制一份给形参。而展开有参宏时,只是将实参简单地置换形参。 ② 在有参函数中,形参是有类型的,所以要求实参的类型与其一致;而在有参宏中,形参是没有类型信息的,因此用于置换的实参,什么类型都可以。有时,可利用有参宏的这一特性,实现通用函数的功能。 ③ 函数调用是在程序运行中处理的,临时给它分配存储单元。而宏代换是在编译时进行的,并不给它分配存储单元,不进行数值的传递,也没有返回值。 ④ 函数的形参和实参都要求定义类型,而且二者要求一致,若二者不一致时,还要进行类型转换。而对于宏则不存在此类问题,宏没有类型,参数也没有类型,它们都仅是一种符号代表,宏展开时只是进行对应符号的代换。 ⑤ 在程序中使用宏时,宏展开后源程序会变长。而函数调用不会使源程序变长。 ⑥ 宏代换不占程序运行时间,只占编译时间。而函数调用则占运行时间(分配单元、保留现场、值传递、返回)。 返回到本章目录
三、文件包含处理 【思考题6-3】修改【思考题6-2】,要求将【思考题6-2】中的带参数宏定义语句单独放在一个头文件中,主函数单独放在一个源程序文件中,功能同【思考题6-2】。 (一)程序分析 将一部分内容放到一个头文件中,在编译预处理时,将头文件内容先替换到文件包含语句部分,然后再进行编译。注意头文件和源程序的组成。 返回到本章目录
(二)编写程序代码 (1)in6_3.h的内容: #define MAX(a,b) ((a)>(b)?(a):(b)) /*MAX为带参数的宏定义,a、b为其参数*/ (2)主程序内容: #include "in6_3.h" /*文件包含命令,将in6_3.h头文件包含到该程序中*/ main() { int a,b,max; printf("\nPlease input 2 integer number:"); scanf("%d%d",&a,&b); max=MAX(a,b); /*求a和b中的最大数,MAX为带参数的宏定义*/ printf("the max value is :%d",max); } (三)调试程序及运行结果 程序运行结果同【思考题6-2】的例程结果。 返回到本章目录
5.文件包含处理 (1)编译预处理的文件包含功能。 ① 编译预处理的文件包含功能是一个源程序通过#include命令把另外一个源文件的全部内容嵌入到源程序中来。 ② 编译预处理程序把#include命令行中所指定的源文件的全部内容放到源程序的#include命令行所在的位置。 ③ 在编译时并不是作为两个文件连接,而是作为一个源程序编译,得到一个目标文件。 返回到本章目录
(2)文件包含#include命令格式主要有以下两种。(2)文件包含#include命令格式主要有以下两种。 ① 只检索C语言编译系统所确定的标准目录,格式如下: ② 首先对使用包含文件的源文件所在的目录进行检索,若没有找到指定的文件,再在标准目录中检索,格式如下: (3)#include命令的嵌套使用。当一个程序中使用#include命令嵌入一个指定的包含文件时,被嵌入的文件中还可以使用#include命令,又可以包含另外一个指定的包含文件。 #include <文件名> #include "文件名" 返回到本章目录
四、条件编译 【思考题6-4】输入一行字母字符,根据需要设置条件编译,使之能将字母全改为大写输出,或全改为小写字母输出。 (一)程序分析 定义一个常量LETTER,通过判断LETTER的值来对某些程序进行条件编译。当LETTER值为1时将字符串str中的全部字符转换成大写字母,否则将大写字母转换成小些字母。注意条件编译的语句格式。 返回到本章目录
(二)编写程序代码 #include "string.h" #define LETTER 1 main() { char str[100], c; int i; i=0; printf("\nPlease input a string:\n"); gets(str); /*注意:如果要输入带空格的字符串必须用gets函数*/ 返回到本章目录
(三)调试程序及运行结果 输入一个字符串"Hello,all the world people!",程序将这一串小写字符变成大写字符"HELLO,ALL THE WORLD PEOPLE!"。程序运行结果如下: 返回到本章目录
while((c=str[i])!='\0') { i++; #ifdef LETTER if(c>='a' && c<='z') c=c-32; #else if(c>='A' && c<='Z') c=c+32; #endif printf("%c", c); } } 返回到本章目录
6.条件编译 所谓条件编译,是指对源程序进行选择性编译。 1)#ifdef命令(或#ifndef命令) (1)#ifdef命令的一般格式如下 (2)功能。#ifdef命令的功能是如果“标识符”已经被#define命令定义过,则编译程序段1,否则编译程序段2。 #ifdef 标识符 程序段1 [#else 程序段2] #endif 返回到本章目录
在不同的系统中,一个int 型数据占用的内存字节数可能是不同的。 利用条件编译,还可使同一源程序既适合于调试(进行程序跟踪、打印较多的状态或错误信息),又适合高效执行要求。 #ifndef命令格式与#ifdef命令一样,功能正好与之相反。 返回到本章目录
2)#if命令 (2)#if命令一般格式如下: (2)功能。#if命令的功能是当表达式为非0(“逻辑真”)时,编译程序段1,否则编译程序段2。 #if 常量表达式 程序段1 [#else 程序段2] #endif 返回到本章目录
(3)条件编译和if语句的区别。 ① if语句控制某些语句是否被执行,#if命令控制着某个程序段是否被编译。 ② 用if语句调试程序成功后,其调试语句仍被编译成目标代码,只是不再执行,成为废码。而使用条件编译调试程序成功后,其调试语句不再被编译,不生成目标代码,没有废码产生,空间借用率较高。 返回到本章目录
练一练 【练习6-1】设计一个程序,从3个数中找最大数,用带参数的宏定义实现。 解:因为条件语句可以通过一条语句实现求3个数中最大值的功能,所以可以设一个带3个参数的宏定义,宏的值用一个条件语句来实现。 注意:在宏体部分的每一个参数和整个宏体都要用圆括号括起来,以防止宏展开时出错。 返回到本章目录
源程序如下: #define MAX(a,b,c) ((a)>(b)?((a)>(c)?(a):(c)):((b)>(c))?(b):(c)) main() { int a,b,c,max; printf("\nPlease input 3 int number:"); scanf("%d%d%d",&a,&b,&c); max= MAX(a,b,c); printf("\nThe max number is:%d",max); } 程序运行结果如下: 返回到本章目录
【练习6-2】用条件编译方法实现以下功能:输入一行电报文字,任选两种输出方式,一为原文输出,二为将字母变成其下一个字母(如'a'变成'b','b'变成'c',依次类推,最后'z'变成'a',其他字符不变)。用#define命令来控制是否要译成密码,例如:【练习6-2】用条件编译方法实现以下功能:输入一行电报文字,任选两种输出方式,一为原文输出,二为将字母变成其下一个字母(如'a'变成'b','b'变成'c',依次类推,最后'z'变成'a',其他字符不变)。用#define命令来控制是否要译成密码,例如: #define CHANGE 1 则输出密码。 若: #define CHANGE 0 则不译成密码,按原文输出。 返回到本章目录
解:当字母不为'z'或'Z'时,将字母变成其下一个字母,就是将字母的ASCII码值加1即可实现;当字母为'z'或'Z'时,令该字母等于'a'或'A'。条件编译可以用#ifdef命令来实现。解:当字母不为'z'或'Z'时,将字母变成其下一个字母,就是将字母的ASCII码值加1即可实现;当字母为'z'或'Z'时,令该字母等于'a'或'A'。条件编译可以用#ifdef命令来实现。 源程序如下: #include "string.h" #define CHANGE 1 /*可将1改为0,则进行字母替换*/ main() { char str[100],ch; int i,len; printf("\nPlease input a string:"); gets(str); len=strlen(str); 返回到本章目录
#if CHANGE for(i=0;i<len;i++) if(str[i]>= 'a'&&str[i]<'z'|| str[i]>= 'A'&&str[i]<'Z') str[i]=str[i]+1; else { if(str[i]== 'z') str[i]= 'a'; else if(str[i]== 'Z') str[i]= 'Z'; } #endif puts(str); } 返回到本章目录
程序运行结果如下: 返回到本章目录
本章小结 • C程序的编译可分为编译预处理和正式编译两个步骤。编译预处理是在将源程序生成目标文件之前,对源程序的加工。正确使用编译预处理命令可以有效提高程序的开发效率。 • C语言提供了多种预处理命令,常用的有宏定义、文件包含和条件编译。 • 宏定义命令为#define,是用一个标识符来代表一个字符串,这个字符串可以是常量、变量或表达式。在宏展开中将用该字符串代替宏名。宏定义还可以带参数,宏展开时除用字符串代替宏名外,还应用实参代替形参。 返回到本章目录
本章小结 • 文件包含命令为#include,它可以把一个或多个文件嵌入到另一个源文件中进行编译,结果将生成一个目标文件。 • 条件编译根据条件成立与否,选择编译程序中满足条件的程序段,便于程序调试,并使生成的目标程序较短,减少内存开销,提高程序运行效率。 返回到本章目录