|
|
@ -4,9 +4,9 @@
|
|
|
|
> <br>
|
|
|
|
> <br>
|
|
|
|
> commit c560db1e0145d5a64b9415c9cfe463c7dac31ab8
|
|
|
|
> commit c560db1e0145d5a64b9415c9cfe463c7dac31ab8
|
|
|
|
|
|
|
|
|
|
|
|
结构体和我们在第三章讨论过的元组类似。和元组一样,结构体的每一部分可以是不同类型。但不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字使得结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。
|
|
|
|
结构体和我们在第三章讨论过的元组类似。和元组一样,结构体的每一部分可以是不同类型。但不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字,结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。
|
|
|
|
|
|
|
|
|
|
|
|
定义结构体,需要使用 `struct` 关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义每一部分数据的名字,它们被称作 **字段**(*field*),并定义字段类型。例如,示例 5-1 展示了一个储存用户账号信息的结构体:
|
|
|
|
定义结构体,需要使用 `struct` 关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义每一部分数据的名字和类型,我们称为 **字段**(*field*)。例如,示例 5-1 展示了一个存储用户账号信息的结构体:
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
struct User {
|
|
|
|
struct User {
|
|
|
@ -19,7 +19,7 @@ struct User {
|
|
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 5-1:`User` 结构体定义</span>
|
|
|
|
<span class="caption">示例 5-1:`User` 结构体定义</span>
|
|
|
|
|
|
|
|
|
|
|
|
一旦定义了结构体后,为了使用它,通过为每个字段指定具体值来创建这个结构体的 **实例**。创建一个实例需要以结构体的名字开头,接着在大括号中使用 `key: value` 键-值对的形式提供字段,其中 key 是字段的名字,value 是需要储存在字段中的数据值。实例中具体说明字段的顺序不需要和它们在结构体中声明的顺序一致。换句话说,结构体的定义就像一个类型的通用模板,而实例则会在这个模板中放入特定数据来创建这个类型的值。例如,可以像示例 5-2 这样来声明一个特定的用户:
|
|
|
|
一旦定义了结构体后,为了使用它,通过为每个字段指定具体值来创建这个结构体的 **实例**。创建一个实例需要以结构体的名字开头,接着在大括号中使用 `key: value` 键-值对的形式提供字段,其中 key 是字段的名字,value 是需要存储在字段中的数据值。实例中字段的顺序不需要和它们在结构体中声明的顺序一致。换句话说,结构体的定义就像一个类型的通用模板,而实例则会在这个模板中放入特定数据来创建这个类型的值。例如,可以像示例 5-2 这样来声明一个特定的用户:
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
# struct User {
|
|
|
|
# struct User {
|
|
|
@ -61,9 +61,9 @@ 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`。
|
|
|
|
示例 5-4 显示了一个 `build_user` 函数,它返回一个带有给定的 email 和用户名的 `User` 结构体实例。`active` 字段的值为 `true`,并且 `sign_in_count` 的值为 `1`。
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
# struct User {
|
|
|
|
# struct User {
|
|
|
@ -85,13 +85,12 @@ fn build_user(email: String, username: String) -> User {
|
|
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 5-4:`build_user` 函数获取 email 和用户名并返回 `User` 实例</span>
|
|
|
|
<span class="caption">示例 5-4:`build_user` 函数获取 email 和用户名并返回 `User` 实例</span>
|
|
|
|
|
|
|
|
|
|
|
|
为函数参数起与结构体字段相同的名字是可以理解的,但是不得不重复 `email` 和 `username` 字段名称与变量有些冗余。如果结构体有更多字段,重复这些名称就显得更加烦人了。幸运的是,有一个方便的简写语法!
|
|
|
|
为函数参数起与结构体字段相同的名字是可以理解的,但是不得不重复 `email` 和 `username` 字段名称与变量有些啰嗦。如果结构体有更多字段,重复每个名称就更加烦人了。幸运的是,有一个方便的简写语法!
|
|
|
|
|
|
|
|
|
|
|
|
### 变量与字段同名时的字段初始化简写语法
|
|
|
|
### 变量与字段同名时的字段初始化简写语法
|
|
|
|
|
|
|
|
|
|
|
|
因为示例 5-4 中的参数名与字段名都完全相同,我们可以使用 **字段初始化简写语法**(*field init shorthand*)来重写 `build_user`,这样其行为与之前完全相同,不过无需重复 `email` 和 `username` 了,如示例 5-5 所示。
|
|
|
|
因为示例 5-4 中的参数名与字段名都完全相同,我们可以使用 **字段初始化简写语法**(*field init shorthand*)来重写 `build_user`,这样其行为与之前完全相同,不过无需重复 `email` 和 `username` 了,如示例 5-5 所示。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
# struct User {
|
|
|
|
# struct User {
|
|
|
|
# username: String,
|
|
|
|
# username: String,
|
|
|
@ -118,7 +117,7 @@ fn build_user(email: String, username: String) -> User {
|
|
|
|
|
|
|
|
|
|
|
|
使用旧实例的大部分值但改变其部分值来创建一个新的结构体实例通常是很有帮助的。这可以通过 **结构体更新语法**(*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
|
|
|
|
```rust
|
|
|
|
# struct User {
|
|
|
|
# struct User {
|
|
|
@ -175,9 +174,9 @@ let user2 = User {
|
|
|
|
|
|
|
|
|
|
|
|
### 使用没有命名字段的元组结构体来创建不同的类型
|
|
|
|
### 使用没有命名字段的元组结构体来创建不同的类型
|
|
|
|
|
|
|
|
|
|
|
|
也可以定义与元组(在第三章讨论过)类似的结构体,称为 **元组结构体**(*tuple structs*),有着结构体名称提供的含义,但没有具体的字段名,只有字段的类型。元组结构体在你希望命名整个元组并使其与其他(同样的)元组为不同类型时很有用,这时像常规结构体那样为每个字段命名就显得冗余和形式化了。
|
|
|
|
也可以定义与元组(在第三章讨论过)类似的结构体,称为 **元组结构体**(*tuple structs*)。元组结构体有着结构体名称提供的含义,但没有具体的字段名,只有字段的类型。当你想给整个元组取一个名字,并使元组成为与其他元组不同的类型时,元组结构体是很有用的,这时像常规结构体那样为每个字段命名就显得多余和形式化了。
|
|
|
|
|
|
|
|
|
|
|
|
定义元组结构体以 `struct` 关键字和结构体名开头并后跟元组中的类型。例如,这里是两个分别叫做 `Color` 和 `Point` 元组结构体的定义和用例:
|
|
|
|
定义元组结构体,以 `struct` 关键字和结构体名开头并后跟元组中的类型。例如,下面是两个分别叫做 `Color` 和 `Point` 元组结构体的定义和用法:
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
struct Color(i32, i32, i32);
|
|
|
|
struct Color(i32, i32, i32);
|
|
|
@ -187,17 +186,17 @@ let black = Color(0, 0, 0);
|
|
|
|
let origin = Point(0, 0, 0);
|
|
|
|
let origin = Point(0, 0, 0);
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
注意 `black` 和 `origin` 值是不同的类型,因为它们是不同的元组结构体的实例。我们定义的每一个结构体有其自己的类型,即使结构体中的字段有着相同的类型。例如,一个获取 `Color` 类型参数的函数不能接受 `Point` 作为参数,即便这两个类型都由三个 `i32` 值组成。在其他方面,元组结构体实例类似于元组:可以将其解构为单独的部分,也可以使用 `.` 后跟索引来访问单独的值,等等。
|
|
|
|
注意 `black` 和 `origin` 值的类型不同,因为它们是不同的元组结构体的实例。你定义的每一个结构体有其自己的类型,即使结构体中的字段有着相同的类型。例如,一个获取 `Color` 类型参数的函数不能接受 `Point` 作为参数,即便这两个类型都由三个 `i32` 值组成。在其他方面,元组结构体实例类似于元组:可以将其解构为单独的部分,也可以使用 `.` 后跟索引来访问单独的值,等等。
|
|
|
|
|
|
|
|
|
|
|
|
### 没有任何字段的类单元结构体
|
|
|
|
### 没有任何字段的类单元结构体
|
|
|
|
|
|
|
|
|
|
|
|
我们也可以定义一个没有任何字段的结构体!它们被称为 **类单元结构体**(*unit-like structs*)因为它们类似于 `()`,即 unit 类型。类单元结构体常常在你想要在某个类型上实现 trait 但不需要在类型内存储数据的时候发挥作用。我们将在第十章介绍 trait。
|
|
|
|
我们也可以定义一个没有任何字段的结构体!它们被称为 **类单元结构体**(*unit-like structs*)因为它们类似于 `()`,即 unit 类型。类单元结构体常常在你想要在某个类型上实现 trait 但不需要在类型中存储数据的时候发挥作用。我们将在第十章介绍 trait。
|
|
|
|
|
|
|
|
|
|
|
|
> ## 结构体数据的所有权
|
|
|
|
> ## 结构体数据的所有权
|
|
|
|
>
|
|
|
|
>
|
|
|
|
> 在示例 5-1 中的 `User` 结构体的定义中,我们使用了自身拥有所有权的 `String` 类型而不是 `&str` 字符串 slice 类型。这是一个有意而为之的选择,因为我们想要这个结构体拥有它所有的数据,为此只要整个结构体是有效的话其数据也是有效的。
|
|
|
|
> 在示例 5-1 中的 `User` 结构体的定义中,我们使用了自身拥有所有权的 `String` 类型而不是 `&str` 字符串 slice 类型。这是一个有意而为之的选择,因为我们想要这个结构体拥有它所有的数据,为此只要整个结构体是有效的话其数据也是有效的。
|
|
|
|
>
|
|
|
|
>
|
|
|
|
> 可以使结构体储存被其他对象拥有的数据的引用,不过这么做的话需要用上 **生命周期**(*lifetimes*),这是一个第十章会讨论的 Rust 功能。生命周期确保结构体引用的数据有效性跟结构体本身保持一致。如果你尝试在结构体中储存一个引用而不指定生命周期将是无效的,比如这样:
|
|
|
|
> 可以使结构体存储被其他对象拥有的数据的引用,不过这么做的话需要用上 **生命周期**(*lifetimes*),这是一个第十章会讨论的 Rust 功能。生命周期确保结构体引用的数据有效性跟结构体本身保持一致。如果你尝试在结构体中存储一个引用而不指定生命周期将是无效的,比如这样:
|
|
|
|
>
|
|
|
|
>
|
|
|
|
> <span class="filename">文件名: src/main.rs</span>
|
|
|
|
> <span class="filename">文件名: src/main.rs</span>
|
|
|
|
>
|
|
|
|
>
|
|
|
@ -235,4 +234,4 @@ let origin = Point(0, 0, 0);
|
|
|
|
> | ^ expected lifetime parameter
|
|
|
|
> | ^ expected lifetime parameter
|
|
|
|
> ```
|
|
|
|
> ```
|
|
|
|
>
|
|
|
|
>
|
|
|
|
> 第十章会讲到如何修复这个问题以便在结构体中储存引用,不过现在,我们会使用像 `String` 这类拥有所有权的类型来替代 `&str` 这样的引用以修正这个错误。
|
|
|
|
> 第十章会讲到如何修复这个问题以便在结构体中存储引用,不过现在,我们会使用像 `String` 这类拥有所有权的类型来替代 `&str` 这样的引用以修正这个错误。
|
|
|
|