C/C++ 预处理器

预处理是在 程序编译之前进行的一步操作。

翻译程序

这个操作是 预处理之前 的操作,在 预处理 之前,编译器会对源代码会进行一些翻译操作:

  1. 将源代码中出现的字符映射到 源字符集。
  2. 查找 反斜线 (\) 后 紧跟 换行符 (回车键产生的字符)的 实例,并删除这些实例。
  3. 编译器将文本划分为:语言符号(token) 序列,空白字符序列,注释序列。(token:空格分隔的组)
    1. 编译器用一个 空白字符 代替 一个注释。
  4. 然后,程序进入预处理阶段
cout << "hello \
world" << \
            endl;
// 根据 2, 上面的物理行 会被转换成
cout << "hello world" << endl;
int /*这里是注释*/ fox;
// 根据3, 上面的语句会转换成
int fox;

预处理器指令

预处理器指令

  • 都是由 # 开头
  • ANSI 允许 # 与指令的其余部分有空格,但是实际上并不行
  • # 开始,到第一个 换行符 为止,(指令的长度仅限于 一行逻辑代码

define

每个#define 行(逻辑行)由三部分组成:

  • #define 自身
  • 所选择的缩略语,这些 缩略语称为 宏(macro
    • 宏的名字中不允许有空格,而且必须遵循C变量命名规则
  • 替换列表(replacement list)或叫 主体(body), (这个地方可以省略,说明只是定义了这个一个宏)

预处理器在程序中发现了宏的实例后,总会用 主体 替换这个宏。

宏展开: 从宏变成最终文本的过程。

#define TWO 2
#define FOUR TWO*TWO

int main(){
  x = FOUR;
  return 0;
}
// 替换过程为
// x = TWO*TWO;
// x = 2*2;
  • 从上面示例可以看出,宏定义中可以包含其他宏!
    • 一般而言,预处理器发现程序中的宏后,会用它的等价替代文本代替宏,如果该 文本中 还包括宏,则继续替换这些宏。
    • 如果宏存在与双引号内,则不予替换。

语言符号

从技术方面看,系统将 宏的 主体 当作语言符号(token)类型字符串,而不是字符型字符串。

C预处理器中的 语言符号 是宏定义主体中 单独的词(空格分割开的词)。

// 这个定义的主体中 只有一个语言符号(token)即 2*3
#define SIX 2*3

// 这个定义的主体中,有三个语言符号,2 * 4,主体中的空格看作 分割语言符号的  符号
#define EIGHT 2 * 4

// 这个和 上面那个是一样的,额外的空格不看做主体的一部分
#define EIGHT 2    *    4

类函数宏

// 括号一定要贴着 PRINT!!!!
#define POWER(x) x*x

注意:

  • 宏的名字不能有空格,但是在 替代字符串 中可以有空格。
  • 主体中, 用圆括号 括住每个参数, 并括住整个主体。
  • 用大写字母表示 宏的名字

可变参数宏

  • 使用 ...__VA_ARGS__
#include <iostream>

using namespace std;
void add(int i, int j = 1) {
    cout << i + j << endl;
}

#define XNAME(...) add(__VA_ARGS__)

int main() {
    XNAME(1);
    XNAME(1, 4);
}

include

预处理器发现 #include 指令后,就会寻找 文件名 并把 这个文件内容 包含到 当前文件中。被包含文件中的文本将替换源代码文件中的 #include 指令。

#include <iostream> // 尖括号代表 搜索系统目录
#include "myHeader.h" // 引号代表,先搜索当前目录,再搜索系统目录
#include "/usr/biff/p.h" // 搜索 /usr/biff 目录

undef

用来取消 #define 的宏定义。

#define LIMIT 100
#undef LIMIT

宏的作用域 从 #define 开始,到 #undef 或文件尾 结束。

条件编译

#ifdef, #else, #endif

#ifdef MAVIS //如果定义了,执行下面的语句,否则到 else 中执行
    #include "horces.h"
#else
    #include "birds.h"
#endif

#ifdef HH
    #include "didi.h"
#endif //必须存在的

#if, #elif

# if SYS==1
    #define SYS_
#elif SYS==2
    #define SYS__
#endif
#define NE 10
int main() {
// 有无空格都可以
#if NE > 10
    cout << __DATE__ << endl;
}

#else
cout << __FILE__ << endl;
}

#endif

C/C++ 宏中,### 的用法

# 的作用

#的功能是将其后面的 宏参数 进行字符串化操作, 就是:宏变量替换后,左右各加一个双引号。(将语言符号转换成字符串!!!)

// #x 之间有无空格都可以
#define TEST(x) \
    #x
int main() {
    cout << "hello" << endl;
    cout << TEST("hello") << endl;
}

// 输出结果为:
// hello
// "hello"
// 可以看到 # 的作用是 加 双引号。

## 的作用

## 称之为 连接符(concatenator),将两个语言符号组合成单个语言符号。这里连接的对象是 Token 就行,不一定非要是 宏变量。

N## 连接 N+1Token

// 注意,x##n 有无空格都可以
#define XNAME(n) x ## n
#include <iostream>
using namespace std;
int main() {
    // XNAME(4) 会变成 x4 
    int XNAME(4) = 3;
    cout << x4 <<endl;
}

预定义的宏

  • __VA_ARGS__ : 可变宏用到
  • __DATE__ : 当前的时间
  • __FILE__:当前文件名
  • __TIME__: 源文件编译时间

参考资料

[C Primer Plus 5] p446

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏数据结构与算法

洛谷P4779 【模板】单源最短路径(标准版)

前几天写dijkstra的时候没打vis标记居然A了,然后天真的我就以为Dijkstra不用打标记。

1053
来自专栏Python小屋

Python编程一定要注意的那些“坑”(八):赋值运算符=

原始问题:下面的代码执行后为什么x的值是[2, 2]呢? >>> x = [3, 5, 7] >>> x = x[1:] = [2] >>> x [2, 2]...

2716
来自专栏ROBOTEDU

控制程序运行指令

控制程序运行指令 一 子程序 1. 概述 在零件程序分为“主程序”和“子程序”时,就出现了“子程序”的概念。子程序指由主程序调用的零件程序。在目前的SINUME...

2784
来自专栏智能算法

Python学习(一)---- Python基础必备

https://blog.csdn.net/fgf00/article/details/52061971

1493
来自专栏java一日一条

最全面的 Android 编码规范指南

这份文档参考了 Google Java 编程风格规范和 Google 官方 Android 编码风格规范。该文档仅供参考,只要形成一个统一的风格,见量知其意就可...

744
来自专栏小詹同学

Leetcode打卡 | No.009 回文数

欢迎和小詹一起定期刷leetcode,每周一和周五更新一题,每一题都吃透,欢迎一题多解,寻找最优解!这个记录帖哪怕只有一个读者,小詹也会坚持刷下去的!

632
来自专栏JetpropelledSnake

SQL学习之SQL注入学习总结

1554
来自专栏前端知识分享

第11天:JS中变量、字符串基础知识

js页面效果:轮播图、选项卡、地图、表单验证javascript是弱变量类型的语言,变量只需要用var来声明。而java要根据变 量类型来声明,

1103
来自专栏李蔚蓬的专栏

JavaScript_note1

4.6.字符串运算符 字符串运算符是用于两个字符型数据之间的运算符,除了比较运算符之外,还可以是+和+=运算符。 Demo:

622
来自专栏CDA数据分析师

产品运营数据分析——SPSS数据分组案例

当我们的样本量过大,譬如以前讲过的,EXCEL2010最大只支持1048576行、16384列,尤其是当行数大于30万,一般的办公电脑处理都比较吃力,所以推荐做...

1955

扫码关注云+社区