Skip to content

Commit 996f172

Browse files
committed
Update: revise titles, fix shiki error code note
1 parent 98e6d47 commit 996f172

File tree

17 files changed

+566
-449
lines changed

17 files changed

+566
-449
lines changed

src/zh/01-hello-world/id-and-keyword.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ int main() {
3434

3535
在 C++ 中,有一些看起来是标识符、但是被保留它用的为关键字。关键字被保留为特殊用途,不能被程序员使用、命名其它实体。例如,`int` 是一个关键字,程序员不能将 `int` 作为对象、函数、类型的名字。
3636

37-
关键字和标识符有相同的组成规则(由字母、数字和下划线组成),但是关键字不是标识符
37+
关键字和标识符有相同的组成规则(由字母、数字和下划线组成),但是关键字**不是**标识符
3838

3939
下面列出了 C++ 中的所有关键字:
4040

@@ -64,7 +64,7 @@ int main() {
6464

6565
具体而言,以 `__` 开头的标识符都是保留的标识符。以 `_` 开头,第二个字符是大写字母的标识符也是保留的标识符。全局命名空间中,以一个下划线开始的标识符。都是被保留的。例如,任何位置出现的`_Reserved``_M_reserved``__m`,在全局命名空间的 `_x` 都是保留的标识符。
6666

67-
读者可能会在调试程序中看到标准库的源文件,会发现许多如本节所述的,被称为保留的标识符,名称。例如,下面是 `microsoft/STL` 中的一段代码:
67+
读者可能会在调试程序中看到标准库的源文件,会发现许多名字是如本节所述的保留的标识符。例如,下面是 `microsoft/STL` 中的一段代码:
6868

6969
```cpp
7070
template <class... _Valty>
@@ -83,7 +83,7 @@ int main() {
8383
8484
这里的 `_CONSTEXPR20`、`_Ty`、`_Emplace_one_at_back`、`_Valty`、`_My_data` 等等全都都是保留的标识符。
8585
86-
除了标准规定的一些名称比较好看的函数与类型,标准库本身实现的时候,基本上被限制使用上述的保留标识符。而作为标准库的使用者,**不应当**使用这些标识符,这是一种规范。
86+
除了标准规定的一些名字比较好看的函数与类型,标准库本身实现的时候,基本上被限制使用上述的保留标识符。而作为标准库的使用者,**不应当**使用这些标识符,这是一种规范。
8787
8888
## 快速练习
8989

src/zh/02-program-structure/cli-program.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: 2.7 简单的控制台程序
2+
title: 2.9 简单的控制台程序
33
---
44

55
控制台是在如今的计算机上最常见最基础的程序运行形式,它是一种基于文本的用户界面。

src/zh/02-program-structure/declaration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ int c; // c 的值是不确定的
7474
如果一个对象的值是不确定的,使用其值会导致错误,例如:
7575
```cpp
7676
int c;
77-
int d = c; // [!code error] // 错误:c 的值是不确定的
77+
int d = c; // 错误:c 的值是不确定的 // [!code error]
7878
```
7979

8080
::: info 不确定值、默认初始化与用户自定义类型

src/zh/02-program-structure/expression/assign-expr.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ title: 2.2.10 赋值表达式
1919
- `逻辑或表达式 |= 赋值表达式`
2020

2121
### 赋值表达式的计算
22-
赋值是 C++ 中一种操作的名称,对于 `int``bool``char` 这三种基本类型,赋值的操作是将一个对象的值改变为另一个值。例如:
22+
赋值是 C++ 中的一类操作,对于 `int``bool``char` 这三种基本类型,赋值的操作是将一个对象的值改变为另一个值。例如:
2323
```cpp
2424
a = 42
2525
```
@@ -109,7 +109,7 @@ true || (a = 42)
109109
虽然不能使用具有不确定值的对象的值,但是可以对其进行赋值。例如:
110110
```cpp
111111
int a; // a 的值是不确定的
112-
// int b = a; // [!code error] // 错误,不能使用 a 的值
112+
int b = a; // 错误,不能使用 a 的值 // [!code error]
113113
a = 42; // 可以对 a 进行赋值
114114
int b = a; // 现在 a 的值是确定的,可以使用 a 的值了
115115
```
@@ -118,7 +118,7 @@ int b = a; // 现在 a 的值是确定的,可以使用 a 的值了
118118

119119
赋值表达式的左边并不能是任意的表达式,例如
120120
```cpp
121-
a + b = 42 // [!code error] // 错误
121+
a + b = 42 // 错误 // [!code error]
122122
```
123123
这是因为 `a + b` 得到的是一个临时的值,并不指代对象,所以不能对其赋值。赋值表达式的左边必须是一个能够指代对象的表达式。
124124

@@ -134,15 +134,15 @@ a = b += 42; // 符合语法
134134
相对的后缀自增、后缀自减、取反运算符等表达式会导致表达式不再能赋值。注意,[正运算符](#正运算符)对于 `int` 类型虽然不会让值发生变化,但也是如此。例如:
135135
```cpp
136136
int a = 0;
137-
a++ = 42; // [!code error] // 错误
138-
a-- = 42; // [!code error] // 错误
139-
~a = 42; // [!code error] // 错误
140-
+a = 42; // [!code error] // 错误
137+
a++ = 42; // 错误 // [!code error]
138+
a-- = 42; // 错误 // [!code error]
139+
~a = 42; // 错误 // [!code error]
140+
+a = 42; // 错误 // [!code error]
141141
```
142142

143143
此外,前缀自增、前缀自减、后缀自增、后缀自减等表达式对参数做出修改,性质上也是赋值。这意味着其操作数如果不是可赋值的,则会产生错误。
144144
```cpp
145-
++(+a); // [!code error] // 错误
145+
++(+a); // 错误 // [!code error]
146146
```
147147

148148
::: info 前缀与后缀的差异

src/zh/02-program-structure/expression/cond-expr.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: 2.2.9 条件表达式
33
---
44

5-
C++ 中的条件表达式又叫做三目运算、三元表达式等名称,其形式是
5+
C++ 中的条件表达式又叫做三目运算、三元表达式等,其形式是
66
- `逻辑或表达式`
77
- `逻辑或表达式 ? 表达式 : 赋值表达式`
88

src/zh/02-program-structure/function.md

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,16 @@ int a2 = x2;
5050
```cpp
5151
return_type function_name ( parameter_list ) statement
5252
```
53-
其中,**return_type** 表示函数的返回类型,是一个表示类型的描述符;**function_name** 表示函数的名称,是一个描述符;**parameter_list** 是函数的参数列表;**statement** 表示函数的函数体,是一个复合语句。
53+
其中,**return_type** 表示函数的返回类型,是一个表示类型的描述符;**function_name** 表示函数的名字,是一个描述符;**parameter_list** 是函数的参数列表;**statement** 表示函数的函数体,是一个复合语句。
5454
5555
**返回值**是函数计算的结果,**返回类型**表示返回值的类型(返回值在后面[返回语句](#返回语句)中介绍)。
5656
5757
**函数名**是一个用来代指函数的标识符,函数名可以用来调用函数。注意,函数并不是一个对象,不能使用赋值表达式来给函数赋值。
5858
5959
**参数列表**可以为空,或者是一个用逗号分隔的参数声明列表,形式是:
6060
61-
- `参数声明`
62-
- `参数声明列表 , 参数声明`
61+
- `参数声明`
62+
- `参数声明列表 , 参数声明`
6363
6464
这里面,每个参数声明的形式是 `类型 标识符` 或者 `类型`(后者省略标识符)。
6565
@@ -94,7 +94,7 @@ int sqrt(int x) {
9494
function_name ( argument_list )
9595
```
9696
97-
这里,**function_name** 是函数的名称,**argument_list** 是参数列表。
97+
这里,**function_name** 是函数的名字,**argument_list** 是参数列表。
9898
9999
其中,参数列表是用逗号分隔的表达式序列,形式是:
100100
@@ -243,7 +243,7 @@ void add_a(int x) {
243243
}
244244

245245
add_a(2); // 正确:add_a 是无返回值函数
246-
int b = add_a(2); // [!code error] // 错误:add_a 是无返回值函数,没有返回值,这个初始化语句是错误的
246+
int b = add_a(2); // 错误:add_a 是无返回值函数,没有返回值,这个初始化语句是错误的 // [!code error]
247247
```
248248
::: info void 类型
249249
`void` 类型没有值,不能进行任何运算操作,也不能声明 `void` 类型的对象。此外,`void` 类型有其他的性质,我们会在后面的章节中介绍。
@@ -268,16 +268,30 @@ int foo(bool a) {
268268
return a;
269269
}
270270
271-
int r = foo(42); // r 的值是 1,用 42 初始化参数 a 被转换为 true,true 被转换为 1
271+
int r = foo(42); // r 的值是 1
272272
```
273273

274+
这里,`int r = foo(42);` 进行了这样的过程:
275+
276+
1. 使用 `42` 初始化 `foo` 函数的参数 `a`, 这会将 `a` 初始化为 `true`
277+
2. 执行 `foo` 函数
278+
1. 执行 `return a;` 语句,返回 `a` 的值,即 `true`
279+
2. 使用 `true` 初始化返回值,这会将 `true` 转换为 `1`
280+
3.`1` 赋值给 `r`
281+
282+
最终,`r` 的值是 `1`
283+
284+
::: info 实际的过程
285+
注意,实际生成的代码并不需要像人这样去理解这个过程。最终生成的代码可能简单的判断一下 `a` 是否为 `0`,然后直接返回 `1`。以非常高的效率等效完成这个过程。这是编译器的工作。
286+
:::
287+
274288
## 前向声明
275289

276290
类似于一个对象的名字必须要在声明之后才能使用,函数的名字也必须在定义之后才能调用。例如:
277291

278292
```cpp
279293
int sqrt(int x) {
280-
add_sqrt_counter(); // [!code error] // 错误:add_sqrt_counter 函数还没有定义
294+
add_sqrt_counter(); // 错误:add_sqrt_counter 函数还没有定义 // [!code error]
281295
int a = x;
282296
while (a * a > x) {
283297
a = (a + x / a) / 2;
@@ -299,7 +313,7 @@ void add_sqrt_counter() {
299313
```cpp
300314
return_type function_name ( expression_list ) ;
301315
```
302-
其中,**return_type** 是函数的返回类型,**function_name** 是函数的名称**expression_list** 是参数列表。
316+
其中,**return_type** 是函数的返回类型,**function_name** 是函数的名字**expression_list** 是参数列表。
303317

304318
读者可以将函数的前向声明的形式理解为把函数体改成一个分号 `;`
305319

@@ -329,11 +343,11 @@ void foo(int x, bool y); // 符合语法的前向声明
329343
330344
void foo(int x, bool); // 符合语法得前向声明
331345
332-
void foo(int, int); // [!code error] // 错误:参数列表类型不一致
346+
void foo(int, int); // 错误:参数列表类型不一致 // [!code error]
333347
334-
void foo(bool, int); // [!code error] // 错误:参数列表类型不一致(顺序也必须一致)
348+
void foo(bool, int); // 错误:参数列表类型不一致(顺序也必须一致) // [!code error]
335349
336-
int foo(int x, bool y); // [!code error] // 错误:返回值类型不一致
350+
int foo(int x, bool y); // 错误:返回值类型不一致 // [!code error]
337351
338352
void foo(int, bool y) { // 函数定义
339353
// ...
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
---
2+
title: 2.6 初识名字查找
3+
---
4+
5+
## 问题的引入
6+
7+
在前面[作用域](./scope.md)的介绍中,有这样的一段示例代码:
8+
9+
```cpp
10+
int sub_mul(int a, int b, int c) {
11+
int diff = a - b;
12+
return diff * c;
13+
}
14+
15+
a = 1; // 错误:a在这里都是不可见的 // [!code error]
16+
```
17+
18+
但是,如果外面有一个全局变量 `a`,那么这段代码就是正确的:
19+
20+
```cpp
21+
int a = 1; // 全局变量 a
22+
23+
int sub_mul(int a, int b, int c) {
24+
int diff = a - b;
25+
return diff * c;
26+
}
27+
28+
a = 1; // 正确:这里被访问的是全局变量 a
29+
```
30+
31+
实际上,对于所有嵌套的作用域,内部的名字都会覆盖外部作用域中的名字。例如:
32+
```cpp
33+
int a = 1; // a1
34+
{
35+
int a = 2; // a2
36+
{
37+
int a = 3; // a3
38+
a += 1; // 在这里,a 是内部的 a3
39+
}
40+
a += 1; // 在这里,a 是 a2
41+
}
42+
a += 1; // 在这里,a 是 a1
43+
```
44+
45+
这样内部可以覆盖外部的设计,让局部代码不需要关心和全局变量的冲突,显著减少了程序员的心智负担。但是,考虑如下的情况:
46+
47+
```cpp
48+
int a = 1;
49+
{
50+
int a = 2;
51+
{
52+
a += 1; // 这里的 a 是哪个呢?
53+
}
54+
}
55+
```
56+
57+
这里的 `a` 是哪个呢?
58+
59+
## 非限定名字查找
60+
61+
以目前讲述过的概念而言,读者可以用下面的规则来理解名字查找的过程:
62+
63+
1. 声明必须在使用之前。
64+
2. 从下往上查找,在当前作用域找不到的话,到外层作用域继续向上查找。
65+
3. 如果一个名字找到至少一个对应的声明,就停止查找。
66+
67+
例如,前面的问题中:
68+
69+
```cpp
70+
int a = 1; // a1
71+
{
72+
int a = 2; // a2
73+
{
74+
a += 1; // 这里的 a 是 a2
75+
}
76+
}
77+
```
78+
79+
名字查找只会向上查找,在如下的代码中:
80+
81+
```cpp
82+
int a = 1; // a1
83+
{
84+
{
85+
a += 1; // 这里的 a 是 a1
86+
int a = 2; // a2
87+
}
88+
int a = 3; // a3
89+
}
90+
```
91+
虽然后面的两个 `a` 在更内层的作用域中声明,但是在使用的时候,查找的过程是从下往上的,所以这里 `a += 1;` 中的 `a` 是外层的 `a`
92+
93+
:::
94+
95+
96+
::: info
97+
这里的标题是非限定名字查找,是因为还有一种查找称为**限定名字查找**,这种查找形式如下:
98+
```cpp
99+
namespace ns {
100+
int a = 1;
101+
}
102+
ns::a = 2; // 限定名字查找
103+
```
104+
这会在后面的章节中介绍。
105+
:::
106+
107+
::: info
108+
*找到至少一个对应的声明就停止查找* 仅用于变量的名字查找。函数如果找到了名字相同的声明,会继续查找,并将所有匹配的函数声明放在一起,组成一个**重载集**。重载集的概念会在后面的章节中介绍。
109+
:::
110+
111+
::: info
112+
在编程语言刚开始发展的时期,作用域的概念还不完善。那个时候甚至有任何名字都不能重复的要求。但随着程序的复杂度增加,这样的要求变得不切实际。要求某个组件的开发者熟悉整个项目的命名,小心翼翼地避免冲突带来的开发成本是巨大的。
113+
114+
读者可能在一些资料上看到了这样的要求,但是在现代的编程语言中,这样的要求已经不再存在。
115+
:::

0 commit comments

Comments
 (0)