Skip to content

Commit 3a2d15f

Browse files
committed
updatesec1
1 parent 2d64139 commit 3a2d15f

File tree

2 files changed

+210
-6
lines changed

2 files changed

+210
-6
lines changed

README.md

+19-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,24 @@
1818

1919
本书完全开源,源文件为 [`cppguidebook.typ`](cppguidebook.typ)
2020

21-
如果发现书写问题,或者你有想加入的新章节,有关于 C++ 新想法,新技巧分享给大家,可以提交 [Pull Request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) 来帮助小彭老师一起写书。
21+
如果发现书写问题,或者你有想加入的新章节,有关于 C++ 新想法,新技巧分享给大家,可以提交 [Pull Request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) 来帮助小彭老师一起写书。合并后,GitHub 的机器人讲自动重新编译出 PDF。
22+
23+
添加新章节,可以从下面的“大纲 / Roadmap”中挑选,认领其中的一个章节,开始编写。也可以选择一个大纲里没有,但你认为很重要的方面来写。
24+
25+
# 大纲 / Roadmap
26+
27+
- 前言 (完成)
28+
- 开发环境与平台选择 (小彭老师施工中)
29+
- 你好,世界 (小彭老师施工中)
30+
- 变量与类型 (待认领)
31+
- 认识标准库 (待认领)
32+
- 自定义函数 (待认领)
33+
- 自定义类型 (待认领)
34+
- 标准库容器 (待认领)
35+
- 成员函数 (待认领)
36+
- 我们需要多态 (待认领)
37+
- 自定义模板 (待认领)
38+
- TODO: 更多章节
2239

2340
## Typst 真好用,家人们
2441

@@ -65,6 +82,6 @@ Typst 语言书写的 `.typ` 源文件编译后,得到可供阅读的 `.pdf`
6582
> [!TIP]
6683
> 目前企业里主流使用的是 C++14 和 C++17。例如谷歌就明确规定要求 C++17。
6784
68-
## 开发环境与平台选择
85+
# 开发环境与平台选择
6986

7087
[>> 继续阅读剩余章节](https://142857.red/files/cppguidebook.pdf)

cppguidebook.typ

+191-4
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,190 @@ void try_call_foo(auto &t) {
213213
// 从现代的 C++23 学起,先知道正常的写法“理应”是什么样。工作中用不上 C++23?我会向你介绍,如果要倒退回 C++14,古代人都是用什么“奇技淫巧”实现同样的效果。
214214
// 这样你最后同样可以适应公司要求的 C++14 环境。但是从 C++23 学起,你的思维又不会被应付古代语言缺陷的“奇技淫巧”扰乱,学起来就事半功倍。
215215
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+
= 你好,世界
217400
218401
== 什么是函数
219402
@@ -258,7 +441,7 @@ int main()
258441
259442
#fun[因此,`main` 可以被看作是#quote[宇宙大爆炸]。]
260443
261-
== main 函数返回值
444+
== main 函数的返回值
262445
263446
```cpp
264447
int main()
@@ -279,7 +462,7 @@ main 函数总是返回一个整数 (`int` 类型),用这个整数向操作系
279462
操作系统:我调用了你这个程序的 main 函数,我好奇程序是否正确执行了?让我们约定好:如果你运转正常的话,就返回0表示成功哦!如果有错误的话,就返回一个错误代码,比如返回1表示无权限,2表示找不到文件……之类的。当然,错误代码都是不为0的。
280463
]
281464
282-
== 黑色的窗口
465+
== 这个黑色的窗口是
283466
284467
TODO: 介绍控制台
285468
@@ -354,7 +537,11 @@ int main()
354537
在我们以后的案例代码中,都会像这样注释说明,充当*就地讲解员*的效果。去除这些注释并不影响程序的正常运行,添加文字注释只是小彭老师为了提醒你每一行的代码作用。
355538
]
356539
357-
= 函数
540+
= 变量与类型
541+
542+
TODO
543+
544+
= 自定义函数
358545
359546
函数可以没有返回值,只需要返回类型写 `void` 即可,这样的函数调用的目的只是为了他的副作用(如修改全局变量,输出文本到控制台,修改引用参数等)。
360547

0 commit comments

Comments
 (0)