首页 > 学院 > 开发设计 > 正文

C预处理

2019-11-11 05:24:06
字体:
来源:转载
供稿:网友

C的预处理,是在程序被编译之前执行的,执行的操作有:将其他文件包含到正在被编译的文件中来,定义符号常量(symbol constant)和宏(macro),程序代码的条件编译(conditional compilation)和有条件地执行预处理命令(conditional execution of PReprocessor directive)。预处理命令都是以#开头,同一行中只有空格和注释会出现在预处理命令之前。

include预处理命令

include预处理命令用于将指定文件的一个副本包含到该命令所在的位置上。有如下两种形式:

#include <filename>#include "filename"

差别在于查找欲包含文件的起始位置不同。 用引号括起来,则预处理器从待编译文件所在的目录里开始查找欲包含的文件(当然也会在其他位置查找)。这种方法通常用来包含程序员定义的头文件。 用尖括号括起来,则预处理器按照一种依赖于系统实现的方式,通常在预先指定的编译器和系统目录中开始查找。通常用来包含标准函数库的头文件。 被不同源程序所公用的声明通常被编辑成一个头文件,然后将其分别包含到各个源程序中。

define预处理命令

符号常量

#define命令的格式如下:

#define identifier replacement-text

在这一行出现之后,除了字符串文本外,其后出现的所有identifier(标识符)都会在程序编译前被自动地替换成replacement-text(替换文本)。在符号常量(标识符)右边的所有内容都会用来替换这个符号常量。例如:

#define PI = 3.1415

将会使程序中所有的PI都被“= 3.1415”所替换

宏也是预处理命令提供的一种标识符。与符号常量一样,程序中所有的宏标识符(macro-identifier)也要在编译前用其对应的替换文本来替换。宏的定义可以带实参,也可以不带。不带实参的宏在处理上和符号常量没有差别。对于带实参的宏,实参将会被代入到替换文本中,这样宏就被展开了(宏标识符和实参列表都被替换)。如下:

#define CIRCLE_AREA(x) ((PI)*(x)*(x))

此后凡是出现CIRCLE_AREA(y)的地方,y的值都会被代入到替换文本中x的位置,符号常量PI也会被它自己的值替换,然后宏就展开了。如:

area = CIRCLE_AREA(4);

被展开为:

area = ((3.1415)*(4)*(4));

当宏实参是一个表达式时,替换文本中将x括起来的圆括号可以保证计算顺序的正确性。如:

area = CIRCLE_AREA(4 + c);

被展开为:

area = ((3.1415)*(4 + c)*(4 + c));

也可以将宏定义成一个函数,但是函数会带来调用函数的开销。宏的优点是直接将代码插入到程序中,避免了调用函数的开销,而且还使程序仍然保持良好的可读性。而缺点是需要对其实参求两次值。 宏有时被用来以内联代码替换一个函数,从而消除了函数调用的开销,但是目前优化的编译器常常会替代程序员将其内联。 标准库中函数有时也被定义成一个基于其他库函数的宏。如:

#define getchar() getc(stdin)

一般在stdio.h中都存在。

宏或者符号常量的替换文本是在#define预处理命令这一行中位于标识符之后的所有文本。如果这一行剩余空间不够写下宏或者符号常量的替换文本,则必须要在行的末尾加上一个反斜杠(/),表示下一行继续是替换文本。 符号常量和宏可以用#undef预处理命令来撤销。该命令将撤销符号常量和宏的定义,所以宏或者符号常量的作用域是从它们的定义开始到被它们#undef命令撤销为止或者文件末尾为止。一旦被撤销,宏名或者符号常量名可以用#define预处理命令来重新定义。

条件编译

条件编译使用户能够控制预处理命令的执行以及对程序代码的编译。每一个条件预处理命令都要计算一个整型表达式的值,但是强制类型转换,sizeof表达式以及枚举常量的值不能在预处理命令中计算。使用如下:

#if 0 code prevented from compiling#endif

将0改为1就可以让上述部分代码参与编译。 条件预处理命令结构非常类似于if选择语句,每个#if都使用#endif来结束。对于多分支的条件预处理结构,需要使用命令#elif(等价于if条件语句中的else if)和#else(等价于if条件语句中的else)。这些命令通常被用来防止头文件被多次包含到同一个源文件中。 预处理命令#ifdef和#ifndef是#if defined(name)和#if !defined(name)的缩写形式。使用如下:

#if !defined(MY_CONSTANT) #define MY_CONSTANT 0#endif

首先判断MY_CONSTANT是否被定义。如果定义了,则表达式defined(MY_CONSTANT)的值为1,否则为0,定义MY_CONSTANT为0。 条件编译一般用于程序调试,如果没有调试器,一般使用printf命令打印变量的值以验证控制的流向。对于这样的printf可以用条件预处理命令封装起来以便使其仅在程序调试过程中参加编译。如下:

#ifdef DEBUG printf("Variable x = %d/n", x);#endif

只有在该命令之前定义了符号常量DEBUG,上面的printf才会参加编译。

#error和#pragma

#error tokens

预处理命令#error打印出包含命令中指定tokens(标记)的信息,信息的具体内容和系统的实现有关。标记是用空格分隔的一个字符序列。使用如下:

#error 1 - Out of range error

包含了6个标记,某些系统执行该条指令时,命令中的标记将被作为出错信息显示出来,然后终止预处理,并停止程序编译。

#pragma tokens

执行一个系统实现中已经定义好了的操作,不能被系统识别出来的将被忽略掉。

#和##运算符

#和##运算符仅在标准C中有效。#将替换文本中的标记转换成一个用引号引起来的字符串。#必须用在一个带有实参的宏当中,因为#的操作数就是宏的实参。使用如下:

#define HELLO(x) printf("Hello, " #x "/n");

当程序中出现了HELLO(John),将被替换成:

printf("Hello, John/n");

##用于将两个标记拼接在一起。使用如下:

#define TOKENCONCAT(x, y) x ## y

若程序中出现TOKENCONCAT(O,K),将会被OK替换。##操作符必须要有两个操作数。

行号

#line使在它之后的后继程序代码行,按照命令中给定的整型常数值,重新编排序号。如:

#line 100

使下一行程序代码的行号从100开始。同时,#line命令中还可以包含文件名,如下:

#line 100 "file1.c"

表示从下一行程序代码开始 ,后继代码行的行号从100开始编号。同时,任何编译器消息采用的文件名都是file1.c。该命令有助于让语法错误和编译器警告产生的信息更好理解。这些符号并不出先在源程序文件中。

预定义的符号常量

以下标识符和defined标识符(判断是否标识符定义过)都不可用于#define和#undef命令。

_LINE_ 源程序文件中当前代码行号(整型常量)_FILE_ 假定的源文件名(一个字符串)_DATE_ 编译源文件的日期(如“Jan 19 2002”)_TIME_ 编译源文件的时间(格式为“时 分 秒”的字符串文本)_STDC_ 如果编译器支持标准C,则值为1

断言

宏assert在头文件assert.h中定义,用于测试一个表达式的值,如果表达式为假(0),则assert打印出错信息,并调用函数abort(stdlib.h中定义)来结束程序执行。是一个用于测试变量的值是否正确的的调试工具。使用如下:

assert(x <= 10);

若x大于10,则包含有行号和文件名的出错信息就会被打印出来,然后程序终止。 如果定义了符号常量NDEBUG,则其后所有的断言都将被忽略掉。若想忽略断言,只需在程序开始时插入一行:

#define NDEBUG
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表