Merge pull request #199 from Turing-Chu/master

update ch05
This commit is contained in:
KaiserY 2018-06-14 20:59:39 +08:00 committed by GitHub
commit ae5fb6f53f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 28 additions and 27 deletions

View File

@ -2,9 +2,9 @@
> [ch05-01-defining-structs.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch05-01-defining-structs.md)
> <br>
> commit e143d8fca3f914811b1388755ff4d325e9d20cc2
> commit c560db1e0145d5a64b9415c9cfe463c7dac31ab8
我们在第三章讨论过,结构体与元组类似。就像元组,结构体的每一部分可以是不同类型。不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字使得结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。
结构体和我们在第三章讨论过的元组类似。和元组一样,结构体的每一部分可以是不同类型。不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字使得结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。
定义结构体,需要使用 `struct` 关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义每一部分数据的名字,它们被称作 **字段***field*),并定义字段类型。例如,示例 5-1 展示了一个储存用户账号信息的结构体:
@ -19,7 +19,7 @@ struct User {
<span class="caption">示例 5-1`User` 结构体定义</span>
一旦定义了结构体后,为了使用它,通过为每个字段指定具体值来创建这个结构体的 **实例**。创建一个实例需要以结构体的名字开头,接着在大括号中使用 `key: value` 对的形式提供字段,其中 key 是字段的名字value 是需要储存在字段中的数据值。实例中具体说明字段的顺序不需要和它们在结构体中声明的顺序一致。换句话说,结构体的定义就像一个类型的通用模板,而实例则会在这个模板中放入特定数据来创建这个类型的值。例如,可以像示例 5-2 这样来声明一个特定的用户:
一旦定义了结构体后,为了使用它,通过为每个字段指定具体值来创建这个结构体的 **实例**。创建一个实例需要以结构体的名字开头,接着在大括号中使用 `key: value` 键-值对的形式提供字段,其中 key 是字段的名字value 是需要储存在字段中的数据值。实例中具体说明字段的顺序不需要和它们在结构体中声明的顺序一致。换句话说,结构体的定义就像一个类型的通用模板,而实例则会在这个模板中放入特定数据来创建这个类型的值。例如,可以像示例 5-2 这样来声明一个特定的用户:
```rust
# struct User {
@ -59,9 +59,9 @@ let mut user1 = User {
user1.email = String::from("anotheremail@example.com");
```
<span class="caption">示例 5-3改变 `User` 结构体 `email` 字段的值</span>
<span class="caption">示例 5-3改变 `User` 实例 `email` 字段的值</span>
注意整个实例必须是可变的Rust 并不允许只将特定字段标记为可变。另外需要注意同其他任何表达式一样,我们可以在函数体的最后一个表达式构造一个结构体,从函数隐式的返回一个结构体的新实例。
注意整个实例必须是可变的Rust 并不允许只将特定字段标记为可变。另外需要注意同其他任何表达式一样,我们可以在函数体的最后一个表达式中构造一个结构体的新实例,来隐式地返回这个实例。
示例 5-4 显示了一个返回带有给定的 `email``username``User` 结构体的实例的 `build_user` 函数。`active` 字段的值为 `true`,并且 `sign_in_count` 的值为 `1`
@ -91,7 +91,6 @@ fn build_user(email: String, username: String) -> User {
因为示例 5-4 中的参数名与字段名都完全相同,我们可以使用 **字段初始化简写语法***field init shorthand*)来重写 `build_user`,这样其行为与之前完全相同,不过无需重复 `email``username` 了,如示例 5-5 所示。
如果有变量与字段同名的话,你可以使用 **字段初始化简写语法***field init shorthand*)。这可以让创建新的结构体实例的函数更为简练。
```rust
# struct User {
@ -115,11 +114,11 @@ fn build_user(email: String, username: String) -> User {
这里我们创建了一个新的 `User` 结构体实例,它有一个叫做 `email` 的字段。我们想要将 `email` 字段的值设置为 `build_user` 函数 `email` 参数的值。因为 `email` 字段与 `email` 参数有着相同的名称,则只需编写 `email` 而不是 `email: email`
### 使用结构体更新语法从其他对象创建对象
### 使用结构体更新语法从其他实例创建实例
可以从老的对象创建新的对象常常是很有帮助的,即复用大部分老对象的值并只改变一部分值。这可以通过 **结构体更新语法***struct update syntax*)实现。
使用旧实例的大部分值但改变其部分值来创建一个新的结构体实例通常是很有帮助的。这可以通过 **结构体更新语法***struct update syntax*)实现。
作为开始,示例 5-6 展示了如何不使用更新语法来在 `user2` 中创建一个新 `User` 实例。我们为 `email``username` 设置了新的值,其他值则使用了实例 5-2 中创建的 `user1` 中的同名值:
首先,示例 5-6 展示了如何不使用更新语法来在 `user2` 中创建一个新 `User` 实例。我们为 `email``username` 设置了新的值,其他值则使用了实例 5-2 中创建的 `user1` 中的同名值:
```rust
# struct User {
@ -172,7 +171,7 @@ let user2 = User {
<span class="caption">示例 5-7使用结构体更新语法为一个 `User` 实例设置新的 `email``username` 值,不过其余值来自 `user1` 变量中实例的字段</span>
例 5-7 中的代码也在 `user2` 中创建了一个新实例,其有不同的 `email``username` 值不过 `active``sign_in_count` 字段的值与 `user1` 相同。
例 5-7 中的代码也在 `user2` 中创建了一个新实例,其有不同的 `email``username` 值不过 `active``sign_in_count` 字段的值与 `user1` 相同。
### 使用没有命名字段的元组结构体来创建不同的类型
@ -198,7 +197,7 @@ let origin = Point(0, 0, 0);
>
> 在示例 5-1 中的 `User` 结构体的定义中,我们使用了自身拥有所有权的 `String` 类型而不是 `&str` 字符串 slice 类型。这是一个有意而为之的选择,因为我们想要这个结构体拥有它所有的数据,为此只要整个结构体是有效的话其数据也是有效的。
>
> 可以使结构体储存被其他对象拥有的数据的引用,不过这么做的话需要用上 **生命周期***lifetimes*),这是一个第十章会讨论的 Rust 功能。生命周期确保结构体引用的数据有效性跟结构体本身保持一致。如果你尝试在结构体中储存一个引用而不指定生命周期,比如这样:
> 可以使结构体储存被其他对象拥有的数据的引用,不过这么做的话需要用上 **生命周期***lifetimes*),这是一个第十章会讨论的 Rust 功能。生命周期确保结构体引用的数据有效性跟结构体本身保持一致。如果你尝试在结构体中储存一个引用而不指定生命周期将是无效的,比如这样:
>
> <span class="filename">文件名: src/main.rs</span>
>

View File

@ -2,11 +2,11 @@
> [ch05-02-example-structs.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch05-02-example-structs.md)
> <br>
> commit 7bf137c1b8f176638c0a7fa136d2e6bdc1f6e7d3
> commit c560db1e0145d5a64b9415c9cfe463c7dac31ab8
为了理解何时会需要使用结构体,让我们编写一个计算长方形面积的程序。我们会从单独的变量开始,接着重构程序直到使用结构体替代他们为止。
使用 Cargo 来创建一个叫做 *rectangles* 的新二进制程序,它会获取一个长方形以像素为单位的宽度和高度并计算它的面积。示例 5-8 中是项目的 *src/main.rs* 文件为此实现的一个小程序:
使用 Cargo 来创建一个叫做 *rectangles* 的新二进制程序,它会获取一个长方形以像素为单位的宽度和高度并计算它的面积。示例 5-8 中是项目的 *src/main.rs* 文件为此实现的一个小程序:
<span class="filename">文件名: src/main.rs</span>
@ -34,8 +34,6 @@ fn area(width: u32, height: u32) -> u32 {
The area of the rectangle is 1500 square pixels.
```
### 使用元组重构
虽然示例 5-8 可以运行,并调用 `area` 函数用长方形的每个维度来计算出面积,不过我们可以做的更好。宽度和高度是相关联的,因为他们在一起才能定义一个长方形。
这些代码的问题突显在 `area` 的签名上:
@ -44,7 +42,11 @@ The area of the rectangle is 1500 square pixels.
fn area(width: u32, height: u32) -> u32 {
```
函数 `area` 本应该计算一个长方形的面积,不过函数却有两个参数。这两个参数是相关联的,不过程序本身却哪里也没有表现出这一点。将长度和宽度组合在一起将更易懂也更易处理。第三章的 “将值组合进元组” 部分已经讨论过了一种可行的方法:元组。示例 5-9 是另一个使用元组的版本:
函数 `area` 本应该计算一个长方形的面积,不过函数却有两个参数。这两个参数是相关联的,不过程序本身却哪里也没有表现出这一点。将长度和宽度组合在一起将更易懂也更易处理。第三章的 “元组类型” 部分已经讨论过了一种可行的方法:元组。
### 使用元组重构
示例 5-9 展示了使用元组的另一个程序版本。
<span class="filename">文件名: src/main.rs</span>
@ -63,11 +65,11 @@ fn area(dimensions: (u32, u32)) -> u32 {
}
```
<span class="caption">示例 5-8:使用元组来指定长方形的宽高</span>
<span class="caption">示例 5-9:使用元组来指定长方形的宽高</span>
在某种程度上说这个程序更好一点了。元组帮助我们增加了一些结构性,现在在调用 `area` 的时候只需传递一个参数。不过在另一方面这个方法却更不明确了:元组并没有给出它元素的名称,所以计算变得更费解了,因为不得不使用索引来获取元组的每一部分:
在某种程度上说这个程序更好一点了。元组帮助我们增加了一些结构性,并且现在只需传一个参数。不过在另一方面这个版本却有一点不明确了:元组并没有给出元素的名称,所以计算变得更费解了,因为不得不使用索引来获取元组的每一部分:
在面积计算时混淆宽高并没有什么问题,不过当在屏幕上绘制长方形时就有问题了!我们将不得不记住元组索引 `0``width``1``height`。如果其他人要使用这些代码,他们也不得不搞清楚并记住他们。容易忘记或者混淆这些值而造成错误,因为我们没有表明代码中数据的意义。
在面积计算时混淆宽高并没有什么问题,不过当在屏幕上绘制长方形时就有问题了!我们将不得不记住元组索引 `0``width``1``height`。如果其他人要使用这些代码,他们也不得不搞清楚并记住他们。容易忘记或者混淆这些值而造成错误,因为我们没有表明代码中数据的意义。
### 使用结构体重构:赋予更多意义
@ -99,13 +101,13 @@ fn area(rectangle: &Rectangle) -> u32 {
这里我们定义了一个结构体并称其为 `Rectangle`。在 `{}` 中定义了字段 `width``height`,都是 `u32` 类型的。接着在 `main` 中,我们创建了一个宽度为 30 和高度为 50 的 `Rectangle` 的具体实例。
函数 `area` 现在被定义为接收一个名叫 `rectangle` 的参数,其类型是一个结构体 `Rectangle` 实例的不可变借用。第四章讲到过,我们希望借用结构体而不是获取它的所有权这样 `main` 函数就可以保持 `rect1` 的所有权并继续使用它,所以这就是为什么在函数签名和调用的地方会有 `&`
函数 `area` 现在被定义为接收一个名叫 `rectangle` 的参数,其类型是一个结构体 `Rectangle` 实例的不可变借用。第四章讲到过,我们希望借用结构体而不是获取它的所有权这样 `main` 函数就可以保持 `rect1` 的所有权并继续使用它,所以这就是为什么在函数签名和调用的地方会有 `&`
`area` 函数访问 `Rectangle``width``height` 字段。`area` 的签名现在明确的表明了我们的意图:通过其 `width``height` 字段,计算一个 `Rectangle` 的面积。这表明了宽高是相互联系的,并为这些值提供了描述性的名称而不是使用元组的索引值 `0``1` 。结构体胜在更清晰明了。
### 通过派生 trait 增加实用功能
如果能够在调试程序时打印出 `Rectangle` 实例来查看其所有字段的值就更好了。示例 5-11 像第二章、第三章和第四章那样尝试了 `println!` 宏:
如果能够在调试程序时打印出 `Rectangle` 实例来查看其所有字段的值就更好了。示例 5-11 像前面章节那样尝试使用 `println!` 宏。但这并不行。
<span class="filename">文件名: src/main.rs</span>
@ -141,7 +143,7 @@ error[E0277]: the trait bound `Rectangle: std::fmt::Display` is not satisfied
让我们来试试!现在 `println!` 宏调用看起来像 `println!("rect1 is {:?}", rect1);` 这样。在 `{}` 中加入 `:?` 指示符告诉 `println!` 我们想要使用叫做 `Debug` 的输出格式。`Debug` 是一个 trait它允许我们在调试代码时以一种对开发者有帮助的方式打印出结构体。
让我们试试运行这个变化。见鬼了!仍然能看到一个错误:
以这个改变运行程序。见鬼了!仍然能看到一个错误:
```text
error[E0277]: the trait bound `Rectangle: std::fmt::Debug` is not satisfied

View File

@ -2,7 +2,7 @@
> [ch05-03-method-syntax.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch05-03-method-syntax.md)
> <br>
> commit ec65990849230388e4ce4db5b7a0cb8a0f0d60e2
> commit c560db1e0145d5a64b9415c9cfe463c7dac31ab8
**方法** 与函数类似:它们使用 `fn` 关键字和名称声明,可以拥有参数和返回值,同时包含一段该方法在某处被调用时会执行的代码。不过方法与函数是不同的,因为它们在结构体的上下文中被定义(或者是枚举或 trait 对象的上下文,将分别在第六章和第十七章讲解),并且它们第一个参数总是 `self`,它代表调用该方法的结构体实例。
@ -47,11 +47,11 @@ fn main() {
> ### `->`运算符到哪去了?
>
> 像在 C++ 这样的语言中,有两个不同的运算符来调用方法:`.` 直接在对象上调用方法,而 `->` 在一个对象的指针上调用方法这时需要先解引用dereference指针。换句话说如果 `object` 是一个指针,那么 `object->something()` 就像 `(*object).something()` 一样。
> 像在 C/C++ 这样的语言中,有两个不同的运算符来调用方法:`.` 直接在对象上调用方法,而 `->` 在一个对象的指针上调用方法这时需要先解引用dereference指针。换句话说如果 `object` 是一个指针,那么 `object->something()` 就像 `(*object).something()` 一样。
>
> Rust 并没有一个与 `->` 等效的运算符相反Rust 有一个叫 **自动引用和解引用***automatic referencing and dereferencing*)的功能。方法调用是 Rust 中少数几个拥有这种行为的地方。
>
> 这是它如何工作的:当使用 `object.something()` 调用方法时Rust 会自动增`&`、`&mut` 或 `*` 以便使 `object` 符合方法的签名。也就是说,这些代码是等价的:
> 他是这样工作的:当使用 `object.something()` 调用方法时Rust 会自动添`&`、`&mut` 或 `*` 以便使 `object` 符合方法的签名。也就是说,这些代码是等价的:
>
> ```rust
> # #[derive(Debug,Copy,Clone)]
@ -78,7 +78,7 @@ fn main() {
### 带有更多参数的方法
让我们更多的实践一下方法,通过为 `Rectangle` 结构体实现第二个方法。这回,我们让一个 `Rectangle` 的实例获取另一个 `Rectangle` 实例并返回 `self` 能否完全包含第二个长方形,如果能则返回 `true` ,如果不能则返回 `false`。一旦定义了 `can_hold` 方法,就可以运行示例 5-14 中的代码了:
让我们练习通过实现 `Rectangle` 结构体上的另一方法来使用方法。这回,我们让一个 `Rectangle` 的实例获取另一个 `Rectangle` 实例并返回 `true` 如果 `self` 能完全包含第二个长方形,否则返回 `false`。一旦定义了 `can_hold` 方法,就可以运行示例 5-14 中的代码了:
<span class="filename">文件名: src/main.rs</span>
@ -95,7 +95,7 @@ fn main() {
<span class="caption">示例 5-14展示还未实现的 `can_hold` 方法的应用</span>
同时我们希望看到如下输出,因为 `rect2`宽高都小于 `rect1`,而 `rect3``rect1` 要宽:
同时我们希望看到如下输出,因为 `rect2`两个维度都小于 `rect1`,而 `rect3``rect1` 要宽:
```text
Can rect1 hold rect2? true