预处理器指令

预处理器指令是程序代码中以井号 (#) 开头的代码行。这些代码行不是程序语句,而是给预处理器的指令。预处理器在代码的实际编译开始之前检查代码,并在常规语句实际生成任何代码之前解析所有这些指令。

这些预处理器指令只占用单行代码。一旦遇到换行符,预处理器指令就结束了。预处理器指令的末尾不需要分号 (;)。让预处理器指令跨越多行的唯一方法是在行尾的换行符前加上一个反斜杠 (\)。

宏定义 (#define, #undef)

要定义预处理器宏,我们可以使用 #define。其语法是:

#define 标识符 替换内容

当预处理器遇到此指令时,它会将代码其余部分中所有出现的 identifier(标识符)替换为 replacement(替换内容)。这个 replacement 可以是一个表达式、一个语句、一个代码块或任何东西。预处理器本身不理解 C++,它只是简单地将所有出现的 identifier 替换为 replacement

1
2
3
#define TABLE_SIZE 100
int table1[TABLE_SIZE];
int table2[TABLE_SIZE];

在预处理器替换了 TABLE_SIZE 之后,代码变得等同于:

1
2
int table1[100];
int table2[100];

#define 也可以与参数一起使用来定义函数宏:

1
#define getmax(a,b) a>b?a:b 

这将会把任何后面跟着两个参数的 getmax 出现的地方,替换为替换表达式,同时也会将每个参数替换为其标识符,正如你期望一个函数那样工作。

// function macro
#include <iostream>
using namespace std;

#define getmax(a,b) ((a)>(b)?(a):(b))

int main()
{
  int x=5, y;
  y= getmax(x,2);
  cout << y << endl;
  cout << getmax(7,x) << endl;
  return 0;
}
5
7

已定义的宏不受代码块结构的影响。一个宏会一直有效,直到用 #undef 预处理器指令取消定义它为止。

1
2
3
4
5
#define TABLE_SIZE 100
int table1[TABLE_SIZE];
#undef TABLE_SIZE
#define TABLE_SIZE 200
int table2[TABLE_SIZE];

这将生成与以下代码相同的代码:

1
2
int table1[100];
int table2[200];

函数宏定义在替换序列中接受两个特殊运算符(###)。
运算符 # 后面跟着一个参数名,它会被替换为一个包含传入参数的字符串字面量(就像用双引号括起来一样)。
1
2
#define str(x) #x
cout << str(test);

这将被翻译成:

1
cout << "test";

运算符 ## 连接两个参数,它们之间不留任何空格。

1
2
#define glue(a,b) a ## b
glue(c,out) << "test";

这同样会被翻译成:

1
cout << "test";

因为预处理器替换发生在任何 C++ 语法检查之前,所以宏定义可能是一个棘手的特性。但是,要小心:严重依赖复杂宏的代码会变得可读性较差,因为其预期的语法在很多情况下与程序员在 C++ 中所期望的正常表达式不同。

条件包含 (#ifdef, #ifndef, #if, #endif, #else 和 #elif)


这些指令允许在满足特定条件时,包含或舍弃程序的一部分代码。

#ifdef 允许一段程序代码只有在作为参数指定的宏已经被定义时才被编译,无论其值是什么。例如:

1
2
3
#ifdef TABLE_SIZE
int table[TABLE_SIZE];
#endif  

在这种情况下,代码行 int table[TABLE_SIZE]; 只有在 TABLE_SIZE 之前已经用 #define 定义过才会被编译,而不管它的值是多少。如果它没有被定义,那行代码将不会被包含在程序编译中。

#ifndef 的作用恰恰相反:在 #ifndef#endif 指令之间的代码,只有在指定的标识符之前没有被定义时才会被编译。例如:

1
2
3
4
#ifndef TABLE_SIZE
#define TABLE_SIZE 100
#endif
int table[TABLE_SIZE];

在这种情况下,如果程序执行到这段代码时,宏 TABLE_SIZE 还没有被定义,那么它将被定义为 100。如果它已经存在,它将保持其先前的值,因为 #define 指令将不会被执行。

#if#else#elif(即 "else if")指令用于指定要满足的某个条件,以使其包围的代码部分被编译。#if#elif 后面的条件只能评估常量表达式,包括宏表达式。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#if TABLE_SIZE>200
#undef TABLE_SIZE
#define TABLE_SIZE 200
 
#elif TABLE_SIZE<50
#undef TABLE_SIZE
#define TABLE_SIZE 50
 
#else
#undef TABLE_SIZE
#define TABLE_SIZE 100
#endif
 
int table[TABLE_SIZE];

请注意,整个由 #if#elif#else 链接起来的指令结构以 #endif 结尾。

#ifdef#ifndef 的行为也可以通过在任何 #if#elif 指令中使用特殊运算符 defined!defined 来实现。

1
2
3
4
5
6
7
#if defined ARRAY_SIZE
#define TABLE_SIZE ARRAY_SIZE
#elif !defined BUFFER_SIZE
#define TABLE_SIZE 128
#else
#define TABLE_SIZE BUFFER_SIZE
#endif 

行控制 (#line)

当我们编译一个程序并且在编译过程中发生错误时,编译器会显示一条错误消息,其中包含错误发生的文件名和行号,以便更容易地找到产生错误的代码。

#line 指令允许我们控制这两样东西:代码文件中的行号以及发生错误时我们希望出现的文件名。其格式为:

#line 行号 "文件名"

其中 number(行号)是将被赋给下一行代码的新行号。后续代码行的行号将从此开始逐一增加。

"filename"(文件名)是一个可选参数,允许重新定义将要显示的文件名。例如:

1
2
#line 20 "assigning variable"
int a?;

这段代码将产生一个错误,该错误将显示为在文件 "assigning variable" 的第 20 行。

错误指令 (#error)

此指令在被发现时会中止编译过程,并生成一个编译错误,该错误可以作为其参数指定。

1
2
3
#ifndef __cplusplus
#error A C++ compiler is required!
#endif 

这个例子会在宏名 __cplusplus 未定义时中止编译过程(这个宏名在所有 C++ 编译器中都是默认定义的)。

源文件包含 (#include)

本教程的其他部分已经频繁使用过此指令。当预处理器发现一个 #include 指令时,它会将其替换为指定的头文件或文件的全部内容。有两种使用 #include 的方式:

1
2
#include <header>
#include "file" 

在第一种情况下,头文件被指定在尖括号 <> 之间。这用于包含由实现提供的头文件,例如构成标准库的头文件(iostream, string, ...)。这些头文件是实际的文件还是以其他形式存在是由实现定义的,但无论如何,它们都应该用这个指令正确地包含。

第二种 #include 使用的语法是引号,它包含一个文件。系统会以由实现定义的方式搜索该文件,这通常包括当前路径。如果找不到文件,编译器会将该指令解释为头文件包含,就像引号 ("") 被替换为尖括号 (<>) 一样。

Pragma 指令 (#pragma)

该指令用于向编译器指定各种选项。这些选项特定于您使用的平台和编译器。有关您可以使用 #pragma 定义的可能参数的更多信息,请查阅您的编译器手册或参考资料。

如果编译器不支持 #pragma 的特定参数,它将被忽略——不会产生语法错误。

预定义的宏名

以下宏名总是被定义的(它们都以两个下划线字符 _ 开始和结束):

__LINE__代表正在编译的源代码文件中当前行的整数值。
__FILE__一个字符串字面量,包含正在编译的源文件的假定名称。
__DATE__一个形式为 "Mmm dd yyyy" 的字符串字面量,包含编译过程开始的日期。
__TIME__一个形式为 "hh:mm:ss" 的字符串字面量,包含编译过程开始的时间。
__cplusplus一个整数值。所有 C++ 编译器都将此常量定义为某个值。其值取决于编译器支持的标准版本:
  • 199711L: ISO C++ 1998/2003
  • 201103L: ISO C++ 2011
不符合标准的编译器会将此常量定义为最多五位长的某个值。请注意,许多编译器并未完全符合标准,因此其定义的此常量值可能不是上述任何一个值。
__STDC_HOSTED__如果实现是托管实现(所有标准头文件都可用),则为 1
否则为 0

以下宏是可选定义的,通常取决于某个功能是否可用:

__STDC__在 C 语言中:如果定义为 1,则该实现符合 C 标准。
在 C++ 中:由实现定义。
__STDC_VERSION__在 C 语言中:
  • 199401L: ISO C 1990, 修正案 1
  • 199901L: ISO C 1999
  • 201112L: ISO C 2011
在 C++ 中:由实现定义。
__STDC_MB_MIGHT_NEQ_WC__如果多字节编码可能在字符字面量中赋予字符不同的值,则为 1
__STDC_ISO_10646__一个形式为 yyyymmL 的值,指定了 wchar_t 字符编码所遵循的 Unicode 标准的日期。
__STDCPP_STRICT_POINTER_SAFETY__如果实现具有严格指针安全性,则为 1 (参见 get_pointer_safety)。
__STDCPP_THREADS__如果程序可以拥有多个线程,则为 1

特定的实现可能会定义额外的常量。

例如:

// standard macro names
#include <iostream>
using namespace std;

int main()
{
  cout << "This is the line number " << __LINE__;
  cout << " of file " << __FILE__ << ".\n";
  cout << "Its compilation began " << __DATE__;
  cout << " at " << __TIME__ << ".\n";
  cout << "The compiler gives a __cplusplus value of " << __cplusplus;
  return 0;
}
This is the line number 7 of file /home/jay/stdmacronames.cpp.
Its compilation began Nov  1 2005 at 10:12:29.
The compiler gives a __cplusplus value of 1
Index
目录