前言
在编写C/C++程序,会用到预处理,但是大多数时候,我们只用到了它的一点点功能。
C++语言有近百个关键字,但预处理指令只有十来个。我们常用的也就是#include、#define、#if,所以很容易掌握。
这里要注意,单独的一个“#”也是一个预处理指令,叫“空指令”,可以当作特别的预处理空行。而“#”与后面的指令之间也可以有空格,从而实现缩进,方便排版。
下面是一个示例,#号都在行首,而且if里面的define有缩进,看起来还是比较清楚的。以后你在写预处理代码的时候,可以参考这个格式。
# // 预处理空行
#if __GNUC__ // 预处理检查宏是否存在
# define __packed 1 // 宏定义,有缩进
#endif // 预处理条件语句结束
# // 预处理空行
包含文件(#include)
正如标题所述,这个最常用的预处理指令“#include”,它的作用是“包含文件”。注意,不是“包含头文件”,而是可以包含任意的文件。
也就是说,使用“#include”可以把任意文件都引进来。但错误使用会出现无法处理的报错等。因此在使用“#include”时,为了防止重复包含。通常会加上“Include Guard”也就是用“#ifndef/#define/#endif”来保护整个头文件
#ifndef _XXX_H_INCLUDED_
#define _XXX_H_INCLUDED_
... // 头文件内容
#endif // XXX_H_INCLUDED
当然,巧妙地使用“#include”可以使得代码更简洁,更易于阅读。比如,编写一些代码片段,存进“**.inc”文件里,然后有选择地加载。
- 处理大数组
static uint32_t table[] = { // 非常大的一个数组,有几十行
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
...
};
这个时候,可以把数组整理出来,另存为一个“.inc”文件,然后再用“#include**”替换原来的大数组,使得代码更加整洁,后期维护更容易。
处理后的代码示例。
static uint32_t table[] = {
# include "table_values.inc" // 非常大的一个数组,细节被隐藏
};
table_values.inc文件
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
...
宏定义(#define/#undef)
宏定义是预处理编程里最重要、最核心的指令。
首先,我们应该知道,宏的展开、替换发生在预处理阶段,不涉及函数调用、参数传递、指针寻址,没有任何运行期的效率损失。
其次,宏是没有作用域概念的,永远是全局生效。所以,对于一些用来简化代码、起临时作用的宏,最好是用完后尽快用“#undef”取消定义。
//示例一
#define CUBE(a) (a) * (a) * (a) // 定义一个简单的求立方的宏
cout << CUBE(10) << endl; // 使用宏简化代码
cout << CUBE(15) << endl; // 使用宏简化代码
#undef CUBE // 使用完毕后立即取消定义
//示例二
#ifdef AUTH_PWD // 检查是否已经有宏定义
# undef AUTH_PWD // 取消宏定义
#endif // 宏定义检查结束
#define AUTH_PWD "xxx" // 重新宏定义
最后,利用宏来定义代码中的常量,可以在后期维护时直接修改,方便高效。
#define MAX_BUF_LEN 65535
条件编译(#if/#else/#endif)
条件编译有两个要点,一个是条件指令“#if”,另一个是后面的“判断依据”,也就是定义好的各种宏,而这个“判断依据”是条件编译里最关键的部分。
#ifdef __cplusplus // 定义了这个宏就是在用C++编译
extern "C" { // 函数按照C的方式去处理
#endif
void a_c_function(int a); // 此部分编译器会按照C的方式去处理,但仍可使用C++调用
#ifdef __cplusplus // 检查是否是C++编译
} // extern "C" 结束
#endif
#if __cplusplus >= 201402 // 检查C++标准的版本号
cout << "c++14 or later" << endl; // 201402就是C++14
#elif __cplusplus >= 201103 // 检查C++标准的版本号
cout << "c++11 or before" << endl; // 201103是C++11
#else // __cplusplus < 201103 // 199711是C++98
# error "c++ is too old" // 太低则预处理报错
#endif // __cplusplus >= 201402 // 预处理语句结束
以上示例要注意,extern “C”的主要作用就是为了能够正确实现C代码调用其他C语言代码。加上extern “C”后,会指示编译器这部分代码按C语言的进行编译,而不是C的。因为由于,C++的函数重载,编译器会将函数的参数类型也加到编译后的代码中。但C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。因此,但需要使用C ++语言调用C语言代码时,需要用extern “C”来强制编译器不要重载函数。
void a_c_function(int a);
//C编译器
_a_c_function
//C++编译器
_a_c_function_int
条件编译还有一个特殊的用法,那就是,使用“#if 1”“#if 0”来显式启用或者禁用大段代码。
#if 0 // 0即禁用下面的代码,1则是启用
... // 任意的代码
#endif // 预处理结束
#if 1 // 1启用代码,用来强调下面代码的必要性
... // 任意的代码
#endif // 预处理结束