C++的预处理
转自:http://blog.sina.com.cn/s/blog_4c362f1e0100087p.html
预处理是一个处理和管理文本型源文件的程序。通常被编译器作为编译处理的第一步所调用。预处理的工作过程叫做转换,预处理器的工作就是把源文件的文本分成一些标记。这些文本形式的标记用来定位预处理指令。
当把一个源文件编译前,预处理经过以下4步:
1、字符映射:把所有源文件字符转化成内部方法表示的字符序列。
2、行结合:任何以反斜杠字符\的后边接行结束符(硬回车)结尾的行都与它后边的行结合起来,形成单独的行。这意味着反斜杠和行末硬回车结合时中间没有空格。
3、符号化:预处理器辨认出源文件中的每个预处理标记和空格,把程序中的命令都删掉。
4、预处理:执行所有的预处理指令,在源文件中把宏展开为初始定义的值。
除空格外,一个含有预处理指令的行中的第一个字符必须是磅的符号#。在磅号和指令间可以加任何数量的空格。可以在源文件的任何位置放置预处理指令,但是该指令只影响文件中该指令之后的代码。
可以在源代码中使用#define指令定义一个新的预处理符号。允许单独定义标志符,用于进行文本替换。除名称标志符外,也可以用于创建进行简单文本替换的标志符。一个通常的用法是用于给操作符起一个别名,使代码更易读。因为C++是数据类型概念很强的语言,而使用宏来定义标志符就不受C++中类型检测机制的约束,所以会导致难于检查的编码错误。宏的参数被称为无类型参数,因为宏发送参数时不用指定参数。
条件编译的基本思想是,在某处定义一个标志符,然后在源代码中需要使用时,可以使用预处理指令来检查该标志符是否已经定义过。
头文件为函数、结构、联合以及类定义了接口,也定义了预处理宏和外部变量,还有其他一些东西。如果在一个模块头文件包含部分定义了一个实体,并且该实体也被其它模块定义,那么编译器就会阻塞,声明在两个或以上模块定义了标志符。可以使用单一文件包含或一次头文件包含的技术,使用#ifdef和#defined指令来防止重复。头文件中实施单一文件包含的方法:
1、创建一个标识的名称,通常从头文件名中导出的。一个典型的单一文件包含标志是在头文件名前加一或两个下划线组成。
2、下划线后边是大写的文件名。
3、在文件名后加下划线来代替文件名中的“.”号。
4、再加上文件的扩展名,是大写字符,最后再加上两个或更多的下划线包围起来。
5、在单一文件标志符前加#ifndef预处理指令,作为头文件的开始。
6、在头文件语句后可以定义新的标志符。
7、头文件最后加入相应的#endif来结束#ifndef指令。
宏是一个很有用的工具,但有时也会导致混乱。编程者经常使用宏为常的难于输入的语句创建别名,来使它们更短一些和看起来更有意义。长的宏可以分几行来写,只要每行以一个反斜杠加硬回车结尾。复杂的宏能为函数指针创建别名,把复杂的数据声明缩短为更易于控制的长度,甚至像函数一样接收参数并返回一个值。
宏展开是预处理的最后一步完成的,生成一个完全预处理过的文本并准备由编译器将它转换成二进制目标代码。当编译器展开宏时,它把宏标志符替换为完整的宏所代表的预处理文本。当宏有两个参数时,应该在每个参数的两边使用圆括号来防止意外的边界问题。
ANSI定义了3种预处理操作符,第一种是defined。第二和第三种用来与指令#defined连接。三个操作符分别为:
1、defined操作符:检测一个操作符有没有被定义。
2、串化操作符#:用于把扩展前的宏参数转化为文本型的串纯量。只有当宏带有参数的时候才使用该操作符,以告诉处理器在实际传送给宏的参数两边加上引用标志串化该参数。操作符#会把原句中的引号当作字符引号。
3、标记传送操作符##:允许作为实际参数的标记连接起来形成其它的标记。有时也叫做合并或者串联操作符,它将单独的标记合并为一个单个的、结合起来的的标记。在宏定义时,不能在第一个或最后一个标记上使用这种操作符。标记的串联发生在宏扩展前,用它来生成新的、唯一的名字是有用的。
六个预先定义的宏:
宏名 描述
__DATE__ 源文件编译的日期
__FILE__ 当前的源文件名称
__LINE__ 当前源文件行号
__STDC__ 指定与ANSI的标准C语言完全一致(大多数编译器未定义)
__TIME__ 当前源文件最近修改日期
__TIMESTAMP__ 当前源文件最近修改的日期和时间
字符序列(一些键损坏时,可以替换)
符号 两字符序列 三字符序列
{ <% ??<
} %> ??>
[ <: ??(
] :> ??)
# %: ??=
## %:%:
\ ??/
^ ??'
| ??!
~ ??-
? ???