Skip to content

Commit 270dbef

Browse files
committed
fixfont
1 parent d9d85f5 commit 270dbef

File tree

3 files changed

+181
-184
lines changed

3 files changed

+181
-184
lines changed

docs/index.md

+181-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,191 @@
22

33
小彭大典是一本关于现代 C++ 编程的权威指南,它涵盖了从基础知识到高级技巧的内容,适合初学者和有经验的程序员阅读。本书由小彭老师亲自编写,通过简单易懂的语言和丰富的示例,帮助读者快速掌握 C++ 的核心概念,并学会如何运用它们来解决实际问题。
44

5-
[点击此处开始阅读第一章](start.md)
5+
> {{ icon.fun }} 敢承诺:土木老哥也能看懂!
66
7-
你也可以在本页面上方导航栏的“章节列表”中,选择感兴趣的章节阅读。
7+
# 前言
8+
9+
推荐用手机或平板**竖屏**观看,可以在床或沙发上躺着。
10+
11+
用电脑看的话,可以按 `WIN + ←`,把本书的浏览器窗口放在屏幕左侧,右侧是你的 IDE。一边看一边自己动手做实验。
12+
13+
![](img/slide.jpg)
14+
15+
> {{ icon.fun }} 请坐和放宽。
16+
17+
可以按顺序阅读,也可以在本页面上方导航栏的“章节列表”中,选择感兴趣的章节阅读。
818

919
本书完全开源和免费,GitHub 仓库:https://github.com/parallel101/cppguidebook
1020

1121
> 如果你是在付费群中“买”到本书,或者打着小彭老师名号卖课,说明你可能是私有制的受害者。因为小彭老师从来没有付费才能看的课程,所有小彭老师课程都对全球互联网开放。
1222
1323
如果你在阅读过程中遇到任何问题,可以在 [GitHub Issues](https://github.com/parallel101/cppguidebook/issues) 中提出,小彭老师会尽力解答。
24+
25+
## 格式约定
26+
27+
> {{ icon.tip }} 用这种颜色字体书写的内容是温馨提示
28+
29+
> {{ icon.warn }} 用这种颜色字体书写的内容是可能犯错的警告
30+
31+
> {{ icon.fun }} 用这种颜色字体书写的内容是笑话或趣味寓言故事
32+
33+
> {{ icon.story }} 用这种颜色书写的是补充说明的课外阅读,看不懂也没关系
34+
35+
> {{ icon.detail }} 用这种颜色字体书写的是初学者可暂时不用理解的细节
36+
37+
* 术语名称: 这里是术语的定义。
38+
39+
## 观前须知
40+
41+
与大多数现有教材不同的是,本课程将会采用“倒叙”的形式,从最新的 **C++23** 讲起!然后讲 C++20、C++17、C++14、C++11,慢慢讲到最原始的 C++98。
42+
43+
不用担心,越是现代的 C++,学起来反而更容易!反而古代 C++ 才**又臭又长**
44+
45+
很多同学想当然地误以为 C++98 最简单,哼哧哼哧费老大劲从 C++98 开始学,才是错误的。
46+
47+
为了应付缺胳膊少腿的 C++98,人们发明了各种**繁琐无谓**的写法,在现代 C++ 中,早就已经被更**简洁直观**的写法替代了。
48+
49+
> {{ icon.story }} 例如所谓的 safe-bool idiom,写起来又臭又长,C++11 引入一个 `explicit` 关键字直接就秒了。结果还有一批劳保教材大吹特吹 safe-bool idiom,吹得好像是个什么高大上的设计模式一样,不过是个应付 C++98 语言缺陷的蹩脚玩意。
50+
51+
就好比一个**老外**想要学习汉语,他首先肯定是从**现代汉语**学起!而不是上来就教他**文言文**
52+
53+
> {{ icon.fun }} 即使这个老外的职业就是“考古”,或者他对“古代文学”感兴趣,也不可能自学文言文的同时完全跳过现代汉语。
54+
55+
当我们学习中文时,你肯定希望先学现代汉语,再学文言文,再学甲骨文,再学 brainf\**\**k,而不是反过来。
56+
57+
对于 C++ 初学者也是如此:我们首先学会简单明了的,符合现代人思维的 C++23,再逐渐回到专为伺候“古代开发环境”的 C++98。
58+
59+
你的生产环境可能不允许用上 C++20 甚至 C++23 的新标准。
60+
61+
别担心,小彭老师教会你 C++23 的正常写法后,会讲解如何在 C++14、C++98 中写出同样的效果。
62+
63+
这样你学习的时候思路清晰,不用被繁琐的 C++98 “奇技淫巧”干扰,学起来事半功倍;但也“吃过见过”,知道古代 C++98 的应对策略。
64+
65+
> {{ icon.tip }} 目前企业里主流使用的是 C++14 和 C++17。例如谷歌就明确规定要求 C++17。
66+
67+
## 举个例子
68+
69+
> {{ icon.story }} 接下来的例子你可能看不懂,但只需要记住这个例子是向你说明:越是新的 C++ 标准,反而越容易学!
70+
71+
例如,在模板元编程中,要检测一个类型 T 是否拥有 `foo()` 这一成员函数。如果存在,才会调用。
72+
73+
在 C++20 中,可以使用很方便的 `requires` 语法,轻松检测一个表达式是否能合法通过编译。如果能,`requires ` 语句会返回 `true`。然后用一个 `if constexpr` 进行编译期分支判断,即可实现检测到存在则调用。
74+
75+
```cpp
76+
template <class T>
77+
void try_call_foo(T &t) {
78+
if constexpr (requires { t.foo(); }) {
79+
t.foo();
80+
}
81+
}
82+
```
83+
84+
但仅仅是回到 C++17,没有 `requires` 语法,我们只能自己定义一个 trait 类,并运用烦人的 SFINAE 小技巧,检测表达式是否的合法,又臭又长。
85+
86+
```cpp
87+
template <class T, class = void>
88+
struct has_foo {
89+
inline constexpr bool value = false;
90+
};
91+
92+
template <class T>
93+
struct has_foo<T, std::void_t<decltype(std::declval<T>().foo())>> {
94+
inline constexpr bool value = true;
95+
};
96+
97+
template <class T>
98+
void try_call_foo(T &t) {
99+
if constexpr (has_foo<T>::value) {
100+
t.foo();
101+
}
102+
}
103+
```
104+
105+
如果回到 C++14,情况就更糟糕了!`if constexpr` 是 C++17 的特性,没有他,要实现编译期分支,我们就得用 `enable_if_t` 的 SFINAE 小技巧,需要定义两个 try_call_foo 函数,互相重载,才能实现同样的效果。
106+
107+
```cpp
108+
template <class T, class = void>
109+
struct has_foo {
110+
static constexpr bool value = false;
111+
};
112+
113+
template <class T>
114+
struct has_foo<T, std::void_t<decltype(std::declval<T>().foo())>> {
115+
static constexpr bool value = true;
116+
};
117+
118+
template <class T, std::enable_if_t<has_foo<T>::value, int> = 0>
119+
void try_call_foo(T &t) {
120+
t.foo();
121+
}
122+
123+
template <class T, std::enable_if_t<!has_foo<T>::value, int> = 0>
124+
void try_call_foo(T &) {
125+
}
126+
```
127+
128+
如果回到 C++11,情况进一步恶化!`enable_if_t` 这个方便的小助手已经不存在,需要使用比他更底层的 `enable_if` 模板类,手动取出 `::type`,并且需要 `typename` 修饰,才能编译通过!并且 `void_t` 也不能用了,要用逗号表达式小技巧才能让 decltype 固定返回 void……
129+
130+
```cpp
131+
template <class T, class = void>
132+
struct has_foo {
133+
static constexpr bool value = false;
134+
};
135+
136+
template <class T>
137+
struct has_foo<T, decltype(std::declval<T>().foo(), (void)0)> {
138+
static constexpr bool value = true;
139+
};
140+
141+
template <class T, typename std::enable_if<has_foo<T>::value, int>::type = 0>
142+
void try_call_foo(T &t) {
143+
t.foo();
144+
}
145+
146+
template <class T, typename std::enable_if<!has_foo<T>::value, int>::type = 0>
147+
void try_call_foo(T &) {
148+
}
149+
```
150+
151+
如果回到 C++98,那又要罪加一等!`enable_if` 和 是 C++11 引入的 `<type_traits>` 头文件的帮手类,在 C++98 中,我们需要自己实现 `enable_if`…… `declval` 也是 C++11 引入的 `<utility>` 头文件中的帮手函数……假设你自己好不容易实现出来了 `enable_if``declval`,还没完:因为 constexpr 在 C++98 中也不存在了!你无法定义 value 成员变量为编译期常量,我们只好又用一个抽象的枚举小技巧来实现定义类成员常量的效果。
152+
153+
```cpp
154+
template <class T, class = void>
155+
struct has_foo {
156+
enum { value = 0 };
157+
};
158+
159+
template <class T>
160+
struct has_foo<T, decltype(my_declval<T>().foo(), (void)0)> {
161+
enum { value = 1 };
162+
};
163+
164+
template <class T, typename my_enable_if<has_foo<T>::value, int>::type = 0>
165+
void try_call_foo(T &t) {
166+
t.foo();
167+
}
168+
169+
template <class T, typename my_enable_if<!has_foo<T>::value, int>::type = 0>
170+
void try_call_foo(T &) {
171+
}
172+
```
173+
174+
如此冗长难懂的抽象 C++98 代码,仿佛是“加密”过的代码一样,仅仅是为了实现检测是否存在成员函数 foo……
175+
176+
> {{ icon.fun }} 如果回到 C 语言,那么你甚至都不用检测了。因为伟大的 C 语言连成员函数都没有,何谈“检测成员函数是否存在”?
177+
178+
反观 C++20 的写法,一眼就看明白代码的逻辑是什么,表达你该表达的,而不是迷失于伺候各种语言缺陷,干扰我们学习。
179+
180+
```cpp
181+
void try_call_foo(auto &t) {
182+
if constexpr (requires { t.foo(); }) {
183+
t.foo();
184+
}
185+
}
186+
```
187+
188+
// 从残废的 C++98 学起,你的思维就被这些无谓的“奇技淫巧”扭曲了,而使得真正应该表达的代码逻辑,淹没在又臭又长的古代技巧中。
189+
// 从现代的 C++23 学起,先知道正常的写法“理应”是什么样。工作中用不上 C++23?我会向你介绍,如果要倒退回 C++14,古代人都是用什么“奇技淫巧”实现同样的效果。
190+
// 这样你最后同样可以适应公司要求的 C++14 环境。但是从 C++23 学起,你的思维又不会被应付古代语言缺陷的“奇技淫巧”扰乱,学起来就事半功倍。
191+
192+
> {{ icon.fun }} 既然现代 C++ 这么好,为什么学校不从现代 C++ 教起,教起来还轻松?因为劳保老师保,懒得接触新知识,认为“祖宗之法不可变”,“版号稳定压倒一切”。

docs/start.md

-181
Original file line numberDiff line numberDiff line change
@@ -1,182 +1 @@
1-
# 前言
21

3-
小彭大典是一本关于现代 C++ 编程的权威指南,它涵盖了从基础知识到高级技巧的内容,适合初学者和有经验的程序员阅读。本书由小彭老师亲自编写,通过简单易懂的语言和丰富的示例,帮助读者快速掌握 C++ 的核心概念,并学会如何运用它们来解决实际问题。
4-
5-
> {{ icon.fun }} 敢承诺:土木老哥也能看懂!
6-
7-
推荐用手机或平板**竖屏**观看,可以在床或沙发上躺着。
8-
9-
用电脑看的话,可以按 `WIN + ←`,把本书的浏览器窗口放在屏幕左侧,右侧是你的 IDE。一边看一边自己动手做实验。
10-
11-
![](img/slide.jpg)
12-
13-
> {{ icon.fun }} 请坐和放宽。
14-
15-
## 格式约定
16-
17-
> {{ icon.tip }} 用这种颜色字体书写的内容是温馨提示
18-
19-
> {{ icon.warn }} 用这种颜色字体书写的内容是可能犯错的警告
20-
21-
> {{ icon.fun }} 用这种颜色字体书写的内容是笑话或趣味寓言故事
22-
23-
> {{ icon.story }} 用这种颜色书写的是补充说明的课外阅读,看不懂也没关系
24-
25-
> {{ icon.detail }} 用这种颜色字体书写的是初学者可暂时不用理解的细节
26-
27-
* 术语名称: 这里是术语的定义。
28-
29-
## 观前须知
30-
31-
与大多数现有教材不同的是,本课程将会采用“倒叙”的形式,从最新的 **C++23** 讲起!然后讲 C++20、C++17、C++14、C++11,慢慢讲到最原始的 C++98。
32-
33-
不用担心,越是现代的 C++,学起来反而更容易!反而古代 C++ 才**又臭又长**
34-
35-
很多同学想当然地误以为 C++98 最简单,哼哧哼哧费老大劲从 C++98 开始学,才是错误的。
36-
37-
为了应付缺胳膊少腿的 C++98,人们发明了各种**繁琐无谓**的写法,在现代 C++ 中,早就已经被更**简洁直观**的写法替代了。
38-
39-
> {{ icon.story }} 例如所谓的 safe-bool idiom,写起来又臭又长,C++11 引入一个 `explicit` 关键字直接就秒了。结果还有一批劳保教材大吹特吹 safe-bool idiom,吹得好像是个什么高大上的设计模式一样,不过是个应付 C++98 语言缺陷的蹩脚玩意。
40-
41-
就好比一个**老外**想要学习汉语,他首先肯定是从**现代汉语**学起!而不是上来就教他**文言文**
42-
43-
> {{ icon.fun }} 即使这个老外的职业就是“考古”,或者他对“古代文学”感兴趣,也不可能自学文言文的同时完全跳过现代汉语。
44-
45-
当我们学习中文时,你肯定希望先学现代汉语,再学文言文,再学甲骨文,再学 brainf\**\**k,而不是反过来。
46-
47-
对于 C++ 初学者也是如此:我们首先学会简单明了的,符合现代人思维的 C++23,再逐渐回到专为伺候“古代开发环境”的 C++98。
48-
49-
你的生产环境可能不允许用上 C++20 甚至 C++23 的新标准。
50-
51-
别担心,小彭老师教会你 C++23 的正常写法后,会讲解如何在 C++14、C++98 中写出同样的效果。
52-
53-
这样你学习的时候思路清晰,不用被繁琐的 C++98 “奇技淫巧”干扰,学起来事半功倍;但也“吃过见过”,知道古代 C++98 的应对策略。
54-
55-
> {{ icon.tip }} 目前企业里主流使用的是 C++14 和 C++17。例如谷歌就明确规定要求 C++17。
56-
57-
## 举个例子
58-
59-
> {{ icon.story }} 接下来的例子你可能看不懂,但只需要记住这个例子是向你说明:越是新的 C++ 标准,反而越容易学!
60-
61-
例如,在模板元编程中,要检测一个类型 T 是否拥有 `foo()` 这一成员函数。如果存在,才会调用。
62-
63-
在 C++20 中,可以使用很方便的 `requires` 语法,轻松检测一个表达式是否能合法通过编译。如果能,`requires ` 语句会返回 `true`。然后用一个 `if constexpr` 进行编译期分支判断,即可实现检测到存在则调用。
64-
65-
```cpp
66-
template <class T>
67-
void try_call_foo(T &t) {
68-
if constexpr (requires { t.foo(); }) {
69-
t.foo();
70-
}
71-
}
72-
```
73-
74-
但仅仅是回到 C++17,没有 `requires` 语法,我们只能自己定义一个 trait 类,并运用烦人的 SFINAE 小技巧,检测表达式是否的合法,又臭又长。
75-
76-
```cpp
77-
template <class T, class = void>
78-
struct has_foo {
79-
inline constexpr bool value = false;
80-
};
81-
82-
template <class T>
83-
struct has_foo<T, std::void_t<decltype(std::declval<T>().foo())>> {
84-
inline constexpr bool value = true;
85-
};
86-
87-
template <class T>
88-
void try_call_foo(T &t) {
89-
if constexpr (has_foo<T>::value) {
90-
t.foo();
91-
}
92-
}
93-
```
94-
95-
如果回到 C++14,情况就更糟糕了!`if constexpr` 是 C++17 的特性,没有他,要实现编译期分支,我们就得用 `enable_if_t` 的 SFINAE 小技巧,需要定义两个 try_call_foo 函数,互相重载,才能实现同样的效果。
96-
97-
```cpp
98-
template <class T, class = void>
99-
struct has_foo {
100-
static constexpr bool value = false;
101-
};
102-
103-
template <class T>
104-
struct has_foo<T, std::void_t<decltype(std::declval<T>().foo())>> {
105-
static constexpr bool value = true;
106-
};
107-
108-
template <class T, std::enable_if_t<has_foo<T>::value, int> = 0>
109-
void try_call_foo(T &t) {
110-
t.foo();
111-
}
112-
113-
template <class T, std::enable_if_t<!has_foo<T>::value, int> = 0>
114-
void try_call_foo(T &) {
115-
}
116-
```
117-
118-
如果回到 C++11,情况进一步恶化!`enable_if_t` 这个方便的小助手已经不存在,需要使用比他更底层的 `enable_if` 模板类,手动取出 `::type`,并且需要 `typename` 修饰,才能编译通过!并且 `void_t` 也不能用了,要用逗号表达式小技巧才能让 decltype 固定返回 void……
119-
120-
```cpp
121-
template <class T, class = void>
122-
struct has_foo {
123-
static constexpr bool value = false;
124-
};
125-
126-
template <class T>
127-
struct has_foo<T, decltype(std::declval<T>().foo(), (void)0)> {
128-
static constexpr bool value = true;
129-
};
130-
131-
template <class T, typename std::enable_if<has_foo<T>::value, int>::type = 0>
132-
void try_call_foo(T &t) {
133-
t.foo();
134-
}
135-
136-
template <class T, typename std::enable_if<!has_foo<T>::value, int>::type = 0>
137-
void try_call_foo(T &) {
138-
}
139-
```
140-
141-
如果回到 C++98,那又要罪加一等!`enable_if` 和 是 C++11 引入的 `<type_traits>` 头文件的帮手类,在 C++98 中,我们需要自己实现 `enable_if`…… `declval` 也是 C++11 引入的 `<utility>` 头文件中的帮手函数……假设你自己好不容易实现出来了 `enable_if``declval`,还没完:因为 constexpr 在 C++98 中也不存在了!你无法定义 value 成员变量为编译期常量,我们只好又用一个抽象的枚举小技巧来实现定义类成员常量的效果。
142-
143-
```cpp
144-
template <class T, class = void>
145-
struct has_foo {
146-
enum { value = 0 };
147-
};
148-
149-
template <class T>
150-
struct has_foo<T, decltype(my_declval<T>().foo(), (void)0)> {
151-
enum { value = 1 };
152-
};
153-
154-
template <class T, typename my_enable_if<has_foo<T>::value, int>::type = 0>
155-
void try_call_foo(T &t) {
156-
t.foo();
157-
}
158-
159-
template <class T, typename my_enable_if<!has_foo<T>::value, int>::type = 0>
160-
void try_call_foo(T &) {
161-
}
162-
```
163-
164-
如此冗长难懂的抽象 C++98 代码,仿佛是“加密”过的代码一样,仅仅是为了实现检测是否存在成员函数 foo……
165-
166-
> {{ icon.fun }} 如果回到 C 语言,那么你甚至都不用检测了。因为伟大的 C 语言连成员函数都没有,何谈“检测成员函数是否存在”?
167-
168-
反观 C++20 的写法,一眼就看明白代码的逻辑是什么,表达你该表达的,而不是迷失于伺候各种语言缺陷,干扰我们学习。
169-
170-
```cpp
171-
void try_call_foo(auto &t) {
172-
if constexpr (requires { t.foo(); }) {
173-
t.foo();
174-
}
175-
}
176-
```
177-
178-
// 从残废的 C++98 学起,你的思维就被这些无谓的“奇技淫巧”扭曲了,而使得真正应该表达的代码逻辑,淹没在又臭又长的古代技巧中。
179-
// 从现代的 C++23 学起,先知道正常的写法“理应”是什么样。工作中用不上 C++23?我会向你介绍,如果要倒退回 C++14,古代人都是用什么“奇技淫巧”实现同样的效果。
180-
// 这样你最后同样可以适应公司要求的 C++14 环境。但是从 C++23 学起,你的思维又不会被应付古代语言缺陷的“奇技淫巧”扰乱,学起来就事半功倍。
181-
182-
> {{ icon.fun }} 既然现代 C++ 这么好,为什么学校不从现代 C++ 教起,教起来还轻松?因为劳保老师保,懒得接触新知识,认为“祖宗之法不可变”,“版号稳定压倒一切”。

mkdocs.yml

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ site_name: ✝️小彭大典✝️
22
nav:
33
- 章节列表:
44
- index.md
5-
- start.md
65
- helloworld.md
76
- platform.md
87
- vartypes.md

0 commit comments

Comments
 (0)