预处理指令
预处理指令在大部分 C++教程中都被放到了较为靠后的位置介绍。但在绝大多数阅读示例源码的时候,一打开文件首先是满眼的预处理指令,所以本指南将从预处理指令开始描述。
C++中的预处理指令都是以#
开头,用来指示编译器在实际编译前先完成预处理指令定义的处理过程。常用的预处理指令有#include
、#define
、#if
等。
#include
#include
指令用于将一个文件包含进来并在当前位置插入。#include
指令有两种语法形式,其用法和区别如下:
#include <文件名>
,使用尖括号的包含指令,编译器会在标准库目录中寻找指定的文件,如果标准库中不存在指定文件,将会报错。#include "文件名"
,使用双引号的包含指令,编译器将会在指定的文件目录中搜索指定文件,这里的文件名一般会采用相对于当前文件所在的相对路径。
包含指令主要用于将包含声明的头文件包含进来以供后续代码使用。注意:.cpp
文件只需要包含需要的头文件即可,你所编写的代码只需要关心要用的功能是如何声明的,不必关心它们是如何定义的。
#define
#define
指令用于创建符号常量,该符号常量通常都被称为宏。使用#define
定义的宏在编译期将直接进行文字的替换,并不做任何计算、转换等工作。宏的定义格式如下:
// 仅用于替换的宏
#define 宏名 用于替代的内容
// 可以携带参数的宏
#define 宏名(参数) (表达式)
例如有宏#define PI 3.14
,则在编译时,代码中出现PI
的位置都会被替换为3.14
;而可以带参数的宏#define MAX(a, b) (a > b ? a : b)
则会在编译时将类似于MAX(5, 6)
的宏调用,替换为(5 > 6 ? 5 : 6)
。需要注意的是,带参数的宏定义不能替代函数。
重要:#define
创建的符号常量仅在定义符号常量的文件中起效。
使用#define
定义的符号常量,可以使用#undef
删除。
在定义符号常量时,可以使用#
来将用于替代的内容转换为使用引号括起来的字符串,例如:#define STR(x) #x
在执行语句STR(234)
,得到的结果是"234"
。操作符##
则可以用来连接两个参数内容,也同样是将其连接为字符串,例如:#define CC(x, y) x##y
在执行语句CC(a, b)
时,得到的结果是变量ab
的值。
条件编译
条件编译是由一组指令组成的,用于在编译之前根据一些条件有选择的对部分源代码进行编译。条件编译结构与if
分支语句结构非常像。条件编译指令有以下两种形式。
// 如果指定符号常量已经定义,则执行后续的编译
#ifdef 第一个符号常量
// 待编译的代码
#elif 第二个符号常量
// 当第一个符号常量未定义,第二个符号常量定义了需要编译的代码
#else
// 未定义全部符号常量时编译的代码
#endif
// 如果指定符号常量未定义,则执行后续的编译
#ifndef 符号常量
// 待编译的代码
#endif
#ifndef
常常用来判断指定符号常量是否已经定义,如果指定符号常量尚未定义,那么就可以结合#define
来进行定义,例如:
#ifndef NONE
#define NONE 0
#endif
这种符号常量的定义方式,避免了在多个位置使用#define
造成的宏的覆盖。
一般头文件还会使用以下格式,来确保自身定义的内容只会被声明一次,防止重复声明产生意料之外的效果。
// 文件class_a.h
#ifndef CLASS_A_H_ // 如果这个常量已经定义,那么后面就不再继续了
#define CLASS_A_H_ // 联合上一句,如果这个常量没有定义过,那就定义一下
class A {}
#endif
每个头文件都应该这样定义,但是需要注意的是头文件中选择的常量时唯一的,没有重复的。