预处理器指令
预处理器指令是代码中的哪些以#开头的行,这些行不是程序的一部分,而是作为预处理器的标识。预处理器在开始编译前检查代码,并且在regularstatements生成代码前处理掉所有的指令。
这些预处理器指令只占代码的一行。一旦检测到换行字符,预处理器指令就结束了。预处理器指令的末尾不需要分号(;)。预处理器指令延伸到多行的唯一方法是在一行的末尾添加反斜杠(\)。
1.宏定义指令(#define,#undef)
你可以使用#define来定义一个预处理器宏,它的语法是:
#define identifier replacement
当预处理器遇到这个指令,就会把
identifier
替换成
replacement
。这个
replacement
可以是一个表达式,声明,代码块或者是任何东西。严格的说,预处理器不懂的
C++,
它只是在遇到
identifier
的时候替换成
replacement
。
| #define TABLE_SIZE 100 int |
在预处理器替换了TABLE_SIZE
后,代码就等价于这样:
| int |
#define
也可以带参数来定义函数宏
| #define getmax(a,b) a>b?a:b |
这不仅会把getmax后面带有两个参数的情况替换成后面的表达式,而且会把每个参数替换成每个的指令,就好像一个函数一样:
| // function macro #include <iostream> using | 5 7 |
定义宏不会影响块结构。宏会一直存在知道遇到#undef预处理器指令:
| #define TABLE_SIZE 100 int |
生成的代码如下:
| int |
函数宏定义在替换串里接受两个特殊的操作符(#和##):
如果#被用在替换串的一个参数前面,这个参数会被替换成一个字符串(就像是被双引号包括一样)。
| #define str(x) #x
|
这会被翻译成:
|
|
##
可以连接两个参数,两个之间没有空格:
Theoperator ##
concatenatestwo arguments leaving no blank spaces between them:
| #define glue(a,b) a ## b |
翻译结果是:
|
|
因为预处理器替换是在C++语法检查之前,所以宏定义是一个巧妙的特性。但是,请注意:因为在很多情况下复杂宏的语法表达和一般的程序员期望的不一样,所以带有复杂宏的代码不易读。
2.条件包含指令(#ifdef,#ifndef, #if, #endif, #else and #elif)
这些指令允许在某个条件满足是包含或者抛弃代码的一部分。
#ifdef
只有在宏的参数被定义,无论它的值是什么的时候,允许程序的某一部分被编译:
| #ifdef TABLE_SIZE
int |
在这种情况下,inttable[TABLE_SIZE];
这行代码只会在
TABLE_SIZE
在之前通过
#define
定义后被编译,
TABLE_SIZE
的值不造成影响。
如果TABLE_SIZE
没有被定义,这行代码就不会被包含进程序编译。
#ifndef
恰恰相反:在#ifndef
和
#endif
指令之间的代码只有在之前没有定义过的情况下被编译。
例如:
| #ifndef TABLE_SIZE
#define TABLE_SIZE 100
#endif
int |
在这种情况下,如果TABLE_SIZE
宏还没有被定义,将会定义一个宏
TABLE_SIZE
,且值为
100.
如果已经存在
TABLE_SIZE
宏,将不会改变宏的值,因为
#define
指令根本没有执行。
#if
,#else
and#elif
(i.e.,"else if") 指令在特定的条件满足时才会使得它们包围的代码被编译。#if
or#elif
后的条件可以只可以是包含宏的常量表达式,例如:
| #if TABLE_SIZE>200 #undef TABLE_SIZE #define TABLE_SIZE 200 |
注意#if
,#elif
and#else
连锁指令的整个结构是以
#endif
结束的
。
#ifdef
and#ifndef
的行为可以分别由
#if
或者
#elif
指令加上
defined
和
!defined
来替换
:
| #if !defined TABLE_SIZE
#define TABLE_SIZE 100
#elif defined ARRAY_SIZE
#define TABLE_SIZE ARRAY_SIZE
int |
3.行控制(#line)
当我们在编译程序时出现了一些错误,编译器会显示包含发生错误的文件名的错误信息和和一个行号,这样能更加准确的找到产生错误的代码。
#line
指令
可以帮助我们控制这两件事,当错误发生后我们需要的源文件里的行号和文件名。它的格式如下:
#line number"filename"
number规定了下一行代码的行号。接下来的行号会依次递增(步长为1)。
"filename"
作为可选参数可以重新定义要显示的文件名。例如:
| #line 20 "assigning variable"
int |
这段代码会产生一个错误,且会被显示成错误发生在文件"assigningvariable"
,
line20
。
4.错误提示指令(#error)
当发现错误提示指令,就会中止编译,并产生一个以它的参数标识的编译错误:
| #ifndef __cplusplus #error A C++ compiler is required! #endif |
这个例子会中止编译处理,如果宏__cplusplus
没有被定义
(这个宏名称默认由C++编译器定义)。
5.源文件包含指令 (#include)
当预处理器找到一个#include
指令
,
就会把它替换成头文件或者其他文件的内容。有两种使用
#include
的方式:
| #include <header> #include "file" |
第一种情况下,头文件在尖括号中间.这是被用来包含系统提供的头文件的,比如说组成标准库的头文件(iostream,string等)。无论这些头文件是真的文件还是以其他的形式存在是不是实现定义,都应该用这个指令包含进来。
另一种情况是#include
使用引号包含一个文件。这个文件按照实现定义的方式查找,一般包含在当前路径下。如果按照这种方式没有找到文件,编译器就会认为这是头文件包含,就好像把引号换成了尖括号。
Pragma指令 (#pragma)
这个指令用来设定编译器的不同参数。这些参数由平台和你使用的编译器一起决定。你可以查看你的编译器的指南或参考来了解你可以通过#pragma
设置哪些可能的参数
.
如果你的编译器不支持#pragma
的
某个特殊的参数,则它会被忽略而不会产生语法错误。
6.预定义宏名称
下面的宏名称一般会被提前定义好(它们都是以下划线_开始和结束);
macro | value |
---|---|
| 表示当前正在编译的代码的行号的整形变量(可能不是真正的行号)。 |
| 表示当前正在编译的文件的假定文件名(可能不是真正的名字)。 |
| 表示编译器开始编译的日期,格式是“Mmmdd yyyy”。 |
| 表示编译器开始编译的时间,格式是“hh:mm:ss”。 |
| 一个整形变量。所有的C++编译器都定义了一个这样的常量(值不确定)。它的值取决于编译器支持的标准的版本。
不符合标准的编译器往往把这些常量定义为最多不多于5位的长整形数字。有许多编译器都不能完全符合标准,所以他们的常量常常和上面的数字都不一样。 |
|
|
以下的宏是可选的,主要依赖于特性是不是可用:
macro | value |
---|---|
| 在C中:如果定义成 |
| 在C中:
在C++中:实现定义(Implementationdefined)。 |
|
|
|
|
|
|
|
|
个别的实现可能会定义另外的常数。
例如:
| // standard macro names #include <iostream> using | This is the line number 7. This is the file:/home/jay/stdmacronames.cpp. Its compilation began:Nov 1 2005 at 10:12:29. The compiler gives a __cplusplus value of 1 |