Refining Ch04.

This commit is contained in:
Peng Hailin, 2023-12-17 20:13:54 +08:00
parent a36eb064b2
commit 9a35a7310f

View File

@ -79,13 +79,12 @@ fn main() {
}
```
*清单 6-1使用结构体 `struct` 存储 IP 地址的数据及 `IpAddrKind` 变种*
*清单 6-1使用 `struct` 存储 IP 地址的数据及 `IpAddrKind` 变种*
这里,我们定义了个有着两个字段的结构体 `IpAddr`:一个是 `IpAddrKind` 类型的 `kind` 字段(我们之前定义的枚举),另一个是 `String` 类型的 `address` 字段。咱们有着此结构体的两个实例。第一个实例是 `home`,其 `kind` 字段的值 `IpAddrKind::V4`,与其关联的地址数据为 `127.0.0.1`。第二个实例是 `loopback`。其 `kind` 字段值为 `IpAddrKind` 的另一变种 `V6`,关联的地址是 `::1`。我们已使用了一个结构体,将 `kind``address` 两个值捆绑在一起,因此现在这个变种,与值相关联了。
不过,只使用一个枚举,来表示这同样的概念,更为简洁:我们可以将数据直接放入各个枚举变种中,而不是将枚举放在结构体中。下面这个 `IpAddr` 枚举的新定义指出,`V4` 和 `V6` 两个变种,都将有着关联的 `String` 值:
这里已定义了有着两个字段的结构体 `IpAddr`:一个类型为 `IpAddrKind` (即先前定义的那个枚举)的 `kind` 字段,以及一个类型为 `String``address` 字段。这里有该结构体的两个实例。第一个是 `home`,而他有着与地址数据 `127.0.0.1` 关联的 `IpAddrKind::V4` 作为其 `kind` 的值。第二个实例为 `loopback`。这个实例则有不同的 `IpAddrKind` 变种作为其 `kind` 的值,即 `V6`,与 `kind` 关联的是地址 `::1`。由于这里使用了结构体将 `kind``address` 值捆绑在一起,因此现在这个 `IpAddrKind` 的变种就与那个 `String` 值关联起来了。
不过,仅使用一个枚举来表示这同一概念,就会更加简练:与其将枚举放在结构体内部,可将数据直接放在各个枚举变种里头。那么这新的 `IpAddr` 枚举定义,就是说 `V4``V6` 两个变种,将同时有着关联的 `String` 值:
```rust
enum IpAddr {
@ -93,17 +92,16 @@ enum IpAddr {
V6(String),
}
fn main() {
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
}
```
这里把数据直接附加到枚举的各个变种上,因此就无需额外的结构体了。这里还更易于发现枚举工作原理的另一细节:所定义的各个枚举变种的名字,还成为了构造该枚举实例的函数。那就是说,`IpAddr::V4()` 现在是个取 `String` 参数并返回该 `IpAddr` 类型实例的函数调用了。作为定义枚举的结果,这里让这个构造函数自动就定义好了。
这里还有另一个使用枚举而非结构体的好处:各个变种可以有不同类型及数量的关联数据。版本四类型的 IP 地址,将始终有着四个会有着 `0``255` 之间值的数字部分。在希望将 `V4` 地址存储为四个 `u8` 值,而仍然将 `V6` 地址表示为一个 `String` 值时,那就没法用结构体了,而枚举则能轻易处理这样的情况:
我们直接将数据,附加到枚举的各个变种,因此不需要一个额外结构体。在这里,我们还可以更容易地了解,枚举工作原理的另一细节:我们定义的每个枚举变种名字,还成为了用来构造枚举实例的一个函数。也就是说,`IpAddr::V4()` 是个取一个 `String` 参数,并返回 `IpAddr` 类型实例的函数调用。定义那个枚举时,我们就自动获得了这个构造函数。
使用枚举而非结构体,还有另一好处:每个变种都可以有不同类型和数量的关联数据。版本四的 IP 地址,总是有范围在 0 到 255 之间的四个数字部分。如果我们打算将 `V4` 地址,存储为四个 `u8` 值,但仍想要将 `V6` 地址,表示为一个 `String` 值,那么我们就无法使用结构体。枚举则可以轻松处理这种情况:
```rust
enum IpAddr {
@ -111,22 +109,21 @@ enum IpAddr {
V6(String),
}
fn main() {
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
}
```
到这里,就已经给出了好几种定义用于存储版本四和版本六 IP 地址的数据结构了。然而事实表明,想要存储 IP 地址,及对这些 IP 地址所属类别进行编码是如此普遍,以致 [标准库就有一个可加以使用的定义](https://doc.rust-lang.org/std/net/enum.IpAddr.html)!下面就来看看,标准库是怎样定义 `IpAddr` 的:他有着与这里曾定义和使用过的相同枚举和变种,不过标准库是将地址数据,以两个不同结构体的形式,嵌入到变种里的,对两个枚举变种,定义了不同的结构体。
我们已经展示了几种不同的,定义存储第四和第六版 IP 地址数据结构的方法。然而,事实证明,想要存储 IP 地址并对其进行编码,是如此普遍,以致 [标准库就有我们可以使用的定义](https://doc.rust-lang.org/std/net/enum.IpAddr.html)!我们来看看标准库是如何定义 `IpAddr` 的:他有着与我们定义和使用的完全同样的枚举和变种,但他将地址数据,以两个不同结构体的形式,嵌入到两个变种中,对于每个变种,表示地址数据的结构体定义都不同:
```rust
struct Ipv4Addr {
// --跳过--
}
struct Ipv4Addr {
struct Ipv6Addr {
// --跳过--
}
@ -136,11 +133,13 @@ enum IpAddr {
}
```
这段代码说明可将任何类别的数据放在枚举变种里面:比如字符串、数字类型,或结构体等等。甚至可以包含另一枚举!还说明了,标准库类型,通常也并不比咱们自己编写的代码复杂多少。
请注意,由于这里不曾将标准库的 `IpAddr` 定义带入到这里的作用域,因此即使标准库包含了一个 `IpAddr` 的定义,这里也仍然可以毫无冲突地创建与使用自己的 `IpAddr` 定义。在第 7 章就会讲到有关带入类型到作用域的问题。
这段代码说明,咱们可以在枚举变种中,放入任何类别的数据:比如字符串、数字类型或结构体等。咱们甚至还可以包含另一枚举!此外,一些标准库类型,通常也不会比咱们接下来会构造出的类型复杂多少。
请注意,即使标准库包含了 `IpAddr` 的定义,我们仍然可以创建并使用咱们自己的定义,而不会发生冲突,因为我们还没有将标准库的定义,引入我们的作用域。我们将在第 7 章中,详细讨论将类型引入作用域的问题。
我们再来看看,下面清单 6-2 中枚举的另一示例:这个枚举的变种中,嵌入了多种类型。
来看看下面清单 6-2 中另一个枚举的示例:这个枚举有着嵌入到其各个变种中的种类繁多的类型。
```rust
enum Message {
@ -153,13 +152,18 @@ enum Message {
*清单 6-2每个变种都存储了不同数量和类型值的 `Message` 枚举*
这个枚举有着四个带有不同类型数据的变种:
该枚举有着分别嵌入了不同类型的四个变种:
- `Quit` 变种完全没有与其关联的数据;
- `Move` 变种像结构体一样,有着两个命名的字段;
- `Write` 变种包含了单个 `String`
- `ChangeColor` 编程包含了三个 `i32` 的值。
定义一个有着一些如上面清单 6-2 中变种的枚举,与定义不同种类的结构体定义类似,不同在于枚举未使用关键字 `struct`,且所有变种在 `Message` 类型下组织在了一起。下面这些结构体,就可保存之前各个枚举变种所保存的那些同样数据:
```rust