03 | 预处理阶段能做什么:宏定义和条件编译
该思维导图由 AI 生成,仅供参考
预处理编程
- 深入了解
- 翻译
- 解释
- 总结
预处理阶段在C/C++程序中扮演着重要角色,通过预处理指令控制预处理器,将源码改造成另一种形式。预处理指令以符号“#”开头,不受C++语法规则的约束,包括#include、#define、#if等。宏定义在预处理阶段无条件内联,对于调用频繁的小代码片段来说,用宏来封装的效果比inline关键字更好。然而,宏是没有作用域概念的,永远是全局生效,因此在使用一些临时作用的宏后,最好及时用#undef取消定义,避免冲突的风险。预处理阶段的宏定义和条件编译是C/C++程序中非常重要的一部分,合理使用预处理指令可以提高代码的整洁性和可读性。条件编译利用“#define”定义出的各种宏,在预处理阶段实现分支处理,通过判断宏的数值来产生不同的源码,改变源文件的形态。除了内置宏,也可以用其他手段自己定义更多的宏来实现条件编译。条件编译还有一个特殊的用法,使用“#if 1”“#if 0”来显式启用或者禁用大段代码,要比“/* ... */”的注释方式安全得多,也清楚得多。预处理编程由预处理器执行,使用#include、#define、#if等指令来实现文件包含、文本替换、条件编译,把编码阶段产生的源码改变为另外一种形式。适当使用的话,可以简化代码、优化性能,但如果是“炫技”式地过分使用,就会导致导致代码混乱,难以维护。
《罗剑锋的 C++ 实战笔记》,新⼈⾸单¥59
全部留言(41)
- 最新
- 精选
- sugar老师,能否讲讲#include 各种头文件的细节,比如尖角和引号 在include时就是不同的。
作者回复: 这个区别就是包含时的搜索路径不同,网上资料都有,所以就不重复了。好像“”是从当前开始搜索,<>是从系统路径开始搜索。
2020-05-2127 - 嵇斌1. #define PI (3.14) -> constexpr float PI = 3.14 其他貌似不能完全对等。比如内链的代码块,可以用lambda,但是效率如何得看编译器的优化了。比如有些条件编译可以想办法用enable_if来替换实现。到了后面这些替换就不单单是语言方面的实践了,可能涉及软件工程、设计模式。 2. 条件编译自己用还好,自己一般都还清楚自己的套路。条件编译最头疼的就是对着代码定位问题,这个实现到底是哪个代码块呢?尤其是那种跨平台的和OS相关的适配层代码。再聪明的IDE和编译器也还是有点区别的。当然现在可能Clion和VS等都已经比较完善了...
作者回复: 说的很好。 1.constexpr是编译期的常量,和预处理期的常量效果等价,但生命周期不同。 2.C++对同一个问题有多种解法,虽然自由,但也有选择困难症,挑出合适自己的就比较累。 3.这个也是追求性能付出的代价吧,怕的就是四处散落的#if-#else-#endif,最好把平台相关的代码集中在一起,再用#include一次包含,可以参考Nginx,它做的很好。
2020-05-1220 - 神经旷野舞者老师 rust很安全,现在大公司很多项目都用rust改写了,cpp还需要学吗,是不是直接学rust呢
作者回复: 可以看一下tobie榜单,C++还在前五,很多框架、系统都是用C++写的,学习它可以更好地理解系统架构。 当然最后你不一定在工作中用到C++,但它作为一个基础,会受用终身的。
2020-05-15314 - 怪兽老师,我在看rapidjson源码时,看到如下代码,不知道有什么作用,为什么不直接使用assert? #ifndef RAPIDJSON_ASSERT #define RAPIDJSON_ASSERT(x) assert(x) #endif // RAPIDJSON_ASSERT
作者回复: 这是开源库的通常做法,把调用的接口用宏或者函数统一做一个自己的封装,这样写代码的时候风格更一致。 在设计模式里这个叫包装外观wrapper facade。
2020-06-2711 - Wynter老师,我最近在阅读一个事件驱动框架代码libev,源码里就充斥着大量的宏,阅读起来很是费力。所以请教老师,阅读这种宏比较多的源码应该怎么办?
作者回复: 如果有好的IDE,它可以帮你找出宏的定义,加快理解。 还有一种办法,就是用课程里的“gcc -E”,让预处理器给你展开宏。
2020-05-1237 - Tedeer在读到这段话时:另存为一个“*.inc”文件,然后再用“#include”替换原来的大批数字。 想起以前开发过程中,曾经在头文件中定义了一个240*160的图片字节数组,现在看来有点蠢,又涨知识了。条件编译在Android系统源码见得比较多,区分不同平台之间代码块的实现。
作者回复: 预处理就是面向源码编程,调整源码的形态,掌握了这一点就可以让代码更干净整齐。
2020-05-1226 - Carlos先回答老师的问题🤔 1. 就中间那个算立方体的例子来说, 我觉得这个用法可以替换成一个 inline function. 储存常数可以直接用 const, 为了简化可以用 reference 2. 说实话, 我没用过条件编译 😂, 今天头一次学到, 但是我觉得这肯定会让代码变得更加复杂, 冒出很多意料之外的 bugs. 再提两个自己的小问题: 1. 使用宏的条件编译是不是可以用 Cmake 来做呢? (虽然我几乎不会用 Cmake 但是我预感它有这个功能) 2. 上文里面的 extern "C" { 部分我有些没看懂, 这个是预处理语言吗? 希望老师帮助理解.
作者回复: 1.对,在C++里大部分函数宏都可以用inline函数代替。 2.条件编译多用在跨平台和高级优化方面,不用当然也可以,但要追求性能极致就必须考虑。 3.cmake不了解,其实预处理就是文本替换,你要是愿意,用shell、Python也可以处理C++源码来实现。 4.extern "C" { }是处理编译器的链接符号,保持与C兼容,通常是为了导出外部接口使用的。暂时不了解也没关系,以后用到的时候再学也来得及。
2020-05-1256 - 企鹅君需要充电1、#include基本不会被替代,但为了避免#define滥用,少用它设置全局变量,const/static/enum处理常量声明还能自带额外信息(例如作用域) 2、debug期间用条件编译方便,但有时候会忽略另一种情况的判断测试,而且写死忘了改回来就gg了
作者回复: 说的很对。 C++今后要引入import关键字和模块,以后#include等预处理指令可能就会越来越少了,不过有的时候还是很有用的。
2021-02-234 - sugar另一个有关include头文件的问题。比如说我看很多例子都是 在单独一个h文件和一个cpp文件中定义了一个类,h里写了类的声明,类的具体实现在cpp里面。其他cpp文件里想引入这个类的时候,就直接include了h文件,并没include cpp文件,按照咱们这一讲 只有声明被预处理时放到实际执行的cpp代码里了,类的实现cpp是怎么引入进来的呢?
作者回复: h里一般只是声明,cpp里是实现,在编译阶段会有个链接动作,把这些都链接起来。 具体的细节比较复杂,我也没细研究过,可以网上找点资料看看,不过我觉得不了解也没关系。
2020-05-214 - 廖熊猫我的一些使用经历: 1. 使用#ifdef __cplusplus这个在用Emscripten编译wasm的时候会跟extern "c" { } 这个一起使用,防止编译后名字被修改掉。 2. 在C语言里直接使用const定义的长度在全局定义数组会报错,但是可以用预处理器来创建。 3. 快速定义一些内容相似的struct还有相关的操作函数,不过在C++里面应该可以使用template来解决这个问题了吧
作者回复: 1.条件编译加__cplusplus经常用在与C配合工作方面,算是个惯用语了。 2.C语言对const的支持比较弱,在C++中好很多。 3.宏在减少重复代码方面有时候还是挺有用的,因为模板和泛型的语法检查很严格,要写好还是要费些力气的,如果是简单的工作用宏就会轻松一些。
2020-05-124