C#
的预处理机制
预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。可见预处理过程先于编译
器对源代码进行处理。
在
C
语言中,并没有任何内在的机制来完成如下一些功能:在编译时包含其他源文件、定义宏、根据条件
决定编译时是否包含某些代码。要完成这些工作,就需要使用预处理程序。尽管在目前绝大多数编译器都
包含了预处理程序,但通常认为它们是独立于编译器的。预处理过程读入源代码,检查包含预处理指令的
语句和宏定义,并对源代码进行响应的转换。预处理过程还会删除程序中的注释和多余的空白字符。
预处理指令是以
#
号开头的代码行。
#
号必须是该行除了任何空白字符外的第一个字符。
#
后是指令关键字,
在关键字和
#
号之间允许存在任意个数的空白字符。
整行语句构成了一条预处理指令,
该指令将在编译器进
行编译之前对源代码做某些转换。下面是部分预处理指令:
指令
用途
#
空指令,无任何效果
#include
包含一个源代码文件
#define
定义宏
#undef
取消已定义的宏
#if
如果给定条件为真,则编译下面代码
#ifdef
如果宏已经定义,则编译下面代码
#ifndef
如果宏没有定义,则编译下面代码
#elif
如果前面的
#if
给定条件不为真,当前条件为真,则编译下面代码
#endif
结束一个
#if……#else
条件编译块
#error
停止编译并显示错误信息
一、文件包含
#include
预处理指令的作用是在指令处展开被包含的文件。包含可以是多重的,也就是说一个被包含的
文件中还可以包含其他文件。标准
C
编译器至少支持八
重嵌套包含。
预处理过程不检查在转换单元中是否已经包含了某个文件并阻止对它的多次包含。这样就可以在多次包
含同一个头文件时,通过给定编译时的条件来达到不
同的效果。例如:
for(i=0;i<MAX_NUM;i++)
/*……*/
在这个例子中,对于阅读该程序的人来说,符号
MAX_NUM
就有特定的含义,它代表的值给出了数
组所能容纳的最大元素数目。程序中可以多次使用这个值。作为一种约定,习惯上总是全部用大写字母来
定义宏,这样易于把程序红的宏标识符和一般变量标识符区别开来。如果想要改变数组的大小,只需要更
改宏定义并重新编译程序即可。
宏表示的值可以是一个常量表达式,其中允许包括前面已经定义的宏标识符。例如:
#define ONE 1
#define TWO 2
#define THREE (ONE+TWO)
注意上面的宏定义使用了括号。尽管它们并不是必须的。但出于谨慎考虑,还是应该加上括号的。例
如:
six=THREE*TWO;
预处理过程把上面的一行代码转换成:
six=(ONE+TWO)*TWO;
如果没有那个括号,就转换成
six=ONE+TWO*TWO;
了。
宏还可以代表一个字符串常量,例如:
#define VERSION "Version 1.0 Copyright(c) 2003"
2.
带参数的
#define
指令
带参数的宏和函数调用看起来有些相似。看一个例子:
#define Cube(x) (x)*(x)*(x)
可以时任何数字表达式甚至函数调用来代替参数
x
。这里再次提醒大家注意括号的使用。宏展开后完
全包含在一对括号中,而且参数也包含在括号中,这样就保证了宏和参数的完整性。看一个用法:
intnum=8+2;
volume=Cube(num);
展开后为
(8+2)*(8+2)*(8+2);
如果没有那些括号就变为
8+2*8+2*8+2
了。
下面的用法是不安全的:
volume=Cube(num++);
如果
Cube
是一个函数,上面的写法是可以理解的。但是,因为
Cube
是一个宏,所以会产生副作用。
这里的擦书不是简单的表达式,它们将产生意想不到的结果。它们展开后是这样的:
volume=(num++)*(num++)*(num++);
很显然,结果是
10*11*12,
而不是
10*10*10;
那么怎样安全的使用
Cube
宏呢?必须把可能产生副作用的操作移到宏调用的外面进行:
intnum=8+2;
volume=Cube(num);
num++;
3.#
运算符
出现在宏定义中的
#
运算符把跟在其后的参数转换成一个字符串。有时把这种用法的
#
称为字符串化运
算符。例如:
#define PASTE(n) "adhfkj"#n
main()
{
printf("%s"n",PASTE(15));
}
宏定义中的
#
运算符告诉预处理程序,把源代码中任何传递给该宏的参数转换成一个字符串。所以输
出应该是
adhfkj15
。
4.##
运算符
##
运算符用于把参数连接到一起。
预处理程序把出现在
##
两侧的参数合并成一个符号。
看下面的例子:
#define NUM(a,b,c) a##b##c
#define STR(a,b,c) a##b##c
main()
{
printf("%d"n",NUM(1,2,3));
printf("%s"n",STR("aa","bb","cc"));
}
最后程序的输出为
:
123
aabbcc
千万别担心,除非需要或者宏的用法恰好和手头的工作相关,否则很少有程序员会知道
##
运算符。绝
大多数程序员从来没用过它。
三、条件编译指令
条件编译指令将决定那些代码被编译,而哪些是不被编译的。可以根据表达式的值或者某个特定的宏是
否被定义来确定编译条件。
1.#if
指令
#if
指令检测跟在制造另关键字后的常量表达式。
如果表达式为真,
则编译后面的代码,
知道出现
#else
、
#elif
或
#endif
为止;否则就不编译。
2.#endif
指令
#endif
用于终止
#if
预处理指令。
#define DEBUG 0
main()
{
#if DEBUG
printf("Debugging"n");
#endif
printf("Running"n");
}
由于程序定义
DEBUG
宏代表
0
,所以
#if
条件为假,不编译后面的代码直到
#endif
,所以程序直接输
出
Running
。
如果去掉
#define
语句,效果是一样的。
3.#ifdef
和
#ifndef
#define DEBUG
main()
{
#ifdef DEBUG
printf("yes"n");
#endif
#ifndef DEBUG
printf("no"n");
#endif
}
#if defined
等价于
#ifdef; #if !defined
等价于
#ifndef
4.#else
指令
#else
指令用于某个
#if
指令之后,当前面的
#if
指令的条件不为真时,就编译
#else
后面的代码。
#endif
指令将中指上面的条件块。
#define DEBUG
main()
{
#ifdef DEBUG
printf("Debugging"n");
#else
printf("Not debugging"n");
#endif
printf("Running"n");
}
5.#elif
指令
#elif
预处理指令综合了
#else
和
#if
指令的作用。
#define TWO
main()
{
#ifdef ONE
printf("1"n");
#elif defined TWO
printf("2"n");
#else
printf("3"n");
#endif
}
程序很好理解,最后输出结果是
2
。
6.
其他一些标准指令
#error
指令将使编译器显示一条错误信息,然后停止编译。
#line
指令可以改变编译器用来指出警告和错误信息的文件号和行号。
#pragma
指令没有正式的定义。编译器可以自定义其用途。典型的用法是禁止或允许某些烦人的警告
信息。