fix: fix typos and punctuation errors 3

pull/1409/head
Ellery Zhang 8 months ago
parent 9d4bb86262
commit 77fd350a73

@ -113,7 +113,7 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
#### 数组元素为非基础类型
学习了上面的知识很多朋友肯定觉得已经学会了Rust的数组类型但现实会给我们一记重锤实际开发中还会碰到一种情况就是**数组元素是非基本类型**的,这时候大家一定会这样写。
学习了上面的知识,很多朋友肯定觉得已经学会了 Rust 的数组类型,但现实会给我们一记重锤,实际开发中还会碰到一种情况,就是**数组元素是非基本类型**的,这时候大家一定会这样写。
```rust
let array = [String::from("rust is good!"); 8];
@ -133,7 +133,7 @@ error[E0277]: the trait bound `String: std::marker::Copy` is not satisfied
= note: the `Copy` trait is required because this value will be copied for each element of the array
```
有些还没有看过特征的小伙伴,有可能不太明白这个报错,不过这个目前可以不提,我们就拿之前所学的[所有权](https://course.rs/basic/ownership/ownership.html)知识就可以思考明白前面几个例子都是Rust的基本类型而**基本类型在Rust中赋值是以Copy的形式**,这时候你就懂了吧,`let array=[3;5]`底层就是不断的Copy出来的但很可惜复杂类型都没有深拷贝只能一个个创建。
有些还没有看过特征的小伙伴,有可能不太明白这个报错,不过这个目前可以不提,我们就拿之前所学的[所有权](https://course.rs/basic/ownership/ownership.html)知识,就可以思考明白,前面几个例子都是 Rust 的基本类型,而**基本类型在 Rust 中赋值是以 Copy 的形式**,这时候你就懂了吧,`let array=[3;5]`底层就是不断的Copy出来的但很可惜复杂类型都没有深拷贝只能一个个创建。
接着就有小伙伴会这样写。
@ -169,7 +169,7 @@ assert_eq!(slice, &[2, 3]);
- 切片的长度可以与数组不同,并不是固定的,而是取决于你使用时指定的起始和结束位置
- 创建切片的代价非常小,因为切片只是针对底层数组的一个引用
- 切片类型[T]拥有不固定的大小,而切片引用类型&[T]则具有固定的大小,因为 Rust 很多时候都需要固定大小数据类型,因此&[T]更有用,`&str`字符串切片也同理
- 切片类型 [T] 拥有不固定的大小,而切片引用类型 &[T] 则具有固定的大小,因为 Rust 很多时候都需要固定大小数据类型,因此 &[T] 更有用,`&str` 字符串切片也同理
## 总结
@ -208,7 +208,7 @@ fn main() {
做个总结,数组虽然很简单,但是其实还是存在几个要注意的点:
- **数组类型容易跟数组切片混淆**[T;n]描述了一个数组的类型,而[T]描述了切片的类型, 因为切片是运行期的数据结构,它的长度无法在编译期得知,因此不能用[T;n]的形式去描述
- **数组类型容易跟数组切片混淆**[T;n] 描述了一个数组的类型,而 [T] 描述了切片的类型, 因为切片是运行期的数据结构,它的长度无法在编译期得知,因此不能用 [T;n] 的形式去描述
- `[u8; 3]`和`[u8; 4]`是不同的类型,数组的长度也是类型的一部分
- **在实际开发中,使用最多的是数组切片[T]**,我们往往通过引用的方式去使用`&[T]`,因为后者有固定的类型大小

@ -18,7 +18,7 @@ enum PokerSuit {
任何一张扑克,它的花色肯定会落在四种花色中,而且也只会落在其中一个花色上,这种特性非常适合枚举的使用,因为**枚举值**只可能是其中某一个成员。抽象来看,四种花色尽管是不同的花色,但是它们都是扑克花色这个概念,因此当某个函数处理扑克花色时,可以把它们当作相同的类型进行传参。
细心的读者应该注意到,我们对之前的 `枚举类型``枚举值` 进行了重点标注,这是因为对于新人来说容易混淆相应的概念,总而言之:
**枚举类型是一个类型,它会包含所有可能的枚举成员, 而枚举值是该类型中的具体某个成员的实例。**
**枚举类型是一个类型,它会包含所有可能的枚举成员而枚举值是该类型中的具体某个成员的实例。**
## 枚举值
@ -134,7 +134,7 @@ enum IpAddr {
这个例子跟我们之前的扑克牌很像,只不过枚举成员包含的类型更复杂了,变成了结构体:分别通过 `Ipv4Addr``Ipv6Addr` 来定义两种不同的 IP 数据。
从这些例子可以看出,**任何类型的数据都可以放入枚举成员中**: 例如字符串、数值、结构体甚至另一个枚举。
从这些例子可以看出,**任何类型的数据都可以放入枚举成员中**例如字符串、数值、结构体甚至另一个枚举。
增加一些挑战?先看以下代码:
@ -207,7 +207,7 @@ enum Websocket {
## Option 枚举用于处理空值
在其它编程语言中,往往都有一个 `null` 关键字,该关键字用于表明一个变量当前的值为空(不是零值,例如整型的零值是 0也就是不存在值。当你对这些 `null` 进行操作时,例如调用一个方法,就会直接抛出**null 异常**,导致程序的崩溃,因此我们在编程时需要格外的小心去处理这些 `null` 空值。
在其它编程语言中,往往都有一个 `null` 关键字,该关键字用于表明一个变量当前的值为空(不是零值,例如整型的零值是 0也就是不存在值。当你对这些 `null` 进行操作时,例如调用一个方法,就会直接抛出 **null 异常**,导致程序的崩溃,因此我们在编程时需要格外的小心去处理这些 `null` 空值。
> Tony Hoare `null` 的发明者,曾经说过一段非常有名的话:
>

@ -30,7 +30,7 @@ fn main() {
}
```
接下来我们的学习非常类似原型设计:有的方法只提供 API 接口但是不提供具体实现。此外有的变量在声明之后并未使用因此在这个阶段我们需要排除一些编译器噪音Rust 在编译的时候会扫描代码,变量声明后未使用会以 `warning` 警告的形式进行提示),引入 `#![allow(unused_variables)]` 属性标记,该标记会告诉编译器忽略未使用的变量,不要抛出 `warning` 警告,具体的常见编译器属性你可以在这里查阅:[编译器属性标记](https://course.rs/profiling/compiler/attributes.html)。
接下来我们的学习非常类似原型设计:有的方法只提供 API 接口但是不提供具体实现。此外有的变量在声明之后并未使用因此在这个阶段我们需要排除一些编译器噪音Rust 在编译的时候会扫描代码,变量声明后未使用会以 `warning` 警告的形式进行提示),引入 `#![allow(unused_variables)]` 属性标记,该标记会告诉编译器忽略未使用的变量,不要抛出 `warning` 警告具体的常见编译器属性你可以在这里查阅:[编译器属性标记](https://course.rs/profiling/compiler/attributes.html)。
`read` 函数也非常有趣,它返回一个 `!` 类型,这个表明该函数是一个发散函数,不会返回任何值,包括 `()`。`unimplemented!()` 告诉编译器该函数尚未实现,`unimplemented!()` 标记通常意味着我们期望快速完成主要代码,回头再通过搜索这些标记来完成次要代码,类似的标记还有 `todo!()`,当代码执行到这种未实现的地方时,程序会直接报错。你可以反注释 `read(&mut f1, &mut vec![]);` 这行,然后再观察下结果。

@ -95,9 +95,9 @@ let slice = &s[..];
> ```
>
> 因为我们只取 `s` 字符串的前两个字节,但是本例中每个汉字占用三个字节,因此没有落在边界处,也就是连 `中` 字都取不完整,此时程序会直接崩溃退出,如果改成 `&s[0..3]`,则可以正常通过编译。
> 因此,当你需要对字符串做切片索引操作时,需要格外小心这一点, 关于该如何操作 UTF-8 字符串,参见[这里](#操作-utf-8-字符串)。
> 因此,当你需要对字符串做切片索引操作时,需要格外小心这一点关于该如何操作 UTF-8 字符串,参见[这里](#操作-utf-8-字符串)。
字符串切片的类型标识是 `&str`,因此我们可以这样声明一个函数,输入 `String` 类型,返回它的切片: `fn first_word(s: &String) -> &str `
字符串切片的类型标识是 `&str`,因此我们可以这样声明一个函数,输入 `String` 类型,返回它的切片`fn first_word(s: &String) -> &str `
有了切片就可以写出这样的代码:
@ -152,7 +152,7 @@ assert_eq!(slice, &[2, 3]);
## 字符串字面量是切片
之前提到过字符串字面量,但是没有提到它的类型:
之前提到过字符串字面量但是没有提到它的类型:
```rust
let s = "Hello, world!";
@ -705,9 +705,9 @@ for b in "中国人".bytes() {
// s 不再有效,内存被释放
```
与其它系统编程语言的 `free` 函数相同Rust 也提供了一个释放内存的函数: `drop`,但是不同的是,其它语言要手动调用 `free` 来释放每一个变量占用的内存,而 Rust 则在变量离开作用域时,自动调用 `drop` 函数: 上面代码中Rust 在结尾的 `}` 处自动调用 `drop`
与其它系统编程语言的 `free` 函数相同Rust 也提供了一个释放内存的函数: `drop`,但是不同的是,其它语言要手动调用 `free` 来释放每一个变量占用的内存,而 Rust 则在变量离开作用域时,自动调用 `drop` 函数上面代码中Rust 在结尾的 `}` 处自动调用 `drop`
> 其实,在 C++ 中,也有这种概念: _Resource Acquisition Is Initialization (RAII)_。如果你使用过 RAII 模式的话应该对 Rust 的 `drop` 函数并不陌生。
> 其实,在 C++ 中,也有这种概念_Resource Acquisition Is Initialization (RAII)_。如果你使用过 RAII 模式的话应该对 Rust 的 `drop` 函数并不陌生。
这个模式对编写 Rust 代码的方式有着深远的影响,在后面章节我们会进行更深入的介绍。

@ -125,7 +125,7 @@ fn build_user(email: String, username: String) -> User {
> 聪明的读者肯定要发问了:明明有三个字段进行了自动赋值,为何只有 `username` 发生了所有权转移?
>
> 仔细回想一下[所有权](https://course.rs/basic/ownership/ownership.html#拷贝浅拷贝)那一节的内容,我们提到了 `Copy` 特征:实现了 `Copy` 特征的类型无需所有权转移,可以直接在赋值时进行
> 数据拷贝,其中 `bool``u64` 类型就实现了 `Copy` 特征,因此 `active``sign_in_count` 字段在赋值给 `user2` 时,仅仅发生了拷贝,而不是所有权转移。
>数据拷贝,其中 `bool``u64` 类型就实现了 `Copy` 特征,因此 `active``sign_in_count` 字段在赋值给 `user2` 时,仅仅发生了拷贝,而不是所有权转移。
>
> 值得注意的是:`username` 所有权被转移给了 `user2`,导致了 `user1` 无法再被使用,但是并不代表 `user1` 内部的其它字段不能被继续使用,例如:
@ -184,7 +184,7 @@ println!("{:?}", user1);
上面定义的 `File` 结构体在内存中的排列如下图所示:
<img alt="" src="https://pic3.zhimg.com/80/v2-8cc4ed8cd06d60f974d06ca2199b8df5_1440w.png" class="center" />
从图中可以清晰地看出 `File` 结构体两个字段 `name``data` 分别拥有底层两个 `[u8]` 数组的所有权(`String` 类型的底层也是 `[u8]` 数组),通过 `ptr` 指针指向底层数组的内存地址,这里你可以把 `ptr` 指针理解为 Rust 中的引用类型。
从图中可以清晰地看出 `File` 结构体两个字段 `name``data` 分别拥有底层两个 `[u8]` 数组的所有权`String` 类型的底层也是 `[u8]` 数组),通过 `ptr` 指针指向底层数组的内存地址,这里你可以把 `ptr` 指针理解为 Rust 中的引用类型。
该图片也侧面印证了:**把结构体中具有所有权的字段转移出去后,将无法再访问该字段,但是可以正常访问其它的字段**。
@ -206,7 +206,7 @@ println!("{:?}", user1);
还记得之前讲过的基本没啥用的[单元类型](https://course.rs/basic/base-type/char-bool.html#单元类型)吧?单元结构体就跟它很像,没有任何字段和属性,但是好在,它还挺有用。
如果你定义一个类型,但是不关心该类型的内容, 只关心它的行为时,就可以使用 `单元结构体`
如果你定义一个类型,但是不关心该类型的内容只关心它的行为时,就可以使用 `单元结构体`
```rust
struct AlwaysEqual;

@ -8,7 +8,7 @@
> if else 无处不在 -- 鲁迅
但凡你能找到一门编程语言没有 `if else`,那么一定更要反馈给鲁迅,反正不是我说的:) 总之,只要你拥有其它语言的编程经验,就一定会有以下认知:`if else` **表达式**根据条件执行不同的代码分支:
但凡你能找到一门编程语言没有 `if else`,那么一定更要反馈给鲁迅,反正不是我说的 :) 总之,只要你拥有其它语言的编程经验,就一定会有以下认知:`if else` **表达式**根据条件执行不同的代码分支:
```rust
if condition == true {
@ -38,7 +38,7 @@ fn main() {
以上代码有以下几点要注意:
- **`if` 语句块是表达式**,这里我们使用 `if` 表达式的返回值来给 `number` 进行赋值:`number` 的值是 `5`
- 用 `if` 来赋值时,要保证每个分支返回的类型一样(事实上,这种说法不完全准确,见[这里](https://course.rs/appendix/expressions.html#if表达式)),此处返回的 `5``6` 就是同一个类型,如果返回类型不一致就会报错
- 用 `if` 来赋值时,要保证每个分支返回的类型一样事实上,这种说法不完全准确,见[这里](https://course.rs/appendix/expressions.html#if表达式),此处返回的 `5``6` 就是同一个类型,如果返回类型不一致就会报错
```console
error[E0308]: if and else have incompatible types
@ -118,7 +118,7 @@ for item in &container {
}
```
> 对于实现了 `copy` 特征的数组(例如 [i32; 10] )而言, `for item in arr` 并不会把 `arr` 的所有权转移,而是直接对其进行了拷贝,因此循环之后仍然可以使用 `arr`
> 对于实现了 `copy` 特征的数组(例如 [i32; 10]而言, `for item in arr` 并不会把 `arr` 的所有权转移,而是直接对其进行了拷贝,因此循环之后仍然可以使用 `arr`
如果想在循环中,**修改该元素**,可以使用 `mut` 关键字:

@ -144,7 +144,7 @@ fn value_in_cents(coin: Coin) -> u8 {
上面代码中,在匹配 `Coin::Quarter(state)` 模式时,我们把它内部存储的值绑定到了 `state` 变量上,因此 `state` 变量就是对应的 `UsState` 枚举类型。
例如有一个印了阿拉斯加州标记的 25 分硬币:`Coin::Quarter(UsState::Alaska)`, 它在匹配时,`state` 变量将被绑定 `UsState::Alaska` 的枚举值。
例如有一个印了阿拉斯加州标记的 25 分硬币:`Coin::Quarter(UsState::Alaska)`它在匹配时,`state` 变量将被绑定 `UsState::Alaska` 的枚举值。
再来看一个更复杂的例子:
@ -236,7 +236,7 @@ error[E0004]: non-exhaustive patterns: `West` not covered // 非穷尽匹配,`
= note: the matched value is of type `Direction`
```
不禁想感叹Rust 的编译器**真强大**忍不住想爆粗口了sorry如果你以后进一步深入使用 Rust 也会像我这样感叹的。Rust 编译器清晰地知道 `match` 中有哪些分支没有被覆盖, 这种行为能强制我们处理所有的可能性,有效避免传说中价值**十亿美金**的 `null` 陷阱。
不禁想感叹Rust 的编译器**真强大**忍不住想爆粗口了sorry如果你以后进一步深入使用 Rust 也会像我这样感叹的。Rust 编译器清晰地知道 `match` 中有哪些分支没有被覆盖这种行为能强制我们处理所有的可能性,有效避免传说中价值**十亿美金**的 `null` 陷阱。
#### `_` 通配符
@ -342,7 +342,7 @@ assert!(matches!(bar, Some(x) if x > 2));
## 变量遮蔽
无论是 `match` 还是 `if let`,这里都是一个新的代码块,而且这里的绑定相当于新变量,如果你使用同名变量,会发生变量遮蔽:
无论是 `match` 还是 `if let`,这里都是一个新的代码块,而且这里的绑定相当于新变量,如果你使用同名变量,会发生变量遮蔽
```rust
fn main() {

@ -32,7 +32,7 @@ let six = plus_one(five);
let none = plus_one(None);
```
`plus_one` 接受一个 `Option<i32>` 类型的参数,同时返回一个 `Option<i32>` 类型的值(这种形式的函数在标准库内随处所见),在该函数的内部处理中,如果传入的是一个 `None` ,则返回一个 `None` 且不做任何处理;如果传入的是一个 `Some(i32)`,则通过模式绑定,把其中的值绑定到变量 `i` 上,然后返回 `i+1` 的值,同时用 `Some` 进行包裹。
`plus_one` 接受一个 `Option<i32>` 类型的参数,同时返回一个 `Option<i32>` 类型的值(这种形式的函数在标准库内随处所见),在该函数的内部处理中,如果传入的是一个 `None` ,则返回一个 `None` 且不做任何处理;如果传入的是一个 `Some(i32)`,则通过模式绑定,把其中的值绑定到变量 `i` 上,然后返回 `i+1` 的值,同时用 `Some` 进行包裹。
为了进一步说明,假设 `plus_one` 函数接受的参数值 x 是 `Some(5)`,来看看具体的分支匹配情况:

@ -89,13 +89,13 @@ let PATTERN = EXPRESSION;
let x = 5;
```
这其中,`x` 也是一种模式绑定,代表将**匹配的值绑定到变量 x 上**。因此,在 Rust 中,**变量名也是一种模式**,只不过它比较朴素很不起眼罢了。
这其中,`x` 也是一种模式绑定,代表将**匹配的值绑定到变量 x 上**。因此,在 Rust 中**变量名也是一种模式**,只不过它比较朴素很不起眼罢了。
```rust
let (x, y, z) = (1, 2, 3);
```
上面将一个元组与模式进行匹配(**模式和值的类型必需相同!**),然后把 `1, 2, 3` 分别绑定到 `x, y, z` 上。
上面将一个元组与模式进行匹配**模式和值的类型必需相同!**,然后把 `1, 2, 3` 分别绑定到 `x, y, z` 上。
模式匹配要求两边的类型必须相同,否则就会导致下面的报错:

Loading…
Cancel
Save