Refining Ch05.

This commit is contained in:
rust-lang.xfoss.com 2023-12-14 16:08:55 +08:00
parent 79ad726f36
commit a763af3eb5
2 changed files with 43 additions and 29 deletions

View File

@ -197,13 +197,13 @@ fn main() {
```
要定义出 `AlwaysEqual`就要使用 `struct` 关键字、想要的名字,随后一个分号即可。是不需要花括号或圆括号的!随后就可以类似方式,得到一个在 `subject` 变量中的 `AlwaysEqual` 的示例了:使用定义的名字,不带任何花括弧或原括弧。设想稍后就要将此类型的表现,实现为每个 `AlwaysEqual` 的实例总是等于任何其他类型的每个实例这样做或许是为测试目的而要有这样的已知结果imagine that later we'll implement behavior for this type such that every instance of `AlwaysEqual` is always equal to every instance of any other type, perhaps to have a known result for testing purposes。对于这样的行为表现是不需要任何数据的在第 10 章就会看到怎样定义特质,以及在包括类单元结构体在内的任何类型上,怎样实现特质
要定义出 `AlwaysEqual`我们需要使用 `struct` 关键字、我们打算使用的名字,及随后的分号。无需使用花括号或圆括号!然后,我们就能以类似的方法,获取到 `subject` 变量的一个 `AlwaysEqual` 的实例:使用我们定义的名字,不需要任何花括号或圆括号。请设想,以后我们将为这种类型实现的行为:`AlwaysEqual` 的每个实例,总是等于任何其他类型的每个实例,或许是为测试目的,有个已知结果。我们不需要任何数据,来实现这种行为!咱们将在第 10 章中,看到如何定义特质,并在任意类型上实现他们,包括类单元值结构体
> **结构体数据的所有权**
>
> 在前面清单 5-1 中的 `User` 结构体定义里,使用的是带有所有权的 `String` 类型,而非 `&str` 字符串切片类型。由于那里是要该结构体的各个实例拥有他自己的数据,且是要在整个结构体有效期间,实例数据有效,因此那里使用 `String` 类型而非 `&str` 类型就是有意而为之的了
> 在清单 5-1 中的 `User` 结构体定义中,我们使用了自由的 `String` 类型,而不是 `&str` 的字符串切片类型。这是有意为之,因为我们希望该结构体的各个实例,都拥有其所有数据,同时只要整个结构有效,这些数据就有效
>
> 结构体存储到其他变量持有数据的引用,也是可能的,但这样做就需要用到 *生命周期lifetimes*,而生命周期则是会在后面的第 10 章会讨论到的一个 Rust 特性。生命周期确保某个结构体引用到的数据,会在该结构体有效期间保持有效。譬如说如同下面这样,在尝试在某个结构体中存储不带生命周期的引用时;这就不会工作
> 结构体存储指向其他数据的引用,也是可行的,但这样做要用到我们将在第 10 章讨论的 Rust *生命周期lifetimes* 特性。生命周期会确保结构体所引用的数据,在结构体存在期间一直有效。假设咱们试图在结构体中,存储某个引用而未指定生命周期,就像下面这样;这是行不通的
>
> 文件名:`src/main.rs`
@ -225,7 +225,7 @@ fn main() {
}
```
> 编译器会抱怨他需要生命周期说明符:
> 编译器会抱怨他需要生命周期说明符lifetime specifiers
```console
$ cargo run
@ -261,4 +261,4 @@ For more information about this error, try `rustc --explain E0106`.
error: could not compile `structs_demo` due to 2 previous errors
```
> 在第 10 章中,就会讨论怎样来修复这些错误,尔后就可以在结构体中存储引用变量了,而至于现在,则只会使用像是 `String` 这样的具有所有权的类型,而避开使用像是 `&str` 这样的引用,来解决这个问题
> 在第 10 章中,我们将讨论如何修复这些报错,以便咱们在结构体中存储引用,但现在,我们将使用自有类型(如 `String`)而不是引用(如 `&str`),来修复这类报错

View File

@ -1,8 +1,14 @@
# 一个使用结构体的示例程序
# 使用结构体的一个示例程序
为搞明白何时会想要使用结构体,下面就来编写一个计算矩形面积的程序。这里会先从使用单个变量开始,并在随后对这个程序进行重构,直到使用结构体为止。
**An Example Program Using Structs**
下面就来以 `Cargo` 构造一个名为 `rectangles` 的新二进制项目,该项目将取得以像素指定的矩形宽和高,并计算出该矩形的面积。下面的清单 5-8 给出了一个简短的程序,该程序正是有着在这个项目的 `src/main.rs` 中的做法:
为了搞明白什么情况下我们可能会打算使用结构体,我们来编写个计算矩形面积的程序。我们将首先使用单个变量,然后重构该程序,直到咱们使用结构体为止。
我们来使用 Cargo构造一个名为 `rectangles`,将以像素为单位,指定出矩形的宽和高,并计算该矩形面积的二进制项目。下面清单 5-8 给出了一个简短的程序,其在咱们项目的 `src/main.rs` 中,有着完成这一点的方法。
文件名:`src/main.rs`
```rust
fn main() {
@ -20,9 +26,10 @@ fn area(width: u32, height: u32) -> u32 {
}
```
*清单 5-8计算由单独宽和高变量指明的矩形面积*
*清单 5-8计算由单独的宽度和高度变量指定的矩形面积*
现在,使用 `cargo run` 允许这个程序:
现在,请使用 `cargo run` 运行这个程序:
```console
$ cargo run
@ -32,15 +39,18 @@ $ cargo run
该矩形的面积为 1500 平方像素。
```
这段代码通过以两个边长调用 `area` 函数,而成功计算出了该矩形的面积,不过还可以进一步让这段代码更为清晰已读。
这段代码的问题,体现在 `area` 函数签名中:
这段代码通过以各个维度调用那个 `area` 函数,成功计算出了该矩形的面积,但我们还可以做得更多,使这段代码更加清晰易读。
这段代码的问题,在 `area` 的签名中很明显:
```rust
fn area(width: u32, height: u32) -> u32 {
```
`area` 函数是要计算某个矩形面积的,但这里编写的该函数,有着两个参数,同时在这个程序中,并未清楚表明那两个参数是有联系的。将宽和高组织在一起,代码就会更具易读性,且更具可管理性。在第 3 章的 [元组类型](Ch03_Common_Programming_Concepts.md#元组类型) 小节,就已讨论过一种可能那样做的方式:使用元组。
`area` 函数本应计算一个矩形的面积,但我们编写的函数却有两个参数,而且咱们的程序中,也没有明确说明这两个参数是相关的。如果将宽度和高度组合在一起,会更易于阅读和管理。在第 3 章 [元组类型](../programming_concepts/data_types.md#元组类型) 小节,我们已经讨论过一种,我们可以实现这一点的方法:使用元组。
## 使用元组重构
@ -48,7 +58,8 @@ fn area(width: u32, height: u32) -> u32 {
**Refactoring with Tuples**
下面的清单 5-9 给出了使用了元组的另一版本的这个程序。
下面清单 5-9 给出了使用元组的咱们程序另一版本。
文件名:`src/main.rs`
@ -67,19 +78,21 @@ fn area(dimensions: (u32, u32)) -> u32 {
}
```
*清单 5-9以一个元组来对矩形的宽和高进行指定*
一方面,这个程序更好了。元组实现了一些代码结构的加入,且现在传递的只有了一个参数。但在另一方面,这个版本变得更不清楚了:元组不会给他的各个元素命名,因此就不得不索引到该元组的各部分,从而令到这里的计算不那么直观了。
将宽和高混合起来对于面积计算并不重要,但在要将这个矩形绘制在屏幕上时,那就会有影响了!那时就必须要记住元组中索引 `0` 的是 `width`,而 `height` 是索引 `1`。这对那些将要使用到这代码的其他人来说,将会更难。由于没有在代码中传达数据的意义,因此现在更易于引入错误。
*清单 5-9使用一个元组指定矩形的宽度和高度*
## 使用结构体重构:加入更多意义
从某种意义上说,这个程序更好。元组让我们增加了一点结构,同时我们现在只传递了一个参数。但从另一个角度看,这个版本就不那么清晰了:元组没有为其元素命名,因此我们必须索引到元组的各个部分,这使得我们的计算不那么直观。
混淆宽度和高度对于面积计算并不重要,但如果我们要在屏幕上绘制矩形,就会有影响!我们必须记住,`width` 是该元组的索引 `0`,而 `height` 是该元组的索引 `1`。如果其他人使用我们的代码,就更难搞清楚并牢记这一点了。因为我们没有在咱们的代码中,传达咱们数据的含义,所以现在更容易引入错误。
## 使用结构体重构:添加更多意义
**Refactoring with Structs: Adding More Meaning**
这里要使用结构体,通过给数据打上标签,来加入更多意义。可将这里正在使用的元组,以给整体命名,同时还给那些部分命名,而转换成为一个结构体。如下清单 5-10 所示。
我们要使用结构体,通过标记数据来增加意义。如下清单 5-10 所示,我们可以将正使用的元组,转换为一个整体和各部分都有名字的结构体。
文件名:`src/main.rs`
@ -106,16 +119,17 @@ fn area(rectangle: &Rectangle) -> u32 {
}
```
*清单 5-10定义一个 `Rectangle` 结构体*
这里就已定义了一个结构体,并将其命名为了 `Rectangle`。在那对花括弧内部,以 `width``height` 定义了两个字段,两个字段都具有 `u32` 类型。随后在 `main` 函数中,创建出了 `Rectangle` 的一个宽为 `30`,高为 `50` 的特定实例。
现在的 `area` 函数被定义为带有一个参数,该参数被命名为 `rectangle`,其类型是结构体 `Rectangle` 实例的不可变借用。如同在第 4 章中提到的那样,这里是要借用那个结构体,而非要取得那个结构体的所有权。在此方式下,`main` 函数仍保留着那个结构体实例的所有权,进而可继续使用变量 `rect1`,这就是在函数 `area` 签名与函数调用中,使用 `&` 符号的原因。
`area` 函数会访问那个 `Rectangle` 实例的 `width``height` 字段。`area` 的函数签名现在表达的正是这里想要的了:使用 `Rectangle``width``height` 字段,计算出他的面积。这就传达出了这里的宽与高是相互关联,同时这样做还给到了这些值描述性的名称,而非使用之前元组的索引 `0``1` 了。这在代码清晰上得了一分。
*清单 5-10定义出 `Rectangle` 结构体*
## 使用派生特质,加入有用功能
这里我们定义了一个结构体,并将其命名为 `Rectangle`。在花括号中,我们将字段定义为 `width``height`,两个字段的类型都是 `u32`。然后,在 `main` 中,我们创建了个有着宽度为 `30`,高度为 `50``Rectangle` 特定实例。
现在,我们的 `area` 函数定义了一个参数,我们将其命名为 `rectangle`,其类型是对 `Rectangle` 结构体实例的不可变借用。正如第 4 章所述,我们希望借用这个结构体,而不是要取得其所有权。这样,`main` 就可以保留其所有权,并继续使用 `rect1`,这也是我们在那个函数签名中,以及调用改函数时,使用 `&` 的原因。
`area` 函数会访问 `Rectangle` 实例的 `width``height` 字段(注意,访问某个借用的结构体实例的字段,不会迁移字段值,这就是为什么我们经常会看到结构体的借用)。现在,我们的 `area` 函数签名,就准确表达了我们的意思:使用 `Rectangle``width``height` 字段,计算其面积。这表达了宽度和高度是相互关联的,并且为这些值提供了描述性的名称,而不是使用 `0``1` 的元组索引值。这是清晰度方面的胜利。
## 使用派生特质添加有用功能
**Adding Useful Functionality with Derived Traits**