罗剑锋的C++实战笔记
罗剑锋
奇虎360技术专家,Nginx/OpenResty开源项目贡献者
立即订阅
3798 人已学习
课程目录
已完结 30 讲
0/4登录后,你可以任选4讲全文学习。
课前导读 (2讲)
开篇词 | 把C++从“神坛”上拉下来,这次咱这么学
免费
课前准备 | 搭建实验环境
概论 (5讲)
01 | 重新认识C++:生命周期和编程范式
02 | 编码阶段能做什么:秀出好的code style
03 | 预处理阶段能做什么:宏定义和条件编译
04 | 编译阶段能做什么:属性和静态断言
05 | 面向对象编程:怎样才能写出一个“好”的类?
语言特性 (5讲)
06 | auto/decltype:为什么要有自动类型推导?
07 | const/volatile/mutable:常量/变量究竟是怎么回事?
08 | smart_ptr:智能指针到底“智能”在哪里?
09 | exception:怎样才能用好异常?
10 | lambda:函数式编程带来了什么?
标准库 (4讲)
11 | 一枝独秀的字符串:C++也能处理文本?
12 | 三分天下的容器:恰当选择,事半功倍
13 | 五花八门的算法:不要再手写for循环了
14 | 十面埋伏的并发:多线程真的很难吗?
技能进阶 (4讲)
15 | 序列化:简单通用的数据交换格式有哪些?
16 | 网络通信:我不想写原生Socket
17 | 脚本语言:搭建高性能的混合系统
18 | 性能分析:找出程序的瓶颈
总结篇 (5讲)
19 | 设计模式(上):C++与设计模式有啥关系?
20 | 设计模式(下):C++是怎么应用设计模式的?
21 | 知识串讲(上):带你开发一个书店应用
22 | 知识串讲(下):带你开发一个书店应用
期末测试 | 这些C++核心知识,你都掌握了吗?
结束语 (1讲)
结束语 | 路远,未有穷期
轻松话题 (4讲)
轻松话题(一) | 4本值得一读再读的经典好书
轻松话题(二) | 给你分享我的工作百宝箱
轻松话题(三) | 提高生活质量的App
轻松话题(四) | 真正高效的生活,是张弛有度
罗剑锋的C++实战笔记
15
15
1.0x
00:00/00:00
登录|注册

03 | 预处理阶段能做什么:宏定义和条件编译

罗剑锋 2020-05-12
你好,我是 Chrono。
上一次我讲了在编码阶段要有好的 code style,尽量写出“人能够看懂的代码”。今天,我就继续讲讲编码后的预处理阶段,看看这个阶段我们能做哪些事情。

预处理编程

其实,只要写 C/C++ 程序,就会用到预处理,只是大多数时候,你只用到了它的一点点功能。比如,在文件开头写上“#include”这样的语句,或者用“#define”定义一些常数。只是这些功能都太简单了,没有真正发挥出预处理器的本领,所以你可能几乎感觉不到它的存在。
预处理只能用很少的几个指令,也没有特别严谨的“语法”,但它仍然是一套完整自洽的语言体系,使用预处理也能够实现复杂的编程,解决一些特别的问题——虽然代码可能会显得有些“丑陋”“怪异”。
那么,“预处理编程”到底能干什么呢?
你一定要记住:预处理阶段编程的操作目标是“源码”,用各种指令控制预处理器,把源码改造成另一种形式,就像是捏橡皮泥一样。
把上面的这句话多读几遍,仔细揣摩体会一下,理解了之后,你再去用那些预处理指令就会有不一样的感觉了。
C++ 语言有近百个关键字,预处理指令只有十来个,实在是少得可怜。而且,常用的也就是 #include、#define、#if,所以很容易掌握。不过,有几个小点我还是要特别说一下。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《罗剑锋的C++实战笔记》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(27)

  • 嵇斌
    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-12
    5
  • 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-12
    1
    4
  • Tedeer
    在读到这段话时:另存为一个“*.inc”文件,然后再用“#include”替换原来的大批数字。
    想起以前开发过程中,曾经在头文件中定义了一个240*160的图片字节数组,现在看来有点蠢,又涨知识了。条件编译在Android系统源码见得比较多,区分不同平台之间代码块的实现。

    作者回复: 预处理就是面向源码编程,调整源码的形态,掌握了这一点就可以让代码更干净整齐。

    2020-05-12
    1
    3
  • EncodedStar
    函数式的方法可以用 inline function, 内联inline类似于宏,使用inline时,代码在执行前,编译器先将调用的inline函数替换城那个函数的执行代码。
    #if endif 这个是复只能制用宏来做百的 其他方式都不行另外,度编译命令行传问递变量值也只能通过宏来做。
    之前写过嵌入式程序,条件编译见得比较多,程序在不同系统下都需要运行时就要条件编译,条件编译百为了提高程序的可移植性,增加程序的灵活性。还有老师提到的“不传之秘”#if 1 #end if也是条件编译,这个注释大段代码调试特别有用。

    作者回复: 说的很好。

    2020-05-18
    2
  • 极客时间
    老师 rust很安全,现在大公司很多项目都用rust改写了,cpp还需要学吗,是不是直接学rust呢

    作者回复: 可以看一下tobie榜单,C++还在前五,很多框架、系统都是用C++写的,学习它可以更好地理解系统架构。

    当然最后你不一定在工作中用到C++,但它作为一个基础,会受用终身的。

    2020-05-15
    2
  • 廖熊猫
    我的一些使用经历:
    1. 使用#ifdef __cplusplus这个在用Emscripten编译wasm的时候会跟extern "c" { } 这个一起使用,防止编译后名字被修改掉。
    2. 在C语言里直接使用const定义的长度在全局定义数组会报错,但是可以用预处理器来创建。
    3. 快速定义一些内容相似的struct还有相关的操作函数,不过在C++里面应该可以使用template来解决这个问题了吧

    作者回复:
    1.条件编译加__cplusplus经常用在与C配合工作方面,算是个惯用语了。

    2.C语言对const的支持比较弱,在C++中好很多。

    3.宏在减少重复代码方面有时候还是挺有用的,因为模板和泛型的语法检查很严格,要写好还是要费些力气的,如果是简单的工作用宏就会轻松一些。

    2020-05-12
    2
  • sugar
    老师,能否讲讲#include 各种头文件的细节,比如尖角和引号 在include时就是不同的。

    作者回复: 这个区别就是包含时的搜索路径不同,网上资料都有,所以就不重复了。好像“”是从当前开始搜索,<>是从系统路径开始搜索。

    2020-05-21
    1
  • Soda
    第一个问题,能想到的就是定义常量用const代替,因为编译器会替我们检查类型。
    第二个问题,用过条件编译,很适合在开发嵌入式程序的时候,做一些跨平台的处理😁

    作者回复: 说的很好。

    2020-05-15
    1
  • Geek_bc5665
    pragram once 不是也可以防止重复包含?

    作者回复: 可以,但不具有普适性,一般不推荐使用。

    2020-05-14
    1
    1
  • yelin
    老师真的经验丰富啊,赞

    作者回复: 这些都是写代码看代码多了总结出来的,希望能够帮助你少走些弯路。

    2020-05-13
    1
  • Wynter
    老师,我最近在阅读一个事件驱动框架代码libev,源码里就充斥着大量的宏,阅读起来很是费力。所以请教老师,阅读这种宏比较多的源码应该怎么办?

    作者回复: 如果有好的IDE,它可以帮你找出宏的定义,加快理解。

    还有一种办法,就是用课程里的“gcc -E”,让预处理器给你展开宏。

    2020-05-12
    2
    1
  • wuwei
    说实话,这节课对于cpp小白来讲太抽象了,很多专业术语是没有接触过的,应该往后挪一挪的

    作者回复: 嗯,预处理这块是学C++的一个难点,因为跟C++其实没多大关系,但确实又很有用。

    把它放在前面是因为它与C++程序的生命周期密切相关,了解它才能更好里理解C++,也能够帮你看懂别人写的C++代码。

    如果觉得难,不好学也没关系,只要先有个大概了解就好,后面也会尽量少用宏,以后在实践中再逐渐应用体会。

    2020-05-12
    1
    1
  • 怪兽
    老师,我在看rapidjson源码时,看到如下代码,不知道有什么作用,为什么不直接使用assert?
    #ifndef RAPIDJSON_ASSERT
    #define RAPIDJSON_ASSERT(x) assert(x)
    #endif // RAPIDJSON_ASSERT

    作者回复: 这是开源库的通常做法,把调用的接口用宏或者函数统一做一个自己的封装,这样写代码的时候风格更一致。

    在设计模式里这个叫包装外观wrapper facade。

    2020-06-27
  • 熊出没
    【另一种做法是宏定义前先检查,如果之前有定义就先 undef】
    对这里有个问题,先undef是否会导致已有定义被取消,但却获得不了提示?

    作者回复: undef的作用就是取消之前的宏定义,这个也不会有提示,目的就是这个,为了避免重复定义。

    2020-06-13
  • Eason Tai
    #elif __cplusplus >= 201103 // 检查C++标准的版本号
        cout << "c++11 or before" << endl; // 201103是C++11

    为什么不是 c++11 or later 呢

    作者回复: 这个好像是笔误,写错了,汗啊。

    先领会条件编译的用法吧。

    2020-05-24
  • sugar
    另一个有关include头文件的问题。比如说我看很多例子都是 在单独一个h文件和一个cpp文件中定义了一个类,h里写了类的声明,类的具体实现在cpp里面。其他cpp文件里想引入这个类的时候,就直接include了h文件,并没include cpp文件,按照咱们这一讲 只有声明被预处理时放到实际执行的cpp代码里了,类的实现cpp是怎么引入进来的呢?

    作者回复: h里一般只是声明,cpp里是实现,在编译阶段会有个链接动作,把这些都链接起来。

    具体的细节比较复杂,我也没细研究过,可以网上找点资料看看,不过我觉得不了解也没关系。

    2020-05-21
  • 有学识的兔子
    学习c++有些时间了,对c++几个版本是基于时间命名才了解到,有必要了解这些版本的大致区别。
    1. 宏不可替代的地方:基于它们早于运行阶段,例如一些操作是需要在运行前完成的;只读变量可以用const,类型别名typedef。
    2. 特别不喜欢有人在类的成员函数里用条件编译来作为代码的分支路径使用,给人感觉特别面向过程,也不理解为什么不考虑使用抽象基类分别实现子类或者实现更小颗粒的函数接口。

    作者回复: 条件编译通常是用来处理系统、平台差异的,如果要像你说的那么使用确实是有点过分。

    2020-05-16
  • 锦鲤
    想找些课程练手写C++代码,最好是开源的,有答案的那种,罗老师有推荐吗?
    Linux下C++编程方面的书,能否先推荐几本?中级水平的那种

    作者回复:
    1.C++开源项目很多都比较大,练手级别的少,我没有太关注,可能要你自己去GitHub上找一下了。
    可以先参考课程GitHub的readme,里面有很多链接。

    2.C++经典书比较少,Linux+C++就更少了,我没有看到特别好的。
    其实你应该分开来看,学C++,再学Linux,而不是Linux+C++。
    后面我会有个经典书推荐,学好C++,再来一本《UNIX环境高级编程》就差不多了。

    2020-05-14
    1
  • 忆水寒
    前一段时间刷了点leetcode题目,用C++写的。
    https://mp.weixin.qq.com/s/LYWEFFDVjUX6N48daq_QBQ

    作者回复: LeetCode太高端了,我那时候都没见过,感觉刷LeetCode的人都很厉害。

    2020-05-14
  • jxon-H
    这节课真是干货满满,我从来都不知道预处理阶段包含这么多知识,我还活在那个以为 # 就是只是和 include 搭配的世界。
    预处理阶段内容是那么的充实丰满……
    我满篇文稿的划线标笔记,但是回个头认真思考,这些丰富的经验总结,岂是几根线就可以消化的吗?
    脑子已经处于滞涨状态了……
    我和C++的隔阂太深了……不仅像是深深的代沟,还貌似是有点难以逾越的鸿沟。
    但愿我能爬出来!

    作者回复: 预处理是C++的重要组成部分,很强大也很有意思,但也很难用好。

    C++迷人之处就在这里,很多特性可以深挖,内涵丰富,但小心不要过度沉迷,一定要记得代码是写给别人看的。

    2020-05-14
    1
收起评论
27
返回
顶部