预处理指令

预处理指令在大部分 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

每个头文件都应该这样定义,但是需要注意的是头文件中选择的常量时唯一的,没有重复的。