资源描述
第九章预处理命令,概述ANSIC标准规定可以在C源程序中加入一些“预处理命令”(preprocessordirectives),以改进程序设计环境,提高编程效率。这些预处理命令是由ANSIC统一规定的,但它们不是C语言本身的组成部分,更不是C语句。编译程序不能识别预处理命令,它们必须在对程序进行通常的编译(包括词法和语法分析、代码生成、优化等)之前被“预处理”,即在编译前先根据预处理命令的要求对程序做出相应的处理。经过预处理后,程序不再含有预处理命令了,然后再由编译程序对预处理后的源程序进行通常的编译处理,得到目标代码。,现在使用的许多C编译系统都包括了预处理、编译和连接等部分,在进行编译时一气呵成。我们必须正确区别预处理命令和C语句、区别预处理和编译,才能正确使用预处理命令。C语言与其它高级语言的一个重要区别是可以使用预处理命令和具有预处理的功能。C语言提供的预处理功能主要有以下三种:1.宏定义2.文件包含3.条件编译分别用相应的宏定义命令、文件包含命令和条件编译命令来实现。为了与一般C语句相区别,这些命令以符号“#”开头。,9.1宏定义,9.1.1不带参数的宏定义在C语言中,我们可以定义一个指定的标识符来代替程序中的一个字符串,这种定义称为“宏定义”,这个标识符(名字)称为“宏名”。一般定义形式为:#define标识符字符串这就是已经介绍过的符号常量的定义。如:#definePI3.1415926它的作用是:在编译预处理时,将程序中在该命令以后出现的所有的标识符PI都替换为3.1415926这个字符串。在预编泽时,将宏名替换成字符串的过程称为“宏展开”。define是宏定义命令。,例9.1#definePI3.1415926main()floatl,s,r,v;printf(inputradius:);scanf(%f,说明:1)宏名一般约定用大写字母表示,以便与变量名相区别。2)使用宏定义,可以提高程序的通用性,能作到“一改俱改”。3)宏定义是用宏名代替一个字符串,只作简单的替换操作,不作正确性检查。如果词义或语义错误,只有在编译已被宏展开后的源程序时才会发现错误并报错。例如:#definePI3.14l59把第二个“1”写成“l”。这样的错误在预处理时是不会被发现的,只有在编译时才会被发现井报错。4)宏定义不是C语句,不要随便在行末加分号。如果加了分号,则会连同分号一起进行替换。5)#define命令出现在程序中函数的外面,宏名的有效范围为宏定义之后到本源文件末。通常,#define命令写在文件开头,函数之前,作为文件一部分,在此文件范围内有效。,6)可用#undef命令来终止宏定义的作用域。这样可以灵活控制宏定义的作用范围。7)在进行宏定义时,又可引用已定义的宏名,实现层层置换。8)对程序中用双引号括起来的字符串内的字符,即使与宏名相同,也不进行置换。如下例printf函数的格式控制串中的L和S字符,它们不被置换。例9.2#defineR3.0#definePI3.1415926#defineL2*PI*R#defineSPI*R*Rmain()printf(“L=%fnS=%fn”,L,S);,运行结果为:L=18.849556S=28.274333,printf(“L=%fnS=%fn”,2*3.1415926*3.0,3.1415926*3.0*3.0);,9.1.2带参数的宏定义,带参数的宏定义也是一种替换操作,但它要进行两次替换(宏名字符串被简单替换和实参字符串简单替换形参)。其定义的一般形式为:#define宏名(参数表)字符串字符串中包含有参数表中所指定的参数。例如:#defineS(a,b)a*barea=S(2,3);其中S(2,3)相当与2*3。带参宏定义的置换过程:在程序中如果有带实参的宏(例如S(3,2),则按define命令行中指定的字符串从左到右进行置换;如果字符串中含有宏中的形参(如a,b),则将相应的实参字符串(可以是常量、变量或表达式)代替形参;如果字符串中的字符不是参数字符(如上例中*),则原样保留。这样,便形成了置换的字符串。,例9.3#definePI3.14#defineS(r)PI*r*rmain()floata,area;a=3.6;area=S(a);printf(“r=%fnarea=%fn”,a,area);说明:1)对带参数的宏的展开只是将宏名后面括号内的实参字符串代替define命令行中的形参。如果有以下语句:area=S(2+3);,与3.14*a*a相同,再相应的宏展开形式为:area=3.14*5*5;area=3.14*(2+3)*(2+3);area=3.14*2+3*2+3;请注意在a+b外面没有括弧,显然这与程序设计者的原意不符。应当在定义时,在字符串中的形式参数外面加一个括弧。即#defineS(r)PI*(r)*(r)只有这样才会得到:area=PI*(2+3)*(2+3);2)宏定义时,在宏名与带参数的括弧之间不应加空格,否则将空格以后的字符都作为宏名所代替的字符串。,说明一点:P192-193中的例9.4和例9.5,只要求大家理解就行了,这两种用法均无实际意义,我个人不提倡这种用法。,带参数的宏和函数的比较:相似之处:在引用函数时,也是在函数名后的括弧内写实参,也要求实参与形参的数目相等,但这只是表面、形式上的相似。带参的宏定义与函数的区别:,1)函数调用时,要先求解实参表达式的值,然后代入给形参;而使用带参的宏,只是进行简单的字符替换。2)函数调用是在程序运行时处理的,要分配内存单元(栈);而宏展开是在预处理时进行的,在展开时并不分配内存单元,不进行值的传递处理,更没有“返回值”的概念。这是两者最本质的区别。3)函数中的实参和形参都要定义类型,二者的类型要求一致,不一致,应进行类型转换;而宏不存在类型问题,宏名无类型,它的参数也无类型,只是代表一个符号。4)每进行一次宏展开都会使源程序增长,而函数调用不会。5)宏替换不占运行时间,只占预处理时间。而函数调用则要占运行时间(分配单元、保现场、值传递、返回)。,9.2“文件包含”处理,所谓“文件包含”处理是指一个源文件将另外的源文件的全部内容包含到本文件中。C语言提供#include命令来实现“文件包含”的操作。一般形式为:#inc1ude“文件名”或#include,#include,file1.c,A,B,B,file2.c,常用在文件头部的被包含文件称为“标题文件”或“头部文件”,常以“h”为后缀(h为head的缩写),如“stdio.h”文件。当然不用“h”为后缀,而用“c”为后缀或者没有后缀也是可以的,但用“h”作后缀更能表示此文件的性质。,说明:1)一个#include命令只能指定一个被包含文件,如果要包含n个文件,要用n个#include命令。2)如果file1.c文件包含file2.h文件,而文件file2.h要用到file3.h的内容,则可在file1.c中用两个#include命令分别包含文件file2.h和file3.h,而且file3.h应出现在file2.h之前。即:#include“file3.h”#include“file2.h”这样file1.c和file2.h都可以用到file3.h的内容,并且file2的源文件中不用再声明#include“file3.h”。3)在一个被包含文件中又可以包含另一个被包含文件,即文件包含可以嵌套。4)在#include命令中,文件名要用双引号或尖括号括起来。双引号表示编译系统先在用户当前目录中寻找要包含的文件,若没有,则在C库函数头文件所在的目录中寻找(尖括号的含义)。5)被包含文件(file2.h)与其所在的文件(file1.c)在预处理之后已成为同一个文件。因此在file2.h中若有全局静态变量,它在file1.c中也同样有效,不必用extern声明。,9.3条件编译(自学),一般情况下,源程序中的所有行都要参加编译。但是,有时希望对源程序中某些内容只在满足一定条件时才进行编译,这就是“条件编译”。条件编译的形式有:(1)#ifdef标识符程序段1#else程序段2#endif它的作用是当所指定的标识符已经被#define命令定义过,则在程序编译阶段只编译程序段1,否则编译程序段2。,其中#else部分可以没有,即#ifdef标识符程序段1#endif这里的“程序段”可以是语句组,也可以是命令行。这种条件编译对于提高C源程序的通用性是很有好处的。例如:如果一个C源程序要在不同的计算机系统上运行,而不同的计算机系统又存在一定的差异(例如,有的机器以16位来存放一个整数,而有的则以32位存放一个整数),这样往往需要对源程序作必要的修改,这就降低了程序的通用性,可以用以下的条件编译来处理。#ifdefCOMPUTER_A#defineINTEGER_SIZE16#else#defineINTEGER_SIZE32#endif,如果COMPUTER_A在前面已被定义过,则编译下面的命令行:#defineINTEGER_SIZE16否则,编译下面的命令行:#defineINTEGER_SIZE32如果在这组条件编译命令之前曾出现以下命令行:#defineCOMPUTER_A0或将COMPUTER_A定义为任何字符串,甚至是:#defineCOMPUTER_A则预处理后程序中的INTEGER_SIZE都用16代替,否则都用32代替。这样,源程序可以不必作任何修改就可以用于不同类型的计算机系统。,又如:在程序调试时,常常希望输出一些所需的信息,而在调试完成后不再输出这些信息。可以在源程序中插入以下的条件编译段。#ifdefDEBUGprintf(“x=%d,y=%d,z=%dn”,x,y,z);#endif如果在它的前面有以下命令行:#defineDEBUG则在程序运行时输出x,y,z的值,以便调试时分析。调试完成后只需将命令行#defineDEBUG删去即可。,(2)#ifndef标识符程序段1#else程序段2#endif它的作用是当所指定的标识符未被#define命令定义过,则在程序编译阶段只编译程序段1,否则编译程序段2。这种形式与第一种形式相似,只是在作用上相反。以上两种形式用法差不多,根据需要任选一种,视方便而定。,(3)#if表达式程序段1#else程序段2#endif它的作用是:当指定的表达式值为真(非零)时就编译程序段1,否则编译程序段2。不用条件编译命令而直接用if语句也能达到要求,但用条件编译命令有如下好处:可以减少被编译的语句,从而减少目标程序的长度,减少运行时间。当使用条件编译的程序段比较多时,目标程序的长度可以大大减少。OK,本章介绍的预编译功能是C语言所特有的,有利于提高程序的通用性、可移植性,使程序变得更加灵活。,第九章编译预处理,9.1宏定义9.2“文件包含”处理9.3条件编译,
展开阅读全文