C语言课件:第14章 预处理器

上传人:pu****.1 文档编号:568660519 上传时间:2024-07-25 格式:PPT 页数:56 大小:405.50KB
返回 下载 相关 举报
C语言课件:第14章 预处理器_第1页
第1页 / 共56页
C语言课件:第14章 预处理器_第2页
第2页 / 共56页
C语言课件:第14章 预处理器_第3页
第3页 / 共56页
C语言课件:第14章 预处理器_第4页
第4页 / 共56页
C语言课件:第14章 预处理器_第5页
第5页 / 共56页
点击查看更多>>
资源描述

《C语言课件:第14章 预处理器》由会员分享,可在线阅读,更多相关《C语言课件:第14章 预处理器(56页珍藏版)》请在金锄头文库上搜索。

1、Chapter 14: The Preprocessor1第14章预处理器Chapter 14: The Preprocessor引言指令如#define和#include等是由预处理器进行处理的,而预处理器是一个在编译前编辑C程序的软件.C语言(和C+语言)因为依赖预处理器而不同于其他的编程语言。预处理器是一个强大的工具,但它同时也可能是许多难以发现的错误的根源.2Chapter 14: The Preprocessor14.1 预处理器的工作方式预处理器查找以#开头的预处理指令.#define和#include指令.3Chapter 14: The Preprocessor预处理器的工作方

2、式#define 指令指令定义一个宏(宏( macro)即一个即一个能够代表其它东西的名字,能够代表其它东西的名字,比如一个常量。预处理器会通过将宏的名字和它的定义存储在一起来响应#define指令当这个宏在后面的程序中使用到时,预处理器“扩展”了宏,将宏替换为它所定义的值。4Chapter 14: The Preprocessor预处理器的工作方式#include指令指令告诉预处理器打开一个特定的文件,将它的内容作为正在编译的文件的一部分“包含”进来。例如,下面一行:#include指示预处理器打开一个名字为stdio.h的文件,并将它的内容加到当前的程序中。5Chapter 14: The

3、 Preprocessor预处理器的工作方式在编译过程中中,预处理器的角色如图所示:6Chapter 14: The Preprocessor预处理器的工作方式预处理器的输入是一个C语言程序,程序可能包含指令。预处理器会执行这些指令,并在处理过程中删除这些指令。预处理器的输出被直接交给编译器。预处理器的输出被直接交给编译器。7Chapter 14: The Preprocessor预处理器的工作方式第二章中的celsius.c:/*ConvertsaFahrenheittemperaturetoCelsius*/#include#defineFREEZING_PT32.0f#defineSCA

4、LE_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;8Chapter 14: The Preprocessor预处理器的工作方式预处理过后为:Blank lineBlank lineLines brought in from st

5、dio.hBlank 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;9Chapter 14: The Preprocessor预处理器的工作方式预处理器不仅仅是执行了指令,还做了一些其他的事情。特别值得注意的是

6、,它将每一处注释都替换为特别值得注意的是,它将每一处注释都替换为一个空格字符。一个空格字符。有一些预处理器还会进一步删除不必要的空白有一些预处理器还会进一步删除不必要的空白字符,包括在每一行开始用于缩进的空格符和字符,包括在每一行开始用于缩进的空格符和制表符。制表符。10Chapter 14: The Preprocessor预处理器的工作方式注意:注意:预处理器仅知道少量预处理器仅知道少量 C语言的规则,在语言的规则,在执行指令时非常有可能产生非法的程序。执行指令时非常有可能产生非法的程序。对于较复杂的程序,检查预处理器的输出可能是找到这类错误的有效途径。11Chapter 14: The

7、Preprocessor14.2 预处理指令大多数预处理指令属于下面3种类型之一:宏定义宏定义。#define指令定义一个宏,指令定义一个宏, #undef指令删除指令删除一个宏定义。一个宏定义。 文件包含文件包含。 #include指令导致一个指定文件的内容指令导致一个指定文件的内容被包含到程序中。被包含到程序中。 条件编译条件编译。 #if、#ifdef、#ifndef、#elif、#else和和#endif指令可以根据测试的条件来将一段文本块包指令可以根据测试的条件来将一段文本块包含到程序中或排除在程序之外。含到程序中或排除在程序之外。12Chapter 14: The Preproce

8、ssor预处理指令几条应用于所有指令规则:指令都以指令都以#开始开始。#符号不需要在一行的行首,只要符号不需要在一行的行首,只要它之前只有空白字符就行。在它之前只有空白字符就行。在 #后是指令名,接着后是指令名,接着是指令所需要的其他信息。是指令所需要的其他信息。 在指令的符号之间可以插入任意数量的空格或横向在指令的符号之间可以插入任意数量的空格或横向制表符。制表符。例如,下面的指令是合法的:例如,下面的指令是合法的: # define N 100 13Chapter 14: The Preprocessor预处理指令指令总是在第一个换行符处结束,除非明确地指令总是在第一个换行符处结束,除非明

9、确地指明要继续。指明要继续。如果想在下一行继续指令,必须在当前行的末尾使用字符,例如:#defineDISK_CAPACITY(SIDES*TRACKS_PER_SIDE*SECTORS_PER_TRACK*BYTES_PER_SECTOR)14Chapter 14: The Preprocessor预处理指令指令可以出现在程序中任何地方指令可以出现在程序中任何地方通常将通常将 #define和和#include指令放在文件的开始,指令放在文件的开始,其他指令则放在后面,甚至在函数定义的中间。注释可以与指令放在同一行。注释可以与指令放在同一行。实际上,在一个宏定义的后面加一个注释来解释宏的意义

10、是一种比较好的习惯:#defineFREEZING_PT32.0f/*freezingpointofwater*/15Chapter 14: The Preprocessor14.3 宏定义简单的宏,没有参数。带参数的宏。16Chapter 14: The Preprocessor简单的宏简单的宏定义有如下格式:#defineidentifierreplacement-list #define 标识符 替代列表replacement-list是一系列的C语言记号,包括标识符、关键字、数、字符常量、字符串字面量、运算符和标点符号。在文件后面的内容中,不管identifier在任何位置出现,预处理

11、器都会用replacement-list代替代替它。17Chapter 14: The Preprocessor简单的宏不要在宏定义中放置任何额外的符号,否则它不要在宏定义中放置任何额外的符号,否则它们会被作为替换列表的一部分。们会被作为替换列表的一部分。一种常见的错误是在宏定义中使用=,如:#defineN=100/*WRONG*/intaN;/*becomesinta=100;*/18Chapter 14: The Preprocessor简单的宏在宏定义的末尾使用分号结尾是另一个常见错误,如:#defineN100;/*WRONG*/intaN;/*becomesinta100;*/19

12、Chapter 14: The Preprocessor简单的宏简单的宏主要用来定义那些被称为简单的宏主要用来定义那些被称为“明示常量明示常量”的东西的东西也就是数值、字符值和字符串值:也就是数值、字符值和字符串值:#defineSTR_LEN80#defineTRUE1#defineFALSE0#definePI3.14159#defineCRr#defineEOS0#defineMEM_ERRError:notenoughmemory20标识符通常用大写,以示区别!Chapter 14: The Preprocessor简单的宏使用使用#define来为常量命名的优点有来为常量命名的优点有

13、:使程序更易于阅读。使程序更易于阅读。名字可以帮助读者理解常量的意义。程序会更易于修改。程序会更易于修改。我们只需要改变一个宏定义,就可以改变整个程序中出现的所有该常量的值。可以帮助避免前后不一致或键盘输入错误。可以帮助避免前后不一致或键盘输入错误。假如数值常量3.14159在程序中大量出现,它可能被意外地误写为3.1416或者3.14195。21Chapter 14: The Preprocessor带参数的宏带参数的宏定义有如下格式:带参数的宏定义有如下格式:#define标识符符(x1,x2,xn)替换列表其中x1,x2,xn是标识符(宏的参数)。在宏的名字和左括号之间必须没有空格,否则

14、会认在宏的名字和左括号之间必须没有空格,否则会认为是在定义一个简单的宏,其中为是在定义一个简单的宏,其中(x1, x2, xn)是替是替换列表的一部分。换列表的一部分。22Chapter 14: The Preprocessor带参数的宏当预处理器遇到一个带参数的宏,会将定义存储起来以便后面使用。在后面的程序中,如果任何地方出现了标识符(y1,y2,yn)格式的宏调用(其中y1,y2,yn是一系列标记),预处理器会使用替换列表替代,并使用y1替换x1,y2替换x2,依此类推。带参数的宏经常用来作为一些简单的函数使用。带参数的宏经常用来作为一些简单的函数使用。23Chapter 14: The

15、Preprocessor带参数的宏带参数的宏举例:#defineMAX(x,y)(x)(y)?(x):(y)#defineIS_EVEN(n)(n)%2=0)调用这些宏:i=MAX(j+k,m-n);if(IS_EVEN(i)i+;宏替换后:i=(j+k)(m-n)?(j+k):(m-n);if(i)%2=0)i+;24Chapter 14: The Preprocessor带参数的宏复杂的类似函数的宏:#defineTOUPPER(c)(a=(c)&(c)=z?(c)-a+A:(c)头提供了一个更容易移植的相似函数toupper。带参数的宏也可能具有空的参数列表:#definegetchar

16、()getc(stdin)空的参数列表不是必需的,但可以使getchar更像一个函数。25Chapter 14: The Preprocessor带参数的宏使用带参数的宏替代实际的函数有两个优点:程序可能会稍微快些。程序可能会稍微快些。一个函数调用在执行时通常会有些额外开销存储上下文信息、复制参数的值等。而一个宏的调用则没有这些运行开销。宏会更宏会更“通用通用”。与函数的参数不同,宏的参数没有类型。只要预处理后的程序依然是合法的,宏可以接受任何类型的参数。26Chapter 14: The Preprocessor带参数的宏带参数的宏也有一些缺点。带参数的宏也有一些缺点。编译后的代码通常会变大

17、。编译后的代码通常会变大。每一处宏调用都会导致插入宏的替换列表,由此导致程序的源代码增加(因此编译后的代码变大)。当宏调用嵌套时,这个问题会相互叠加从而使程序更加复杂。n=MAX(i,MAX(j,k);预处理后的这条语句:n=(i)(j)(k)?(j):(k)?(i):(j)(k)?(j):(k);27Chapter 14: The Preprocessor带参数的宏带参数的宏也有一些缺点带参数的宏也有一些缺点宏参数没有类型检查。宏参数没有类型检查。无法用一个指针来指向一个宏。无法用一个指针来指向一个宏。28Chapter 14: The Preprocessor带参数的宏宏可能会不止一次地计

18、算它的参数。宏可能会不止一次地计算它的参数。如果参数有副作用,多次计算参数的值可能会产生意外的结果。n=MAX(i+,j);这条语句在预处理之后的结果:n=(i+)(j)?(i+):(j);如果i大于j,那么i可能会被(错误地)增加了两次,同时n可能被赋予了错误的值。29为了自保护,最好避免使为了自保护,最好避免使用带有副作用的参数。用带有副作用的参数。Chapter 14: The Preprocessor带参数的宏带参数的宏经常用作需要重复书写的代码段模式。显示整数的一种更容易的宏:#definePRINT_INT(n)printf(%dn,n)预处理器将把下行PRINT_INT(i/j)

19、;转换为printf(%dn,i/j);30Chapter 14: The Preprocessor#运算符宏定义可以包含两个运算符:宏定义可以包含两个运算符:#和和#。#运算符将一个宏的参数转换为运算符将一个宏的参数转换为字符串字面量字符串字面量。它仅允许出现在带参数的宏的替换列表中。它仅允许出现在带参数的宏的替换列表中。31Chapter 14: The Preprocessor#运算符改进后的PRINT_INT:#definePRINT_INT(n)printf(#n=%dn,n)调用PRINT_INT(i/j);将转换为printf(i/j=%dn,i/j);编译器自动连接两个相邻的字

20、符串字面量,因此上面语句等价为:printf(i/j=%dn,i/j);32Chapter 14: The Preprocessor#运算符#运算符可以将两个记号(例如标识符)“粘”在一起,成为一个记号。如果其中一个操作数是宏参数,“粘合”会在当形式参数被相应的实际参数替换后发生。33Chapter 14: The Preprocessor#运算符使用#运算符的宏:#defineMK_ID(n)i#n下面声明调用MK_ID三次:intMK_ID(1),MK_ID(2),MK_ID(3);预处理过后,该语句为:inti1,i2,i3;34Chapter 14: The Preprocessor宏

21、的通用属性同时适用于简单和带参数的宏的规则:宏的替换列表可以包含对另一个宏的调用。宏的替换列表可以包含对另一个宏的调用。例如:#definePI3.14159#defineTWO_PI(2*PI)当预处理器在后面的程序中遇到TWO_PI时,会将它替换成(2*PI)。预处理器会不断重新检查替换列表,直到将所有的宏名字都替换掉为止。35Chapter 14: The Preprocessor宏的通用属性预处理器只会替换完整的记号,而不会替换记号预处理器只会替换完整的记号,而不会替换记号的片断。的片断。因此,预处理器会忽略嵌在标识符名、因此,预处理器会忽略嵌在标识符名、字符常量、字符串字面量之中的宏

22、名。字符常量、字符串字面量之中的宏名。例如:#defineSIZE256intBUFFER_SIZE;if(BUFFER_SIZESIZE)puts(Error:SIZEexceeded);预处理过后:intBUFFER_SIZE;if(BUFFER_SIZE256)puts(Error:SIZEexceeded);36Chapter 14: The Preprocessor宏的通用属性一个宏定义的作用范围通常到出现这个宏的文件一个宏定义的作用范围通常到出现这个宏的文件末尾。末尾。p一个定义在函数中的宏并不是仅在函数内起作用,而是作用到文件末尾作用到文件末尾。宏不可以被定义两遍,除非新的定义与

23、旧的定义宏不可以被定义两遍,除非新的定义与旧的定义是一样的。是一样的。37Chapter 14: The Preprocessor宏的通用属性宏可以使用宏可以使用 #undef指令指令“取消定义取消定义”。#undef指令有如下形式:指令有如下形式: #undef标识符其中标识符是宏名#undef指令的一个用途是取消一个宏的现有定义,以便于重新给出新的定义。38Chapter 14: The Preprocessor宏定义中的圆括号宏定义中的替换列表往往需要圆括号,以免发生意料之外的结果。如果宏的替换列表中有运算符,那么始终要将替换列表放如果宏的替换列表中有运算符,那么始终要将替换列表放在括号

24、中:在括号中:#defineTWO_PI(2*3.14159)如果宏有参数,每次参数在替换列表中出现时都要放在圆如果宏有参数,每次参数在替换列表中出现时都要放在圆括号中括号中:#defineSCALE(x)(x)*10)没有括号的话,编译器可能会不按我们期望的方式应用运没有括号的话,编译器可能会不按我们期望的方式应用运算符的优先级和结合性规则。算符的优先级和结合性规则。39Chapter 14: The Preprocessor宏定义中的圆括号考虑下面的宏定义,其中的替换列表没有添加圆括号:#defineTWO_PI2*3.14159/*needsparenthesesaroundreplac

25、ementlist*/预处理过程中,语句:conversion_factor=360/TWO_PI;成为conversion_factor=360/2*3.14159;除法会在乘法之前完成,产生期望之外的结果。40Chapter 14: The Preprocessor宏定义中的圆括号当宏有参数时,仅给替换列表添加圆括号是不够的。参数的每一次出现都要添加圆括号。参数的每一次出现都要添加圆括号。#defineSCALE(x)(x*10)/*needsparenthesesaroundx*/在预处理过程中,语句j=SCALE(i+1);变为j=(i+1*10);由于乘法的优先级比加法高,这条语句等

26、价于:j=i+10;41Chapter 14: The Preprocessor14.4 条件编译C语言的预处理器可以识别大量用于支持条件编译的指令。预处理器根据条件测试结果来决定包含或排除程序中的一些片断。42Chapter 14: The Preprocessor#if指令和#endif指令假如我们正在调试一个程序。我们想要程序显示出特定变量的值,因此将printf函数调用添加到程序中重要的部分。一旦找到错误,经常需要保留这些printf函数调用,以备以后使用。条件编译允许我们保留这些调用,但是让编译器忽略它们。43Chapter 14: The Preprocessor#if指令和#en

27、dif指令首先定义一个宏,并给它一个非0的值:#defineDEBUG1接下来,我们要在每组printf函数调用的前后加上#if和#endif:#ifDEBUGprintf(Valueofi:%dn,i);printf(Valueofj:%dn,j);#endif44Chapter 14: The Preprocessor#if指令和#endif指令在预处理过程中,#if指令会测试DEBUG的值。由于DEBUG的值非0,因此预处理器会将这两个printf函数调用保留在程序中(但#if和#endif行会消失)。如果将DEBUG的值改为0并重新编译程序,预处理器则会将这4行代码都删除。可以将#if

28、-#endif保留在最终的程序中,这样如果程序在运行时出错,可以继续产生这些诊断信息(将DEBUG改为1并重新编译)。45Chapter 14: The Preprocessor#if指令和#endif指令一般来说,#if指令的格式如下:#ifconstant-expression#endif当预处理器遇到#if指令时,会计算常量表达式。如果表达式的值为0,那么在#if与#endif之间的行将在预处理过程中从程序中删除。否则,这些在#if和#endif之间的行会被保留在程序中,并继续被编译器处理这时#if和#endif对程序没有任何影响。46Chapter 14: The Preprocess

29、or#if指令和#endif指令对于没有定义过的标识符,#if指令会把它当作是值为0的宏对待。如果我们没有定义DEBUG,则测试:#ifDEBUG将会失败(但不会产生错误消息)。而测试:#if!DEBUG则会成功。47Chapter 14: The Preprocessordefined运算符预处理器支持三种运算符:预处理器支持三种运算符: #, #, 和和defined。当defined应用于标识符时,如果标识符是一个当前定义过的宏则返回1,否则返回0。# defined运算符通常与运算符通常与#if指令结合使用。指令结合使用。48Chapter 14: The Preprocessorde

30、fined运算符例子:#ifdefined(DEBUG)#endif仅当DEBUG被定义成宏时,#if和#endif之间的代码会被保留在程序中。DEBUG两侧的括号不是必需的,因此可以简单写成:#ifdefinedDEBUG也不必给DEBUG一个值:#defineDEBUG49Chapter 14: The Preprocessor#ifdef指令和#ifndef指令#ifdef指令测试一个标识符是否已经定义为宏:#ifdefidentifier其效果与下面语句相同:#ifdefined(identifier)#ifndef指令测试一个标识符当前是否未被定义为宏:#ifndefidentifi

31、er其效果与下面语句相同:#if!defined(identifier)50Chapter 14: The Preprocessor#ifdef指令和#ifndef指令#if,#ifdef,和#ifndef可以像普通if语句一样嵌套。当出现嵌套时,采用缩进来表示不同的嵌套层次是比较好的实践。某些程序员在每个#endif后面留注释来表明所匹配的测试条件:#ifDEBUG#endif/*DEBUG*/51Chapter 14: The Preprocessor#elif指令和#else指令#elif指令和#else指令可以与#if指令、#ifdef指令和#ifndef指令组合使用,来测试一系列条件

32、:#ifexpr1当表达式1非0时需要包含的代码#elifexpr2当表达式 1为 0但表达式 2非 0时需要包含的代码#else其他情况下需要包含的代码 #endif在#if指令和#endif指令之间可以有多个#elif指令,但最多只能有一个#else指令。52Chapter 14: The Preprocessor使用条件编译除了调试外,条件编译还有其它用处。编写在多台机器或多种操作系统之间可移植的编写在多台机器或多种操作系统之间可移植的程序。程序。例如:#ifdefined(WIN32)#elifdefined(MAC_OS)#elifdefined(LINUX)#endif53Chap

33、ter 14: The Preprocessor使用条件编译编写可以使用不同的编译器进行编译的程序编写可以使用不同的编译器进行编译的程序.使用_STDC_宏的例子:#if_STDC_标准C函数原型#else经典 C函数声明#endif如果编译器不支持标准C,则会采用经典C的函数声明而不是标准C的函数原型.54Chapter 14: The Preprocessor使用条件编译为宏提供默认定义。为宏提供默认定义。条件编译使我们可以检测一个宏当前是否已经被定义了,如果没有,则提供一个默认的定义:#ifndefBUFFER_SIZE#defineBUFFER_SIZE256#endif55Chapter 14: The Preprocessor使用条件编译临时屏蔽包含注释的代码。临时屏蔽包含注释的代码。由于在标准C中注释不能嵌套,因此不能直接“注释掉”包含注释的代码。然而,可以使用#if指令替代:#if0包含注释的代码行#endif56

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 高等教育 > 研究生课件

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号