第14章预处理器

上传人:bin****86 文档编号:54903604 上传时间:2018-09-21 格式:PPT 页数:39 大小:239.01KB
返回 下载 相关 举报
第14章预处理器_第1页
第1页 / 共39页
第14章预处理器_第2页
第2页 / 共39页
第14章预处理器_第3页
第3页 / 共39页
第14章预处理器_第4页
第4页 / 共39页
第14章预处理器_第5页
第5页 / 共39页
点击查看更多>>
资源描述

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

1、第14章,预处理器,本章要点,预处理器的工作原理 预处理指令 宏定义 条件编译,引言,指令如#define 和#include 等是由预处理器进行处理的,而预处理器是一个在编译前编辑C程序的软件. C语言(和 C+语言)因为依赖预处理器而不同于其他的编程语言。 预处理器是一个强大的工具,但它同时也可能是许多难以发现的错误的根源。,14.1 预处理器的工作原理,预处理器查找以#开头的预处理指令. 前面,我们已经遇到过#define 和#include指令. #define 指令定义一个宏( macro)即一个能够代表其它东西的名字,比如一个常量。 预处理器会通过将宏的名字和它的定义存储在一起来响

2、应#define指令。 当这个宏在后面的程序中使用到时,预处理器“扩展”了宏,将宏替换为它所定义的值。,预处理器的工作原理,#include指令告诉预处理器打开一个特定的文件,将它的内容作为正在编译的文件的一部分“包含”进来。 例如,下面一行: #include 指示预处理器打开一个名字为 stdio.h的文件,并将它的内容加到当前的程序中。,预处理器的工作原理,在编译过程中中,预处理器的角色如图所示:,预处理器的工作原理,预处理器的输入是一个 C语言程序,程序可能包含指令。 预处理器会执行这些指令,并在处理过程中删除这些指令。 预处理的输出是另一个C程序:原程序编辑后的版本。 预处理器的输出

3、被直接交给编译器。,预处理器的工作原理,第二章中的 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“, ,预处理器的工作原理,预处理过后为: Blank line Blank line Lines br

4、ought in from stdio.h Blank line Blank line Blank line Blank line int main(void) float fahrenheit, celsius;printf(“Enter Fahrenheit temperature: “);scanf(“%f“, ,预处理器的工作原理,预处理器不仅仅是执行了指令,还做了一些其他的事情。 特别值得注意的是,它将每一处注释都替换为一个空格字符。 有一些预处理器还会进一步删除不必要的空白字符,包括在每一行开始用于缩进的空格符和制表符。 在 C语言较早的时期,预处理器是一个单独的程序,并将它的输出

5、提供给编译器。 如今,预处理器通常和编译器集成在一起(为了提高编译的速度)。 在理解上,将预处理器从编译器分开是比较有用的。,预处理器的工作方式,大多数编译器提供查看预处理输出的方法。 一些编译器通过使用特定选项来产生预处理结果(如 gcc E)。 其它一些编译环境带有类似集成预处理器的独立的程序。 注意,预处理器仅知道少量 C语言的规则,因此,它在执行指令时非常有可能产生非法的程序。 对于较复杂的程序,检查预处理器的输出可能是找到这类错误的有效途径。,14.2 预处理指令,大多数预处理指令属于下面 3种类型之一: 宏定义。#define指令定义一个宏, #undef指令删除一个宏定义。 文件

6、包含。 #include指令导致一个指定文件的内容被包含到程序中。 条件编译。 #if、#ifdef、#ifndef、#elif、#else和#endif指令可以根据测试的条件来将一段文本块包含到程序中或排除在程序之外。 几条应用于所有指令规则: 指令都以#开始。#符号不需要在一行的行首,只要它之前只有空白字符就行。在 #后是指令名,接着是指令所需要的其他信息。 在指令的符号之间可以插入任意数量的空格或横向制表符。例如,下面的指令是合法的:# define N 100,预处理指令,指令总是在第一个换行符处结束,除非明确地指明要继续。如果想在下一行继续指令,我们必须在当前行的末尾使用字符,例如:

7、 #define DISK_CAPACITY (SIDES * TRACKS_PER_SIDE * SECTORS_PER_TRACK * BYTES_PER_SECTOR) 指令可以出现在程序中任何地方。我们通常将 #define和#include指令放在文件的开始,其他指令则放在后面,甚至在函数定义的中间。 注释可以与指令放在同一行。实际上,在一个宏定义的后面加一个注释来解释宏的意义是一种比较好的习惯: #define FREEZING_PT 32.0f /* freezing point of water */,14.3 宏定义,我们从第 2章以来使用的宏被称为简单的宏,它们没有参数,预

8、编译器也支持带参数的宏。 简单的宏定义有如下格式: #define 标识符 替换列表 替换列表是一系列的 C语言记号,包括标识符、关键字、数、字符常量、字符串字面量、运算符和标点符号。 在文件后面的内容中,不管标识符在任何位置出现,预处理器都会用替换列表代替它。,14.3.1 简单的宏,不要在宏定义中放置任何额外的符号,否则它们会被作为替换列表的一部分。 一种常见的错误是在宏定义中使用 = ,如: #define N = 100 /* WRONG */ int aN; /* becomes int a= 100; */ 在宏定义的末尾使用分号结尾是另一个常见错误,如: #define N 10

9、0; /* WRONG */ int aN; /* becomes int a100; */ 编译器将会检测到绝大多数由多余符号所导致的错误。 编译器会将每一处使用这个宏的地方标为错误,而不会直接找到错误的根源宏定义本身,因为宏定义已经被预处理器删除了。,简单的宏,简单的宏可以定义 “明示常量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“,简

10、单的宏,#define的优点: 使程序更容易读。 使程序更容易修改。 避免前后不一致的输入错误,例如3.14159,误输入为 3.1416或 3.14195。 可以对C语法做小的修改。 #define BEGIN #define END #define LOOP for (;) 对类型重命名。 #define BOOL int 控制条件编译。 #define DEBUG,14.3.2 带参数的宏,带参数的宏定义有如下格式: #define 标识符(x1, x2, xn) 替换列表 其中 x1, x2, xn是标识符(宏的参数)。 在宏的名字和左括号之间必须没有空格。 如果有空格,预处理器会认为

11、是在定义一个简单的宏,其中(x1, x2, xn)是替换列表的一部分。,带参数的宏,带参数的宏举例: #define MAX(x,y) (x)(y)?(x):(y) #define IS_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+;,带参数的宏,复杂的类似函数的宏:#define TOUPPER(c) (a 头提供了一个更容易移植的相似函数toupper。 带参数的宏也可能具有空的参数列表: #define getch

12、ar() getc(stdin) 空的参数列表不是一定确实需要,但可以使 getchar更像一个函数。,带参数的宏,使用带参数的宏替代实际的函数有两个优点: 程序可能会稍微快些。一个函数调用在执行时通常会有些额外开销 存储上下文信息、复制参数的值等。而一个宏的调用则没有这些运行开销。 宏会更“通用”。与函数的参数不同,宏的参数没有类型。只要预处理后的程序依然是合法的,宏可以接受任何类型的参数。 带参数的宏也有一些缺点。 编译后的代码通常会变大。每一处宏调用都会导致插入宏的替换列表,由此导致程序的源代码增加 (因此编译后的代码变大)。 当宏调用嵌套时,这个问题会相互叠加从而使程序更加复杂。 n

13、= MAX(i, MAX(j, k); /*预处理后*/ n = (i)(j)(k)?(j):(k)?(i):(j)(k)?(j):(k);,14.3.3 #运算符,宏定义可以包含两个运算符:#和#。 编译器不会识别这两种运算符;相反,它们会在预处理时被执行。 #运算符将一个宏的参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。 #运算符有大量的用途,这里只来讨论其中的一种。 假设我们决定在调试过程中使用PRINT_INT宏作为一个便捷的方法,来输出一个整型变量或表达式的值。 #运算符可以使PRINT_INT为每个输出的值添加标签。,#运算符,改进后的 PRINT_INT: #de

14、fine PRINT_INT(n) printf(#n “ = %dn“, n) 调用 PRINT_INT(i/j); 将转换为 printf(“i/j“ “ = %dn“, i/j); 编译器自动连接两个相邻的字符串字面量,因此上面语句等价为: printf(“i/j = %dn“, i/j);,14.3.4 #运算符,#运算符可以将两个记号(例如标识符)“粘”在一起,成为一个记号。 如果其中一个操作数是宏参数,“粘合”会在当形式参数被相应的实际参数替换后发生。 使用# 运算符的宏: #define MK_ID(n) i#n 下面声明调用 MK_ID 三次: int MK_ID(1), MK

15、_ID(2), MK_ID(3); 预处理过后,该语句为: int i1, i2, i3;,#运算符,#运算符不属于预处理器经常使用的特性。 考虑前面提到过的 MAX宏,当 MAX的参数有副作用时会无法正常工作;同时,由于参数类型的原因,一个max函数也是不够的。 一种解决方法是用 MAX宏来写一个 max 函数,并使它展开后成为 max函数的定义。 宏的参数将用于说明参数和返回值的类型。,#运算符,问题:如果我们是用宏来创建多个 max函数,程序将无法编译。( C语言不允许在同一文件中出现两个同名的函数。) 为了解决这个问题,我们是用#运算符为每个版本的 max函数构造不同的名字。下面是宏的

16、显示形式: #define GENERIC_MAX(type) type type#_max(type x, type y) return x y ? x : y; 对该宏的调用: GENERIC_MAX(float) 预处理过后的代码为: float float_max(float x, float y) return x y ? x : y; ,14.3.5 宏的通用属性,同时适用于简单和带参数的宏的规则: 宏的替换列表可以包含对另一个宏的调用,例如: #define PI 3.14159 #define TWO_PI (2*PI) 当预处理器在后面的程序中遇到 TWO_PI时,会将它替换成(2*PI)。 预处理器会不断重新检查替换列表,直到将所有的宏名字都替换掉为止。,

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 大杂烩/其它

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