第14章预处理器_第1页
第14章预处理器_第2页
第14章预处理器_第3页
第14章预处理器_第4页
第14章预处理器_第5页
已阅读5页,还剩34页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第第 14章章l 本章要点n 预处理器的工作原理预处理器的工作原理n 预处理指令预处理指令n 宏定义宏定义n 条件编译条件编译引言v 指令如 #define 和 #include 等是由预处理器进行处理的,而预处理器是一个在编译前编辑 C程序的软件 .v C语言(和 C+语言)因为依赖预处理器而不同于其他的编程语言。v 预处理器是一个强大的工具,但它同时也可能是许多难以发现的错误的根源。14.1 预处理器的工作原理v 预处理器查找以 #开头的预处理指令 .v 前面,我们已经遇到过 #define 和 #include指令 .v #define 指令定义一个 宏( macro) 即一个能够代表其它东西的名字,比如一个常量。v 预处理器会通过将宏的名字和它的定义存储在一起来响应#define指令。v 当这个宏在后面的程序中使用到时,预处理器 “扩展 ”了宏,将宏替换为它所定义的值。预处理器的工作原理v #include指令告 诉预处 理器打开一个特定的文件,将它的内容作 为 正在 编译 的文件的一部分 “包含 ”进 来。v 例如,下面一行:#include v 指示 预处 理器打开一个名字 为 stdio.h的文件,并将它的内容加到当前的程序中。预处理器的工作原理v 在编译过程中中,预处理器的角色如图所示:预处理器的工作原理v 预处理器的输入是一个 C语言程序,程序可能包含指令。v 预处理器会执行这些指令,并在处理过程中删除这些指令。v 预处理的输出是另一个 C程序:原程序编辑后的版本。v 预处理器的输出被直接交给编译器。预处理器的工作原理v 第二章中的 celsius.c :/* Converts a Fahrenheit temperature to Celsius */#include #define FREEZING_PT 32.0f#define SCALE_FACTOR (5.0f / 9.0f) int main(void) float fahrenheit, celsius;printf(“Enter Fahrenheit temperature: “);scanf(“%f“, celsius = (fahrenheit - FREEZING_PT) * SCALE_FACTOR;printf(“Celsius equivalent is: %.1fn“, celsius); return 0;预处理器的工作原理v 预处理过后为 :Blank lineBlank lineLines brought in from stdio.hBlank lineBlank lineBlank lineBlank lineint main(void) float fahrenheit, celsius;printf(“Enter Fahrenheit temperature: “);scanf(“%f“, celsius = (fahrenheit - 32.0f) * (5.0f / 9.0f); printf(“Celsius equivalent is: %.1fn“, celsius); return 0; 预处理器的工作原理v 预处理器不仅仅是执行了指令,还做了一些其他的事情。v 特别值得注意的是,它将每一处注释都替换为一个空格字符。v 有一些预处理器还会进一步删除不必要的空白字符,包括在每一行开始用于缩进的空格符和制表符。v 在 C语言较早的时期,预处理器是一个单独的程序,并将它的输出提供给编译器。v 如今,预处理器通常和编译器集成在一起 (为了提高编译的速度 )。v 在理解上,将预处理器从编译器分开是比较有用的。预处理器的工作方式v 大多数编译器提供查看预处理输出的方法。v 一些编译器通过使用特定选项来产生预处理结果(如 gcc E)。v 其它一些编译环境带有类似集成预处理器的独立的程序。v 注意,预处理器仅知道少量 C语言的规则,因此,它在执行指令时非常有可能产生非法的程序。v 对于较复杂的程序,检查预处理器的输出可能是找到这类错误的有效途径。 14.2 预处理指令v 大多数预处理指令属于下面 3种类型之一 : 宏定义。 #define指令定义一个宏, #undef指令删除一个宏定义。 文件包含。 #include指令导致一个指定文件的内容被包含到程序中。 条件编译。 #if、 #ifdef、 #ifndef、 #elif、 #else和 #endif指令可以根据测试的条件来将一段文本块包含到程序中或排除在程序之外。v 几条应用于所有指令规则: 指令都以 #开始。 #符号不需要在一行的行首,只要它之前只有空白字符就行。在 #后是指令名,接着是指令所需要的其他信息。 在指令的符号之间可以插入任意数量的空格或横向制表符。例如,下面的指令是合法的:# define N 100 预处理指令v 指令总是在第一个换行符处结束,除非明确地指明要继续。如果想在下一行继续指令,我们必须在当前行的末尾使用 字符,例如:#define DISK_CAPACITY (SIDES * TRACKS_PER_SIDE * SECTORS_PER_TRACK * BYTES_PER_SECTOR)v 指令可以出现在程序中 任何地方 。我们通常将 #define和 #include指令放在文件的开始,其他指令则放在后面,甚至在函数定义的中间。v 注释可以与指令放在同一行。实际上,在一个宏定义的后面加一个注释来解释宏的意义是一种比较好的习惯:#define FREEZING_PT 32.0f /* freezing point of water */14.3 宏定义v 我们从第 2章以来使用的宏被称为简单的宏,它们没有参数,预编译器也支持带参数的宏。v 简单的宏定义有如下格式:#define 标识 符 替 换 列表v 替换列表 是一系列的 C语言记号,包括标识符、关键字、数、字符常量、字符串字面量、运算符和标点符号。v 在文件后面的内容中,不管 标识符 在任何位置出现,预处理器都会用 替换列表 代替它。14.3.1 简单的宏v 不要在宏定义中放置任何额外的符号,否则它们会被作为替换列表的一部分。v 一种常见的错误是在宏定义中使用 = ,如 :#define N = 100 /* WRONG */int aN; /* becomes int a= 100; */v 在宏定义的末尾使用分号结尾是另一个常见错误,如:#define N 100; /* WRONG */int aN; /* becomes int a100; */v 编译器将会检测到绝大多数由多余符号所导致的错误。v 编译器会将每一处使用这个宏的地方标为错误,而不会直接找到错误的根源 宏定义本身,因为宏定义已经被预处理器删除了。简单的宏v 简单的宏可以定义 “明示常量 manifest constants”#define STR_LEN 80#define TRUE 1#define FALSE 0#define PI 3.14159#define CR r#define EOS 0#define MEM_ERR “Error: not enough memory“简单的宏v #define的 优 点: 使程序更容易 读 。 使程序更容易修改。 避免前后不一致的 输 入 错误 ,例如 3.14159, 误输 入 为 3.1416或 3.14195。v 可以 对 C语 法做小的修改。#define BEGIN #define END #define LOOP for (;)v 对类型重命名。#define BOOL intv 控制条件编译。#define DEBUG14.3.2 带参数的宏v 带参数的宏定义有如下格式:#define 标识符 (x1, x2, xn) 替换列表 其中 x1, x2, xn是标识符 (宏的参数 )。v 在宏的名字和左括号之间必须没有空格。v 如果有空格,预处理器会认为是在定义一个简单的宏,其中(x1, x2, xn)是替换列表的一部分。带参数的宏v 带参数的宏举例:#define MAX(x,y) (x)(y)?(x):(y)#define IS_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 复杂的类似函数的宏 :#define TOUPPER(c) (a 头提供了一个更容易移植的相似函数toupper。v 带参数的宏也可能具有空的参数列表:#define getchar() getc(stdin)v 空的参数列表不是一定确实需要,但可以使 getchar更像一个函数。带参数的宏v 使用带参数的宏替代实际的函数有两个优点: 程序可能会稍微快些。一个函数调用在执行时通常会有些额外开销 存储上下文信息、复制参数的值等。而一个宏的调用则没有这些运行开销。 宏会更 “通用 ”。与函数的参数不同,宏的参数没有类型。只要预处理后的程序依然是合法的,宏可以接受任何类型的参数。v 带参数的宏也有一些缺点。 编译后的代码通常会变大。每一处宏调用都会导致插入宏的替换列表,由此导致程序的源代码增加 (因此编译后的代码变大 )。 当宏调用嵌套时,这个问题会相互叠加从而使程序更加复杂。v n = 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:#define PRINT_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 使用 # 运算符的宏:#define MK_ID(n) i#nv 下面声明 调 用 MK_ID 三次:int MK_ID(1), MK_ID(2), MK_ID(3);v 预处 理 过 后, 该语 句 为 :int i1, i2, i3;#运算符v #运算符不属于 预处 理器 经 常使用的特性。v 考 虑 前面提到 过 的 MAX宏,当 MAX的参数有副作用 时会无法正常工作;同 时 ,由于参数 类 型的原因,一个 max函数也是不 够 的。v 一种解决方法是用 MAX宏来写一个 max 函数, 并使它展开后成 为 max函数的定 义 。v 宏的参数将用于 说 明参数和返回 值 的 类 型。#运算符v 问题:如果我们是用宏来创建多个 max函数,程序将无法编译。 ( C语言不允许在同一文件中出现两个同名的函数。 )v 为了解决这个问题,我们是用 #运算符为每个版本的 max函数构造不同的名字。下面是宏的显示形式:#define GENERIC_MAX(type) type type#_max(type x, type y) return x y ? x : y; v 对该宏的调用:GENERIC_MAX(float)v 预处理过后的代码为:float float_max(float x, float y) return x y ? x : y; 14.3.5 宏的通用属性v 同时适用于简单和带参数的宏的规则:v 宏的替换列表可以包含对另一个宏的调用,例如:#define PI 3.14159#define TWO_PI (2*PI)v 当预处理器在后面的程序中遇到 TWO_PI时,会将它替换成 (2*PI)。v 预处理器会不断重新检查替换列表,直到将所有的宏名字都替换掉为止。 宏的通用属性v 预处理器只会替换完整的记号,而不会替换记号的片断。因此,预处理器会忽略嵌在标识符名、字符常量、字符串字面量之中的宏名。例如 :#define SIZE 256int BUFFER_SIZE;if (BUFFER_SIZE SIZE)puts(“Error: SIZE exceeded“);预处理过后 :int BUFFER_SIZE;if (BUFFER_SIZE 256)puts(“Error: SIZE exceeded“);宏的通用属性v 一个宏定义的作用范围通常到出现这个宏的文件末尾。 由于宏是由预处理器处

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论