《学习C++》基本概念之预处理器(c程序预处理)

预处理器在C++翻译过程的第三步“预处理令牌序列至令牌序列”中执行。这个过程在真正编译过程之前,产生的预处理结果交由编译器进行编译。

预处理器通过识别一系列的预处理指令(preprocessing directive,进行相应的操作。预处理指令的格式为:以‘#’字符开头,后接标准定义的指令名(directive name,再后接相应的指令参数(argument,最后以换行(line break)结尾。

预处理器根据预处理执行对源文件进行转换变化,预处理指令按能力分类如下:

条件编译(Conditionally Compile)

包括指令:#if, #ifdef, #ifndef, #else, #elif, #elifdef, #endif, #elifndef(自C++23起)

这些指令支持在特定条件下,对某些代码块进行编译(或不进行编译)。结合某些预定义宏,可以完成代码级的跨平台、跨编译器支持,或者一套代码生成多个目标版本(DEBUG/RELEASE),以及其他功能。

替换文本宏(Replace Text Macro)

包括指令:#define, #undef,以及操作符#, ##

既支持简单的对象宏(Object-like macro)替换展开,也支持函数形式宏(Function-like macro)替换展开(带参数)。两者语法格式如下:

#define identifier replacement-list (optional)
#define identifier (parameters ) replacement-list (optional)

以上将标识符identifier定义为宏,并将之后出现的identifier替换展开为replacement-list(替换列表)中的内容。parameters表示函数形式宏的参数。

自C++20起,引入__VA_OPT__与__VA_ARGS__用于支持变长参数替换。__VA_ARGS__替换为参数列表,__VA_OPT__(content)在参数列表不为空时替换为content内容。

#define F(...) f(0 __VA_OPT__(,) __VA_ARGS__)
F(a, b, c) // 替换为f(0, a, b, c)
F()        // 替换为f(0)

在函数形式宏定义的替换列表中,可能需要使用操作符#, ##进行文本拼接。

  • #操作符:在替换列表中,如果#出现在某个参数前,那么在做此参数的替换时,将替换结果包裹在“”中,从而产生一个字符串常量。
#define STRINGIFY(x) #x
STRINGIFY(Hello World) // 替换为"Hello World"
  • ##操作符:在替换列表中,如果##出现在两个参数之间,那么在做此参数的替换时,将替换结果拼接在一起。
#define CONCAT(a, b) a##b
CONCAT(x, y) // 替换为xy

文件包含

包括指令:#include

用于将其他源文件引入当前源文件,引入位置在此预编译指令行之后。一般使用<>包裹标准够文件名,使用“”包裹用户自定义的源文件。

被引入的头文件中常使用条件编译,以免重复包含。

#ifndef FOO_H_INCLUDED /* 这里使用一个与文件名相关的唯一宏 */
#define FOO_H_INCLUDED
// 被引入的文件内容
#endif

C++17引入__has_include检测某个头文件是否可以被包含,通常结合#if使用。

#if __has_include(<头文件名>)
    // 头文件存在时的代码
#else
    // 头文件不存在时的代码
#endif

诊断

包括指令:#error, #warning(自C++23起)

指令后接诊断消息,用于编译过程中输出。#error指令将导致编译过程停止;但#warning指令仅输出消息,编译过程继续。

原文链接:,转发请注明来源!