C++ “#”的作用与用法

1 ###的作用和用法

C/C++ 的宏中,#的功能是将其后面的宏参数进行字符串化操作,简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号。##连接符号由两个井号组成,其功能是在带参数的宏定义中将两个子串联接起来,从而形成一个新的子串。但它不可以是第一个或者最后一个子串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
using namespace std;
#define WARN_IF(EXP) if(EXP) cerr << #EXP << endl;
#define paster( n ) cout << "token" << #n << " = " << n << endl;
#define _CONS(a, b) int(a##+##b)
#define _STRI(s) #s
void main()
{
int div = 0;
WARN_IF(div == 0); // prints : div == 0
paster(9); // prints : token9 = 9
cout << _CONS(1, 2) << endl; // prints : 3
cout << _STRI(INT_MAX) << endl; // prints : INT_MAX
}

凡是宏定义里有用###的地方宏参数是不会再展开,例如_STRI(INT_MAX)中的INT_MAX就不会被展开为2147483647。如果想要使其中的宏参数展开,则需要多加一层中间转换宏:

1
2
3
#define STRI(s) _STRI(s)
cout << STRI(INT_MAX) << endl; // prints : 2147483647

加这层宏的用意是把所有宏的参数在这层里全部展开,那么在转换宏里的宏就能得到对应的宏参数。

接下来,我们来了解通过预处理指令创建条件编译参数控制代码编译的一些用法。
$ $

2 #include的用法

包含头文件的操作,通常有两种格式:

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

<>""表示编译器在搜索头文件时的顺序不同:

  • <>表示从系统目录下开始搜索,然后再搜索PATH环境变量所列出的目录,不搜索当前目录
  • ""是表示从当前目录开始搜索,然后是系统目录和PATH环境变量所列出的目录。

所以,系统头文件一般用<>,用户自己定义的则可以使用"",加快搜索速度。除此外,写代码多了就会发现,有些头文件之间的相互包含是有隐藏依赖关系的,一定要加以注意。Google C++ Style Guide中也强调使用标准的头文件包含顺序可增强可读性, 避免隐藏依赖:

1 相关文件(优先位置,如dir2/foo2.h
2 C系统文件
3 C++ 系统文件
4 其他库的.h文件
5 本项目内.h文件

$ $

3 #if,#elif,#else,#endif用法

1
2
3
4
5
6
7
8
9
// structure 1
#if constant_expression
#else
#endif
// structure 2
#if constant_expression
#elif constant_expression
#endif

这里的结构跟常见的if...elseif...else if...else语句类似,当#if后的条件为非零(true)时,编译#if#else#elif之间的代码,否则编译#else#endif之间的代码(或者判断#elif后的条件是否非零(true),决定是否编译#elif#endif之间的代码)。

1
2
3
4
5
6
7
#if 1
cout << "Hello world!" << endl;
#else
cout << "Nice to meet you!" << endl;
#endif
// prints : Hello world!
1
2
3
4
5
6
7
8
#if 1
cout << "Hello world!" << endl;
#elif 1
cout << "Nice to meet you!" << endl;
#endif
// prints: Hello world!
// Nice to meet you!

$ $

4 #define,#undef,#ifdef,#ifndef用法

#define是大家都常见的宏定义方法,用法结构为:

1
2
3
// #define identifier replacement-code
#define PI 3.1415926
#define ADD(x,y) (x + y)

#undef顾名思义,就是从该处取消前面已经定义的宏,如果标识符当前没有被定义称为一个宏名称,就会忽略该指令:

1
2
// #undef identifier
#undef PI

#ifdef#ifndef 含义相反,前者含义为如果定义了该宏,则编译相应代码;后者则为如果没有定义该宏,则编译相应代码。通用结构为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* #ifdef identifier
* #else or #elif
* #endif
**/
#define DEBUG
#ifdef DEBUG
cout << "This is a debug message." << endl;
#endif
// prints : This is a debug message.
/*
* #ifndef identifier
* #else or #elif
* #endif
**/
#ifndef DEBUG
cout << "This is a debug message." << endl;
#endif
// prints nothing

在编程时,为了避免头文件重定义,经常使用的就是#define配合条件编译解决:

1
2
3
4
5
6
7
8
9
10
#ifndef MY_HEADER_FILE_H
#define MY_HEADER_FILE_H
// ...
class MyHeaderFile
{
// ....
};
#endif // MY_HEADER_FILE_H

除此以外,还有#pragma once的用法,只要在头文件的最开始加入这条指令就能够保证头文件被编译一次。(在所有的预处理指令中,#pragma指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作,本文不多讲述。)
$ $

5 #line用法

#line命令是用于更改__LINE____FILE__变量的值。__FILE____LINE__描述被读取的当前文件和所在行数。

1
2
3
4
5
6
7
// #line line-number filename
int main()
{
#line 10 "main.cpp"
cout << __FILE__ << " " << __LINE__ << endl;
}
// prints : main.cpp 10

$ $

6 #error用法

#error会直接导致程序停止编译并输出指定的错误信息:

1
2
3
4
5
6
7
// #error message
#ifndef VERSION
#error Version number not specified.
#endif
// The compiler will halt compiling and return with the specified error message:
// fatal error C1189: #error : Version number not specified.