比如,在文件开头写上“#include”这样的语句,或者用“#define”定义一些常数。
预处理只能用很少的几个指令,也没有特别严谨的“语法”,但它仍然是一套完整自洽的语言体系,使用预处理也能够实现复杂的编程,解决一些特别的问题——虽然代码可能会显得有些“丑陋”“怪异”。
预处理阶段编程的操作目标是“源码”,用各种指令控制预处理器,把源码改造成另一种形式,就像是捏橡皮泥一样。
预处理指令就十来个,例如 #include、#define、#if等。 预处理指令都以符号“#”开头,但是虽然都在一个源文件里,但它不属于 C++ 语言,它走的是预处理器,不受 C++ 语法规则的约束。
所以,预处理编程也就不用太遵守 C++ 代码的风格。一般来说,预处理指令不应该受 C++ 代码缩进层次的影响,不管是在函数、类里,还是在 if、for 等语句里,永远是顶格写。
单独的一个“#”也是一个预处理指令,叫“空指令”,可以当作特别的预处理空行。而“#”与后面的指令之间也可以有空格,从而实现缩进,方便排版。
# // 预处理空行
#if __linux__ // 预处理检查宏是否存在
# define HAS_LINUX 1 // 宏定义,有缩进
#endif // 预处理条件语句结束
# // 预处理空行
预处理程序也有它的特殊性,暂时没有办法调试,不过可以让 GCC 使用“-E”选项,略过后面的编译链接,只输出预处理后的源码,比如: g++ test03.cpp -E -o a.cxx #输出预处理后的源码
常用预处理指令
include
它的作用是“包含文件”。注意,不是“包含头文件”,而是可以包含任意的文件。
include还可以当作数据源使用
static uint32_t calc_table[] = { // 非常大的一个数组,有几十行
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
...
};
这个时候,你就可以把它单独摘出来,另存为一个“*.inc”文件,然后再用“#include”替换原来的大批数字。这样就节省了大量的空间,让代码更加整洁。
static uint32_t calc_table[] = {
# include "calc_values.inc" // 非常大的一个数组,细节被隐藏
};
define宏定义
它用来定义一个源码级别的“文本替换”,也就是我们常说的“宏定义”。
“#define”可谓“无所不能”,在预处理阶段可以无视 C++ 语法限制,替换任何文字,定义常量 / 变量,实现函数功能,为类型起别名(typedef),减少重复代码……
所以,使用宏的时候一定要谨慎,时刻记着以简化代码、清晰易懂为目标,不要“滥用”,避免导致源码混乱不堪,降低可读性。
宏是没有作用域概念的,永远是全局生效。所以,对于一些用来简化代码、起临时作用的宏,最好是用完后尽快用“#undef”取消定义,避免冲突的风险。像下面这样:
#define CUBE(a) (a) * (a) * (a) // 定义一个简单的求立方的宏
cout << CUBE(10) << endl; // 使用宏简化代码
cout << CUBE(15) << endl; // 使用宏简化代码
#undef CUBE // 使用完毕后立即取消定义
另一种做法是宏定义前先检查,如果之前有定义就先 undef,然后再重新定义:
#ifdef AUTH_PWD // 检查是否已经有宏定义
# undef AUTH_PWD // 取消宏定义
#endif // 宏定义检查结束
#define AUTH_PWD "xxx" // 重新宏定义
条件编译(#if/#else/#endif)
利用“#define”定义出的各种宏,我们还可以在预处理阶段实现分支处理,通过判断宏的数值来产生不同的源码,改变源文件的形态,这就是“条件编译”。
此文章为3月Day28学习笔记,内容来源于极客时间《gk.link/a/120Fm》