C/C++ #define与编译器的预处理

文章目录

预处理

在预处理之前,编译器必须对该程序进行一些翻译处理。首先,编译器

把源代码中出现的字符映射到源字符集。该过程处理多字节字符和三字符序

列------字符扩展让C更加国际化。

第二,编译器定位每个反斜杠后面跟着换行符的实例,并删除它们。也

就是说,把下面两个物理行(physical line):

cpp 复制代码
printf("That's wond\
erful!\n");

转换成一个逻辑行(logical line):

cpp 复制代码
printf("That's wonderful\n!");

注意,在这种场景中,"换行符"的意思是通过按下Enter键在源代码文件

中换行所生成的字符,而不是指符号表征\n。

由于预处理表达式的长度必须是一个逻辑行,所以这一步为预处理器做

好了准备工作。一个逻辑行可以是多个物理行。

第三,编译器把文本划分成预处理记号序列、空白序列和注释序列(记

号是由空格、制表符或换行符分隔的项)。

这里要注意的是,编译器将用一个空格字符替换每一条注释。因此,下面的代码:

cpp 复制代码
int/* 这看起来并不像一个空格*/fox;

将变成:

cpp 复制代码
int fox;

而且,实现可以用一个空格替换所有的空白字符序列(不包括换行符)。最后,程序已经准备好进入预处理阶段,预处理器查找一行中以#号开始的预处理指令。

#define

新版本特性

#define预处理器指令和其他预处理器指令一样,以#号作为一行的开

始。ANSI和后来的标准都允许#号前面有空格或制表符,而且还允许在#和

指令的其余部分之间有空格。

旧版本特性

但是旧版本的C要求指令从一行最左边开始,而且#和指令其余部分之间不能有空格。指令可以出现在源文件的任何地方,其定义从指令出现的地方到该文件末尾有效。

#define除了定义明示常量的其他用途

我们大量使用#define指令来定义明示常量(manifest constant)(也叫做符号常量),但是该指令还有许多其他用途。

以下程序演示了#define指令的一些用法和属性。

预处理器指令从#开始运行,到后面的第1个换行符为止。也就是说,指令的长度仅限于一行。然而,前面提到过,在预处理开始前,编译器会把多行物理行处理为一行逻辑行。

cpp 复制代码
/* preproc.c -- 简单的预处理示例 */
#include <stdio.h>
#define TWO 2 /* 可以使用注释 */
#define OW "Consistency is the last refuge of the unimagina\
tive.- Oscar Wilde" /* 反斜杠把该定义延续到下一行 */
#define FOUR TWO*TWO
#define PX printf("X is %d.\n", x)
#define FMT "X is %d.\n"
int main(void)
{
	int x = TWO;
	PX;
	x = FOUR;
	printf(FMT, x);
	printf("%s\n", OW);
	printf("TWO: OW\n");
	return 0;
}

输出:

cpp 复制代码
运行该程序示例后,输出如下:
X is 2.
X is 4.
Consistency is the last refuge of the unimaginative.- Oscar Wilde
TWO: OW
下面分析具体的过程。下面的语句:
int x = TWO;
变成了:
int x = 2;
2代替了TWO。而语句:
PX;
变成了:
printf("X is %d.\n", x);

这里同样进行了替换。这是一个新用法,到目前为止我们只是用宏来表

示明示常量。从该例中可以看出,宏可以表示任何字符串,甚至可以表示整

个 C 表达式。但是要注意,虽然 PX 是一个字符串常量,它只打印一个名为

x的变量。

下一行也是一个新用法。读者可能认为FOUR被替换成4,但是实际的

过程是:

cpp 复制代码
x = FOUR;

变成了:

cpp 复制代码
x = TWO*TWO;

即是:

cpp 复制代码
x = 2*2;

宏展开到此处为止。由于编译器在编译期对所有的常量表达式(只包含

常量的表达式)求值,所以预处理器不会进行实际的乘法运算,这一过程在

编译时进行。预处理器不做计算,不对表达式求值,它只进行替换。

#define的组成

每行#define(逻辑行)都由3部分组成。

#define本身:预处理指令

第1部分是#define指令本身。

第2部分是选定的缩写,也称为宏。有些宏代表值(如本例),这些宏被称为

类对象宏(object-like macro)。C 语言还有类函数宏(function-like

macro),稍后讨论。宏的名称中不允许有空格,而且必须遵循C变量的命

名规则:只能使用字符、数字和下划线(_)字符,而且首字符不能是数

字。

替换列表或替换体

第3部分(指令行的其余部分)称为替换列表或替换体(见图)。

宏展开

一旦预处理器在程序中找到宏的示实例后,就会用替换体代替该宏(也有例外,稍后解释)。从宏变成最终替换文本的过程称为宏展开(macro expansion)。

注意,可以在#define行使用标准C注释。如前所述,每条注释都会被一个空格代替。

参考资料

《C Primer Plus》

相关推荐
小俊俊的博客3 分钟前
海康RGBD相机使用C++和Opencv采集图像记录
c++·opencv·海康·rgbd相机
嵌入式科普17 分钟前
十三、从0开始卷出一个新项目之瑞萨RZN2L串口DMA接收不定长
c语言·stm32·瑞萨·e2studio·rzn2l
_WndProc18 分钟前
C++ 日志输出
开发语言·c++·算法
薄荷故人_19 分钟前
从零开始的C++之旅——红黑树及其实现
数据结构·c++
m0_7482400219 分钟前
Chromium 中chrome.webRequest扩展接口定义c++
网络·c++·chrome
qq_4335545427 分钟前
C++ 面向对象编程:+号运算符重载,左移运算符重载
开发语言·c++
努力学习编程的伍大侠31 分钟前
基础排序算法
数据结构·c++·算法
yuyanjingtao1 小时前
CCF-GESP 等级考试 2023年9月认证C++四级真题解析
c++·青少年编程·gesp·csp-j/s·编程等级考试
闻缺陷则喜何志丹1 小时前
【C++动态规划 图论】3243. 新增道路查询后的最短距离 I|1567
c++·算法·动态规划·力扣·图论·最短路·路径
charlie1145141912 小时前
C++ STL CookBook
开发语言·c++·stl·c++20