@@ -213,7 +213,190 @@ void try_call_foo(auto &t) {
213
213
// 从现代的 C++23 学起,先知道正常的写法“理应”是什么样。工作中用不上 C++23?我会向你介绍,如果要倒退回 C++14,古代人都是用什么“奇技淫巧”实现同样的效果。
214
214
// 这样你最后同样可以适应公司要求的 C++14 环境。但是从 C++23 学起,你的思维又不会被应付古代语言缺陷的“奇技淫巧”扰乱,学起来就事半功倍。
215
215
216
- = 开始
216
+ = 开发环境与平台选择
217
+
218
+ TODO
219
+
220
+ == IDE 不是编译器!
221
+
222
+ TODO
223
+
224
+ == 编译器是?
225
+
226
+ 编译器是将源代码 (`.cpp`) 编译成可执行程序 (`.exe`) 的工具。
227
+
228
+ #fun[C++ 是*编译型语言*,源代码不能直接执行哦!刚开始学编程的小彭老师曾经把网上的 “Hello, World” 代码拷贝到 `.c` 源码文件中,然后把后缀名改成 `.exe`,发现这样根本执行不了……后来才知道需要通过一种叫做*编译器*编译 `.c` 文件,才能得到计算机可以直接执行的 `.exe` 文件。]
229
+
230
+ C++ 源码 `.cpp` 是写给人类看的!计算机并不认识,计算机只认识二进制的机器码。要把 C++ 源码转换为计算机可以执行的机器码。
231
+
232
+ == 编译器御三家
233
+
234
+ 最常见的编译器有:GCC、Clang、MSVC
235
+
236
+ #fun[俗称“御三家”。]
237
+
238
+ 这些编译器都支持了大部分 C++20 标准和小部分 C++23 标准,而 C++17 标准都是完全支持的。
239
+
240
+ #fun[有人说过:“如果你不知道一个人是用的什么编译器,那么你可以猜他用的是 GCC。”]
241
+
242
+ - GCC 主要只在 Linux 和 MacOS 等 Unix 类系统可用,不支持 Windows 系统。但是 GCC 有着大量好用的扩展功能,例如大名鼎鼎的 `pbds`(基于策略的数据结构),还有各种 `__attribute__`,各种 `__builtin_` 系列函数。不过随着新标准的出台,很多原本属于 GCC 的功能都成了标准的一部分,例如 `__attribute__((warn_unused))` 变成了标准的 `[[nodiscard]]`,`__builtin_clz` 变成了标准的 `std::countl_zero`,`__VA_OPT__` 名字都没变就进了 C++20 标准。
243
+
244
+ #fun[PBDS 又称 “平板电视”]
245
+
246
+ - 也有 MinGW 这样的魔改版 GCC 编译器,把 GCC 移植到了 Windows 系统上,同时也能用 GCC 的一些特性。不过 MinGW 最近已经停止更新,最新的 GCC Windows 移植版由 MinGW-w64 继续维护。
247
+
248
+ - Clang 是跨平台的编译器,支持大多数主流平台,包括操作系统界的御三家:Linux、MacOS、Windows。Clang 支持了很大一部分 GCC 特性和部分 MSVC 特性。其所属的 LLVM 项目更是编译器领域的中流砥柱,不仅支持 C、C++、Objective-C、Fortran 等,Rust 和 Swift 等语言也是基于 LLVM 后端编译的,不仅如此,还有很多显卡厂商的 OpenGL 驱动也是基于 LLVM 实现编译的。并且 Clang 身兼数职,不仅可以编译,还支持静态分析。许多 IDE 常见的语言服务协议 (LSP) 就是基于 Clang 的服务版————Clangd 实现的 (例如你可以按 Ctrl 点击,跳转到函数定义,这样的功能就是 IDE 通过调用 Clangd 的 LSP 接口实现)。不过 Clang 的性能优化比较激进,虽然有助于性能提升,如果你不小心犯了未定义行为,Clang 可能优化出匪夷所思的结果,如果你要实验未定义行为,Clang 是最擅长复现的。且 Clang 对一些 C++ 新标准特性支持相对较慢,没有 GCC 和 MSVC 那么上心。
249
+
250
+ #tip[例如 C++20 早已允许 lambda 表达式捕获 structural-binding 变量,而 Clang 至今还没有支持,尽管 Clang 已经支持了很多其他 C++20 特性。]
251
+
252
+ - Apple Clang 是苹果公司自己魔改的 Clang 版本,只在 MacOS 系统上可用,支持 Objective-C 和 Swift 语言。但是版本较官方 Clang 落后一些,很多新特性都没有跟进,基本上只有专门伺候苹果的开发者会用。
253
+
254
+ #tip[GCC 和 Clang 也支持 Objective-C。]
255
+
256
+ - MSVC 是 Windows 限定的编译器,提供了很多 MSVC 特有的扩展。也有人在 Clang 上魔改出了 MSVC 兼容模式,兼顾 Clang 特性的同时,支持了 MSVC 的一些特性(例如 `__declspec`),可以编译用了 MSVC 特性的代码,即 `clang-cl`,在最新的 VS2022 IDE 中也集成了 `clang-cl`。值得注意的是,MSVC 的优化能力是比较差的,比 GCC 和 Clang 都差,例如 MSVC 几乎总是假定所有指针 aliasing,这意味着当遇到很多指针操作的循环时,几乎没法做循环矢量化。但是也使得未定义行为不容易产生 Bug,另一方面,这也导致一些只用 MSVC 的人不知道某些写法是未定义行为。
257
+
258
+ - Intel C++ compiler 是英特尔开发的 C++ 编译器,由于是硬件厂商开发的,特别擅长做性能优化。但由于更新较慢,基本没有更上新特性,也没什么人在用了。
259
+
260
+ #tip[最近他们又出了个 Intel DPC++ compiler,支持最新的并行编程领域特定语言 SyCL。]
261
+
262
+ == 使用编译器编译源码
263
+
264
+ === MSVC
265
+
266
+ ```cmd
267
+ cl.exe /c main.cpp
268
+ ```
269
+
270
+ 这样就可以得到可执行文件 `main.exe` 了。
271
+
272
+ === GCC
273
+
274
+ ```bash
275
+ g++ -c main.cpp -o main
276
+ ```
277
+
278
+ 这样就可以得到可执行文件 `main` 了。
279
+
280
+ #tip[Linux 系统的可执行文件并没有后缀名,所以没有 `.exe` 后缀。]
281
+
282
+ === Clang
283
+
284
+ Windows 上:
285
+
286
+ ```bash
287
+ clang++.exe -c main.cpp -o main.exe
288
+ ```
289
+
290
+ Linux / MacOS 上:
291
+
292
+ ```bash
293
+ clang++ -c main.cpp -o main
294
+ ```
295
+
296
+ == 编译器选项
297
+
298
+ 编译器选项是用来控制编译器的行为的。不同的编译器有不同的选项,语法有微妙的不同,但大致功效相同。
299
+
300
+ 例如当我们说“编译这个源码时,我用了 GCC 编译器,`-O3` 和 `-std=c++20` 选项”,说的就是把这些选项加到了 `g++` 的命令行参数中:
301
+
302
+ ```bash
303
+ g++ -O3 -std=c++20 -c main.cpp -o main
304
+ ```
305
+
306
+ 其中 Clang 和 GCC 的编译器选项有很大交集。而 MSVC 基本自成一派。
307
+
308
+ Clang 和 GCC 的选项都是 `-xxx` 的形式,MSVC 的选项是 `/xxx` 的形式。
309
+
310
+ 常见的编译器选项有:
311
+
312
+ === C++ 标准
313
+
314
+ 指定要选用的 C++ 标准。
315
+
316
+ Clang 和 GCC:`-std=c++98`、`-std=c++03`、`-std=c++11`、`-std=c++14`、`-std=c++17`、`-std=c++20`、`-std=c++23`
317
+
318
+ MSVC:`/std:c++98`、`/std:c++11`、`/std:c++14`、`/std:c++17`、`/std:c++20`、`/std:c++latest`
319
+
320
+ 例如要编译一个 C++20 源码文件,分别用 GCC、Clang、MSVC:
321
+
322
+ GCC(Linux):
323
+
324
+ ```bash
325
+ g++ -std=c++20 -c main.cpp -o main
326
+ ```
327
+
328
+ Clang(Linux):
329
+
330
+ ```bash
331
+ clang++ -std=c++20 -c main.cpp -o main
332
+ ```
333
+
334
+ MSVC(Windows):
335
+
336
+ ```bash
337
+ cl.exe /std:c++20 /c main.cpp
338
+ ```
339
+
340
+ === 优化等级
341
+
342
+ Clang 和 GCC:`-O0`、`-O1`、`-O2`、`-O3`、`-Ofast`、`-Os`、`-Oz`、`-Og`
343
+
344
+ - `-O0`:不进行任何优化,编译速度最快,忠实复刻你写的代码,未定义行为不容易产生诡异的结果,一般用于开发人员内部调试阶段。
345
+ - `-O1`:最基本的优化,会把一些简单的死代码(编译器检测到的不可抵达代码)删除,去掉没有用的变量,把部分变量用寄存器代替等,编译速度较快,执行速度也比 `-O0` 快。但是会丢失函数的行号信息,影响诸如 gdb 等调试,如需快速调试可以用 `-Og` 选项。
346
+ - `-O2`:比 `-O1` 更强的优化,会把一些循环展开,把一些函数内联,减少函数调用,把一些简单的数组操作用更快的指令替代等,执行速度更快。
347
+ - `-O3`:比 `-O2` 更激进的优化,会把一些复杂的循环用 SIMD 矢量指令优化加速,把一些复杂的数组操作用更快的指令替代等。性能提升很大,但是如果你的程序有未定义行为,可能会导致一些 Bug。如果你的代码没有未定义行为则绝不会有问题,对自己的代码质量有自信就可以放心开,编译速度也会很慢,一般用于程序最终成品发布阶段。
348
+ - `-Ofast`:在 `-O3` 的基础上,进一步对浮点数的运算进行更深层次的优化,但是可能会导致一些浮点数计算结果不准确。如果你的代码不涉及到 NaN 和 Inf 的处理,那么 `-Ofast` 不会有太大的问题,一般用于科学计算领域的终极性能优化。
349
+ - `-Os`:在 `-O2` 的基础上,专门优化代码大小,性能被当作次要需求,但是会禁止会导致可执行文件变大的优化。会把一些循环展开、内联等优化关闭,把一些代码用更小的指令实现,尽可能减小可执行文件的尺寸,比 `-O0`、`-O1`、`-O2` 都要小,通常用于需要节省内存的嵌入式系统开发。
350
+ - `-Oz`:在 `-Os` 的基础上,进一步把代码压缩,可能把本可以一条大指令完成的任务也拆成多条小指令,为了尺寸完全性能,大幅减少了函数内联的机会,有时用于嵌入式系统开发。
351
+ - `-Og`:在 `-O0` 的基础上,尽可能保留更多调试信息,不做破坏函数行号等信息的优化,建议配合产生更多调试信息的 `-g` 选项使用。但还是会做一些简单的优化,比 `-O0` 执行速度更快。但 `-Og` 的所有优化都不会涉及到未定义行为,因此非常适合调试未定义行为。但是由于插入了调试信息,最终的可执行文件会变得很大,一般在开发人员调试时使用。
352
+
353
+ MSVC:`/Od`、`/O1`、`/O2`、`/Ox`、`/Ob1`、`/Ob2`、`/Os`
354
+
355
+ - `/Od`:不进行任何优化,忠实复刻你写的代码,未定义行为不容易产生诡异的结果,一般用于调试阶段。
356
+ - `/O1`:最基本的优化,会把一些简单的死代码删除,去掉没有用的变量,把变量用寄存器代替等。
357
+ - `/O2`:比 `/O1` 更强的优化,会把一些循环展开,把一些函数内联,减少函数调用,还会尝试把一些循环矢量化,把一些简单的数组操作用更快的指令替代等。一般用于发布阶段。
358
+ - `/Ox`:在 `/O2` 的基础上,进一步优化,但是不会导致未定义行为,一般用于发布阶段。
359
+ - `/Ob1`:启用函数内联。
360
+ - `/Ob2`:启用函数内联,但是会扩大内联范围,一般比 `/Ob1` 更快,但是也会导致可执行文件变大。
361
+ - `/Os`:在 `/O2` 的基础上,专门优化代码大小,性能被当作次要需求,但是会禁止会导致可执行文件变大的优化。会把一些循环展开、内联等优化关闭,把一些代码用更小的指令实现,尽可能减小可执行文件的尺寸,通常用于需要节省内存的嵌入式系统开发。
362
+
363
+ === 调试信息
364
+
365
+ Clang 和 GCC:`-g`、`-g0`、`-g1`、`-g2`、`-g3`
366
+
367
+ MSVC:`/Z7`、`/Zi`
368
+
369
+ === 头文件搜索路径
370
+
371
+ === 指定要链接的库
372
+
373
+ === 库文件搜索路径
374
+
375
+ === 定义宏
376
+
377
+ Clang 和 GCC:`-Dmacro=value`
378
+
379
+ MSVC:`/Dmacro=value`
380
+
381
+ 例如:
382
+
383
+ === 警告开关
384
+
385
+ == 标准库御三家
386
+
387
+ - libstdc++ 是 GCC 官方的 C++ 标准库实现,由于 GCC 是 Linux 系统的主流编译器,所以 libstdc++ 也是 Linux 上最常用的标准库。你可以在这里看到他的源码:https://github.com/gcc-mirror/gcc/tree/master/libstdc%2B%2B-v3
388
+
389
+ - libc++ 是 Clang 官方编写的 C++ 标准库实现,由于 Clang 是 MacOS 系统的主流编译器,所以 libc++ 也是 MacOS 上最常用的标准库。libc++ 也是 C++ 标准库中最早实现 C++11 标准的。项目的开源地址是:https://github.com/llvm/llvm-project/tree/main/libcxx
390
+
391
+ - MSVC STL 是 MSVC 官方的 C++ 标准库实现,由于 MSVC 是 Windows 系统的主流编译器,所以 MSVC STL 也是 Windows 上最常用的标准库。MSVC STL 也是 C++ 标准库中最晚实现 C++11 标准的,但是现在他已经完全支持 C++20,并且也完全开源了:https://github.com/microsoft/STL
392
+
393
+ 值得注意的是,标准库和编译器并不是绑定的,例如 Clang 可以用 libstdc++ 或 MSVC STL,GCC 也可以被配置使用 libc++。
394
+
395
+ 在 Linux 系统中,Clang 默认用的就是 libstdc++。需要为 Clang 指定 `-stdlib=libc++` 选项,才能使用。
396
+
397
+ #fun[牛头人笑话:“如果你不知道一个人是用的什么标准库,那么你可以猜他用的是 libstdc++。即使他的编译器是 Clang,他用的大概率依然是 libstdc++。”]
398
+
399
+ = 你好,世界
217
400
218
401
== 什么是函数
219
402
@@ -258,7 +441,7 @@ int main()
258
441
259
442
#fun[因此,`main` 可以被看作是#quote[宇宙大爆炸]。]
260
443
261
- == main 函数返回值
444
+ == main 函数的返回值
262
445
263
446
```cpp
264
447
int main()
@@ -279,7 +462,7 @@ main 函数总是返回一个整数 (`int` 类型),用这个整数向操作系
279
462
操作系统:我调用了你这个程序的 main 函数,我好奇程序是否正确执行了?让我们约定好:如果你运转正常的话,就返回0表示成功哦!如果有错误的话,就返回一个错误代码,比如返回1表示无权限,2表示找不到文件……之类的。当然,错误代码都是不为0的。
280
463
]
281
464
282
- == 黑色的窗口 ?
465
+ == 这个黑色的窗口是 ?
283
466
284
467
TODO: 介绍控制台
285
468
@@ -354,7 +537,11 @@ int main()
354
537
在我们以后的案例代码中,都会像这样注释说明,充当*就地讲解员*的效果。去除这些注释并不影响程序的正常运行,添加文字注释只是小彭老师为了提醒你每一行的代码作用。
355
538
]
356
539
357
- = 函数
540
+ = 变量与类型
541
+
542
+ TODO
543
+
544
+ = 自定义函数
358
545
359
546
函数可以没有返回值,只需要返回类型写 `void` 即可,这样的函数调用的目的只是为了他的副作用(如修改全局变量,输出文本到控制台,修改引用参数等)。
360
547
0 commit comments