《预处理器》PPT课件.ppt
第第14章章l 本章要点引言引言v指令如指令如#define和和#include等是由预处理器进行处理的,等是由预处理器进行处理的,而预处理器是一个在编译前编辑而预处理器是一个在编译前编辑C程序的软件程序的软件.vC语言(和语言(和C+语言)因为依赖预处理器而不同于其他的编语言)因为依赖预处理器而不同于其他的编程语言。程语言。v预处理器是一个强大的工具,但它同时也可能是许多难以发预处理器是一个强大的工具,但它同时也可能是许多难以发现的错误的根源。现的错误的根源。14.1预处理器的工作原理预处理器的工作原理v预处理器查找以预处理器查找以#开头的预处理指令开头的预处理指令.v前面,我们已经遇到过前面,我们已经遇到过#define和和#include指令指令.v#define指令定义一个指令定义一个宏(宏(macro)即一个能够代表其即一个能够代表其它东西的名字,比如一个常量。它东西的名字,比如一个常量。v预处理器会通过将宏的名字和它的定义存储在一起来响应预处理器会通过将宏的名字和它的定义存储在一起来响应#define指令。指令。v当这个宏在后面的程序中使用到时,预处理器当这个宏在后面的程序中使用到时,预处理器“扩展扩展”了宏,了宏,将宏替换为它所定义的值。将宏替换为它所定义的值。预处理器的工作原理预处理器的工作原理v#include指令告指令告诉预处诉预处理器打开一个特定的文件,将它的理器打开一个特定的文件,将它的内容作内容作为为正在正在编译编译的文件的一部分的文件的一部分“包含包含”进进来。来。v例如,下面一行:例如,下面一行:#includev指示指示预处预处理器打开一个名字理器打开一个名字为为的文件,并将它的内容加到的文件,并将它的内容加到当前的程序中。当前的程序中。预处理器的工作原理预处理器的工作原理v在编译过程中中,预处理器的角色如图所示:在编译过程中中,预处理器的角色如图所示:预处理器的工作原理预处理器的工作原理v预处理器的输入是一个预处理器的输入是一个C语言程序,程序可能包含指令。语言程序,程序可能包含指令。v预处理器会执行这些指令,并在处理过程中删除这些指令。预处理器会执行这些指令,并在处理过程中删除这些指令。v预处理的输出是另一个预处理的输出是另一个C程序:原程序编辑后的版本。程序:原程序编辑后的版本。v预处理器的输出被直接交给编译器。预处理器的输出被直接交给编译器。预处理器的工作原理预处理器的工作原理v第二章中的第二章中的:/*ConvertsaFahrenheittemperaturetoCelsius*/#include#defineSCALE_FACTOR(5.0f/9.0f)intmain(void)floatfahrenheit,celsius;printf(EnterFahrenheittemperature:);scanf(%f,&fahrenheit);celsius=(fahrenheit-FREEZING_PT)*SCALE_FACTOR;printf(Celsiusequivalentis:%.1fn,celsius);return0;预处理器的工作原理预处理器的工作原理v预处理过后为预处理过后为:Blank lineBlank lineLines brought in from Blank lineBlank lineBlank lineBlank lineintmain(void)floatfahrenheit,celsius;printf(EnterFahrenheittemperature:);scanf(%f,&fahrenheit);celsius=(fahrenheit-32.0f)*(5.0f/9.0f);printf(Celsiusequivalentis:%.1fn,celsius);return0;预处理器的工作原理预处理器的工作原理v预处理器不仅仅是执行了指令,还做了一些其他的事情。预处理器不仅仅是执行了指令,还做了一些其他的事情。v特别值得注意的是,它将每一处注释都替换为一个空格字符。特别值得注意的是,它将每一处注释都替换为一个空格字符。v有一些预处理器还会进一步删除不必要的空白字符,包括在有一些预处理器还会进一步删除不必要的空白字符,包括在每一行开始用于缩进的空格符和制表符。每一行开始用于缩进的空格符和制表符。v在在C语言较早的时期,预处理器是一个单独的程序,并将它语言较早的时期,预处理器是一个单独的程序,并将它的输出提供给编译器。的输出提供给编译器。v如今,预处理器通常和编译器集成在一起如今,预处理器通常和编译器集成在一起(为了提高编译的为了提高编译的速度速度)。v在理解上,将预处理器从编译器分开是比较有用的。在理解上,将预处理器从编译器分开是比较有用的。预处理器的工作方式预处理器的工作方式v大多数编译器提供查看预处理输出的方法。大多数编译器提供查看预处理输出的方法。v一些编译器通过使用特定选项来产生预处理结果(如一些编译器通过使用特定选项来产生预处理结果(如gccE)。v其它一些编译环境带有类似集成预处理器的独立的程序。其它一些编译环境带有类似集成预处理器的独立的程序。v注意,预处理器仅知道少量注意,预处理器仅知道少量C语言的规则,因此,它在执行语言的规则,因此,它在执行指令时非常有可能产生非法的程序。指令时非常有可能产生非法的程序。v对于较复杂的程序,检查预处理器的输出可能是找到这类错对于较复杂的程序,检查预处理器的输出可能是找到这类错误的有效途径。误的有效途径。14.2预处理指令预处理指令v大多数预处理指令属于下面大多数预处理指令属于下面3种类型之一种类型之一:宏定义。宏定义。#define指令定义一个宏,指令定义一个宏,#undef指令删除一个宏定义。指令删除一个宏定义。文件包含。文件包含。#include指令导致一个指定文件的内容被包含到程序中。指令导致一个指定文件的内容被包含到程序中。条件编译。条件编译。#if、#ifdef、#ifndef、#elif、#else和和#endif指令可以根指令可以根据测试的条件来将一段文本块包含到程序中或排除在程序之外。据测试的条件来将一段文本块包含到程序中或排除在程序之外。v几条应用于所有指令规则:几条应用于所有指令规则:指令都以指令都以#开始。开始。#符号不需要在一行的行首,只要它之前只有空白符号不需要在一行的行首,只要它之前只有空白字符就行。在字符就行。在#后是指令名,接着是指令所需要的其他信息。后是指令名,接着是指令所需要的其他信息。在指令的符号之间可以插入任意数量的空格或横向制表符。例如,在指令的符号之间可以插入任意数量的空格或横向制表符。例如,下面的指令是合法的:下面的指令是合法的:#define N100预处理指令预处理指令v指令总是在第一个换行符处结束,除非明确地指明要继续。如果想在下指令总是在第一个换行符处结束,除非明确地指明要继续。如果想在下一行继续指令,我们必须在当前行的末尾使用一行继续指令,我们必须在当前行的末尾使用字符,例如:字符,例如:#defineDISK_CAPACITY(SIDES*TRACKS_PER_SIDE*SECTORS_PER_TRACK*BYTES_PER_SECTOR)v指令可以出现在程序中指令可以出现在程序中任何地方任何地方。我们通常将。我们通常将#define和和#include指令指令放在文件的开始,其他指令则放在后面,甚至在函数定义的中间。放在文件的开始,其他指令则放在后面,甚至在函数定义的中间。v注释可以与指令放在同一行。实际上,在一个宏定义的后面加一个注释注释可以与指令放在同一行。实际上,在一个宏定义的后面加一个注释来解释宏的意义是一种比较好的习惯:来解释宏的意义是一种比较好的习惯:#defineFREEZING_PT32.0f/*freezingpointofwater*/14.3宏定义宏定义v我们从第我们从第2章以来使用的宏被称为简单的宏,它们没有参数,章以来使用的宏被称为简单的宏,它们没有参数,预编译器也支持带参数的宏。预编译器也支持带参数的宏。v简单的宏定义有如下格式:简单的宏定义有如下格式:#define标识标识符符替替换换列表列表v替换列表替换列表是一系列的是一系列的C语言记号,包括标识符、关键字、数、语言记号,包括标识符、关键字、数、字符常量、字符串字面量、运算符和标点符号。字符常量、字符串字面量、运算符和标点符号。v在文件后面的内容中,不管在文件后面的内容中,不管标识符标识符在任何位置出现,预处理在任何位置出现,预处理器都会用器都会用替换列表替换列表代替它。代替它。14.3.1简单的宏简单的宏v不要在宏定义中放置任何额外的符号,否则它们会被作为替换列表的一不要在宏定义中放置任何额外的符号,否则它们会被作为替换列表的一部分。部分。v一种常见的错误是在宏定义中使用一种常见的错误是在宏定义中使用=,如,如:#defineN=100/*WRONG*/intaN;/*becomesinta=100;*/v在宏定义的末尾使用分号结尾是另一个常见错误,如:在宏定义的末尾使用分号结尾是另一个常见错误,如:#defineN100;/*WRONG*/intaN;/*becomesinta100;*/v编译器将会检测到绝大多数由多余符号所导致的错误。编译器将会检测到绝大多数由多余符号所导致的错误。v编译器会将每一处使用这个宏的地方标为错误,而不会直接找到错误的编译器会将每一处使用这个宏的地方标为错误,而不会直接找到错误的根源根源宏定义本身,因为宏定义已经被预处理器删除了。宏定义本身,因为宏定义已经被预处理器删除了。简单的宏简单的宏v简单的宏可以定义简单的宏可以定义“明示常量明示常量manifestconstants”#defineSTR_LEN80#defineTRUE1#defineFALSE0#defineCRr#defineEOS0#defineMEM_ERRError:notenoughmemory简单的宏简单的宏v#define的的优优点:点:使程序更容易使程序更容易读读。使程序更容易修改。使程序更容易修改。避免前后不一致的避免前后不一致的输输入入错误错误,例如,例如,误输误输入入为为或或。v可以可以对对C语语法做小的修改。法做小的修改。#defineBEGIN#defineEND#defineLOOPfor(;)v对类型重命名。对类型重命名。#defineBOOLintv控制条件编译。控制条件编译。#defineDEBUG14.3.2带参数的宏带参数的宏v带参数的宏定义有如下格式:带参数的宏定义有如下格式:#define标识符标识符(x1,x2,xn)替换列表替换列表其中其中x1,x2,xn是标识符是标识符(宏的参数宏的参数)。v在宏的名字和左括号之间必须没有空格。在宏的名字和左括号之间必须没有空格。v如果有空格,预处理器会认为是在定义一个简单的宏,其中如果有空格,预处理器会认为是在定义一个简单的宏,其中(x1,x2,xn)是替换列表的一部分。是替换列表的一部分。带参数的宏带参数的宏v带参数的宏举例:带参数的宏举例:#defineMAX(x,y)(x)(y)?(x):(y)#defineIS_EVEN(n)(n)%2=0)v调用这些宏:调用这些宏:i=MAX(j+k,m-n);if(IS_EVEN(i)i+;v宏替换后:宏替换后:i=(j+k)(m-n)?(j+k):(m-n);if(i)%2=0)i+;带参数的宏带参数的宏v复杂的类似函数的宏复杂的类似函数的宏:#defineTOUPPER(c)(a=(c)&(c)=z?(c)-a+A:(c)v头提供了一个更容易移植的相似函数头提供了一个更容易移植的相似函数toupper。v带参数的宏也可能具有空的参数列表:带参数的宏也可能具有空的参数列表:#definegetchar()getc(stdin)v空的参数列表不是一定确实需要,但可以使空的参数列表不是一定确实需要,但可以使getchar更像更像一个函数。一个函数。带参数的宏带参数的宏v使用带参数的宏替代实际的函数有两个优点:使用带参数的宏替代实际的函数有两个优点:程序可能会稍微快些。一个函数调用在执行时通常会有些额外开销程序可能会稍微快些。一个函数调用在执行时通常会有些额外开销存储上下文信息、复制参数的值等。而一个宏的调用则没有这存储上下文信息、复制参数的值等。而一个宏的调用则没有这些运行开销。些运行开销。宏会更宏会更“通用通用”。与函数的参数不同,宏的参数没有类型。只要预。与函数的参数不同,宏的参数没有类型。只要预处理后的程序依然是合法的,宏可以接受任何类型的参数。处理后的程序依然是合法的,宏可以接受任何类型的参数。v带参数的宏也有一些缺点。带参数的宏也有一些缺点。编译后的代码通常会变大。每一处宏调用都会导致插入宏的替换列编译后的代码通常会变大。每一处宏调用都会导致插入宏的替换列表,由此导致程序的源代码增加表,由此导致程序的源代码增加(因此编译后的代码变大因此编译后的代码变大)。当宏调用嵌套时,这个问题会相互叠加从而使程序更加复杂。当宏调用嵌套时,这个问题会相互叠加从而使程序更加复杂。vn=MAX(i,MAX(j,k);/*预处理后预处理后*/n=(i)(j)(k)?(j):(k)?(i):(j)(k)?(j):(k);14.3.3#运算符运算符v宏定义可以包含两个运算符:宏定义可以包含两个运算符:#和和#。v编译器不会识别这两种运算符;相反,它们会在预处理时被编译器不会识别这两种运算符;相反,它们会在预处理时被执行。执行。v#运算符将一个宏的参数转换为字符串字面量。它仅允许出运算符将一个宏的参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。现在带参数的宏的替换列表中。v#运算符有大量的用途,这里只来讨论其中的一种。运算符有大量的用途,这里只来讨论其中的一种。v假设我们决定在调试过程中使用假设我们决定在调试过程中使用PRINT_INT宏作为一个便捷宏作为一个便捷的方法,来输出一个整型变量或表达式的值。的方法,来输出一个整型变量或表达式的值。v#运算符可以使运算符可以使PRINT_INT为每个输出的值添加标签。为每个输出的值添加标签。#运算符运算符v改进后的改进后的PRINT_INT:#definePRINT_INT(n)printf(#n=%dn,n)调调用用PRINT_INT(i/j);将转换为将转换为printf(i/j=%dn,i/j);v编译器自动连接两个相邻的字符串字面量,因此上面语编译器自动连接两个相邻的字符串字面量,因此上面语句等价为:句等价为:printf(i/j=%dn,i/j);14.3.4#运算符运算符v#运算符可以将两个记号运算符可以将两个记号(例如标识符例如标识符)“粘粘”在一起,成为一在一起,成为一个记号。个记号。v如果其中一个操作数是宏参数,如果其中一个操作数是宏参数,“粘合粘合”会在当形式参数被会在当形式参数被相应的实际参数替换后发生。相应的实际参数替换后发生。v使用使用#运算符的宏:运算符的宏:#defineMK_ID(n)i#nv下面声明下面声明调调用用MK_ID三次:三次:intMK_ID(1),MK_ID(2),MK_ID(3);v预处预处理理过过后,后,该语该语句句为为:inti1,i2,i3;#运算符运算符v#运算符不属于运算符不属于预处预处理器理器经经常使用的特性。常使用的特性。v考考虑虑前面提到前面提到过过的的MAX宏,当宏,当MAX的参数有副作用的参数有副作用时时会会无法正常工作;同无法正常工作;同时时,由于参数,由于参数类类型的原因,一个型的原因,一个max函函数也是不数也是不够够的。的。v一种解决方法是用一种解决方法是用MAX宏来写一个宏来写一个max函数,函数,并使它并使它展开后成展开后成为为max函数的定函数的定义义。v宏的参数将用于宏的参数将用于说说明参数和返回明参数和返回值值的的类类型。型。#运算符运算符v问题:如果我们是用宏来创建多个问题:如果我们是用宏来创建多个max函数,程序将无法编译。函数,程序将无法编译。(C语言不允许在同一文件中出现两个同名的函数。语言不允许在同一文件中出现两个同名的函数。)v为了解决这个问题,我们是用为了解决这个问题,我们是用#运算符为每个版本的运算符为每个版本的max函数构造不函数构造不同的名字。下面是宏的显示形式:同的名字。下面是宏的显示形式:#defineGENERIC_MAX(type)typetype#_max(typex,typey)returnxy?x:y;v对该宏的调用:对该宏的调用:GENERIC_MAX(float)v预处理过后的代码为:预处理过后的代码为:floatfloat_max(floatx,floaty)returnxy?x:y;14.3.5宏的通用属性宏的通用属性v同时适用于简单和带参数的宏的规则:同时适用于简单和带参数的宏的规则:v宏的替换列表可以包含对另一个宏的调用,例如:宏的替换列表可以包含对另一个宏的调用,例如:#defineTWO_PI(2*PI)v当预处理器在后面的程序中遇到当预处理器在后面的程序中遇到TWO_PI时,会将它替换成时,会将它替换成(2*PI)。v预处理器会不断重新检查替换列表,直到将所有的宏名字都替换预处理器会不断重新检查替换列表,直到将所有的宏名字都替换掉为止。掉为止。宏的通用属性宏的通用属性v预处理器只会替换完整的记号,而不会替换记号的片断。因此,预处理器只会替换完整的记号,而不会替换记号的片断。因此,预处理器会忽略嵌在标识符名、字符常量、字符串字面量之中的预处理器会忽略嵌在标识符名、字符常量、字符串字面量之中的宏名。例如宏名。例如:#defineSIZE256intBUFFER_SIZE;if(BUFFER_SIZESIZE)puts(Error:SIZEexceeded);预处理过后预处理过后:intBUFFER_SIZE;if(BUFFER_SIZE256)puts(Error:SIZEexceeded);宏的通用属性宏的通用属性v一个宏定义的作用范围通常到出现这个宏的文件末尾。一个宏定义的作用范围通常到出现这个宏的文件末尾。由于宏是由预处理器处理的,他们不遵从通常的范围规则。一个定由于宏是由预处理器处理的,他们不遵从通常的范围规则。一个定义在函数中的宏并不是仅在函数内起作用,而是作用到文件末尾。义在函数中的宏并不是仅在函数内起作用,而是作用到文件末尾。v宏不可以被定义两遍,除非新的定义与旧的定义是一样的。宏不可以被定义两遍,除非新的定义与旧的定义是一样的。小的间隔上的差异是允许的,但是宏的替换列表小的间隔上的差异是允许的,但是宏的替换列表(和参数,如果有的和参数,如果有的话话)中的记号都必须一致。中的记号都必须一致。v宏可以使用宏可以使用#undef指令指令“取消定义取消定义”。#undef指令有如下指令有如下形式:形式:#undef指令指令#undef标识符标识符v#undef指令的一个用途是取消一个宏的现有定义,以便于重指令的一个用途是取消一个宏的现有定义,以便于重新给出新的定义。新给出新的定义。#undefN14.3.6宏定义中的圆括号宏定义中的圆括号v宏定义中的替换列表往往需要圆括号,以免发生意料之外的结果。宏定义中的替换列表往往需要圆括号,以免发生意料之外的结果。v如果宏的替换列表中有运算符,那么始终要将替换列表放在括号中:如果宏的替换列表中有运算符,那么始终要将替换列表放在括号中:#defineTWO_PI(2*3.14159)v如果宏有参数,每次参数在替换列表中出现时都要放在圆括号中:如果宏有参数,每次参数在替换列表中出现时都要放在圆括号中:#defineSCALE(x)(x)*10)v没有括号的话,将无法确保编译器会将替换列表和参数作为完整的表达没有括号的话,将无法确保编译器会将替换列表和参数作为完整的表达式。编译器可能会不按我们期望的方式应用运算符的优先级和结合性规式。编译器可能会不按我们期望的方式应用运算符的优先级和结合性规则。则。v考虑下面的宏定义,其中的替换列表没有添加圆括号:考虑下面的宏定义,其中的替换列表没有添加圆括号:v预处理过程中,语句:预处理过程中,语句:conversion_factor=360/TWO_PI;v成为:成为:conversion_factor=360/2*3.14159;v除法会在乘法之前完成,产生期望之外的结果。除法会在乘法之前完成,产生期望之外的结果。宏定义中的圆括号宏定义中的圆括号v当宏有参数时,仅给替换列表添加圆括号是不够的。参数的当宏有参数时,仅给替换列表添加圆括号是不够的。参数的每一次出现都要添加圆括号。每一次出现都要添加圆括号。v#defineSCALE(x)(x*10)v在预处理过程中,语句:在预处理过程中,语句:j=SCALE(i+1);v变为:变为:j=(i+1*10);v由于乘法的优先级比加法高,这条语句等价于:由于乘法的优先级比加法高,这条语句等价于:j=i+10;14.4条件编译条件编译vC语言的预处理器可以识别大量用于支持条件编译的指令。语言的预处理器可以识别大量用于支持条件编译的指令。v这个功能能够使预处理器根据条件测试结果来决定包含或排这个功能能够使预处理器根据条件测试结果来决定包含或排除程序中的一些片断。除程序中的一些片断。14.4.1#if指令和指令和#endif指令指令v假如我们正在调试一个程序,我们想要程序显示出特定变量的值,假如我们正在调试一个程序,我们想要程序显示出特定变量的值,因此将因此将printf函数调用添加到程序中重要的部分。函数调用添加到程序中重要的部分。v一旦找到错误,经常需要保留这些一旦找到错误,经常需要保留这些printf函数调用,以备以后使函数调用,以备以后使用,条件编译允许我们保留这些调用,但是让编译器忽略它们。用,条件编译允许我们保留这些调用,但是让编译器忽略它们。v首先定义一个宏,并给它一个非首先定义一个宏,并给它一个非0的值:的值:#defineDEBUG1v接下来,我们要在每组接下来,我们要在每组printf函数调用的前后加上函数调用的前后加上#if和和#endif:#ifDEBUGprintf(Valueofi:%dn,i);printf(Valueofj:%dn,j);endif#if指令和指令和#endif指令指令v在预处理过程中,在预处理过程中,#if指令会测试指令会测试DEBUG的值。的值。v由于由于DEBUG的值非的值非0,因此预处理器会将这两个,因此预处理器会将这两个printf函数函数调用保留在程序中调用保留在程序中(但但#if和和#endif行会消失行会消失)。v如果我们将如果我们将DEBUG的值改为的值改为0并重新编译程序,预处理器并重新编译程序,预处理器则会将这则会将这4行代码都删除。行代码都删除。v我们可以将我们可以将#if-#endif保留在最终的程序中,这样如果程序保留在最终的程序中,这样如果程序在运行时出错,可以继续产生这些诊断信息在运行时出错,可以继续产生这些诊断信息(将将DEBUG改为改为1并重新编译并重新编译)。#if指令和指令和#endif指令指令v一般来说,一般来说,#if指令的格式如下:指令的格式如下:#ifconstant-expression#endifv当预处理器遇到当预处理器遇到#if指令时,会计算常量表达式。指令时,会计算常量表达式。v如果表达式的值为如果表达式的值为0,那么在,那么在#if与与#endif之间的行将在预之间的行将在预处理过程中从程序中删除。处理过程中从程序中删除。v否则,这些在否则,这些在#if和和#endif之间的行会被保留在程序中,并之间的行会被保留在程序中,并继续被编译器处理继续被编译器处理这时这时#if和和#endif对程序没有任何影对程序没有任何影响。响。#if指令和指令和#endif指令指令v对于没有定义过的标识符,对于没有定义过的标识符,#if指令会把它当作是值为指令会把它当作是值为0的宏的宏对待。对待。v如果我们没有定义如果我们没有定义DEBUG,则测试:则测试:#ifDEBUG将会失败(但不会产生错误消息)。将会失败(但不会产生错误消息)。v而测试:而测试:#if!DEBUG则会成功。则会成功。36defined运算符运算符v节介绍了节介绍了#和和#运算符,还有一种预处理器运算符:运算符,还有一种预处理器运算符:definedv如果标识符是一个定义过的宏则返回如果标识符是一个定义过的宏则返回1,否则返回,否则返回0。vdefined运算符通常与运算符通常与#if指令结合使用。指令结合使用。#ifdefined(DEBUG)#endifv仅当仅当DEBUG被定义成宏时,被定义成宏时,#if和和#endif之间的代码会被保留在程序中之间的代码会被保留在程序中vDEBUG两侧的括号不是必需的,因此可以简单写成:两侧的括号不是必需的,因此可以简单写成:#ifdefinedDEBUGv也不必给也不必给DEBUG一个值:一个值:#defineDEBUG14.4.3#ifdef指令和指令和#ifndef指令指令v#ifdef指令测试一个标识符是否已经定义为宏:指令测试一个标识符是否已经定义为宏:#ifdefidentifierv其效果与此语句相同:其效果与此语句相同:#ifdefined(identifier)v#ifndef指令测试一个标识符当前是否未被定义为宏:指令测试一个标识符当前是否未被定义为宏:#ifndefidentifierv其效果与此语句相同:其效果与此语句相同:#if!defined(identifier)14.4.4#elif指令和指令和#else指令指令v#if,#ifdef,和和#ifndef可以像普通可以像普通if语语句一句一样样嵌套。嵌套。v当出当出现现嵌套嵌套时时,采用,采用缩进缩进来表示不同的嵌套来表示不同的嵌套层层次是比次是比较较好的好的实实践。践。v某些程序某些程序员员在每个在每个#endif后面留注后面留注释释来表明所匹配的来表明所匹配的测试测试条件:条件:#ifDEBUG#endif/*DEBUG*/v#elif指令和指令和#else指令可以与指令可以与#if指令、指令、#ifdef指令和指令和#ifndef指指令令组组合使用,来合使用,来测试测试一系列条件:一系列条件:#ifexpr1当表达式当表达式1非非0时时需要包含的代需要包含的代码码#elifexpr2当表达式当表达式 1为为 0但表达式但表达式 2非非 0时时需要包含的代需要包含的代码码#else其他情况下需要包含的代其他情况下需要包含的代码码#endifv在在#if指令和指令和#endif指令之指令之间间可以有多个可以有多个#elif指令,但最多只能有一个指令,但最多只能有一个#else指令。指令。