使用结构的示例程序
为了了解我们何时可能想要使用结构体,让我们编写一个程序 计算矩形的面积。我们将从使用单个变量开始,然后 然后重构程序,直到我们改用结构体。
让我们用 Cargo 做一个新的二进制项目,叫做 rectangles,它将 以像素为单位指定的矩形的宽度和高度,并计算面积 的矩形。示例 5-8 显示了一个具有 one作方式的简短程序 正是在我们项目的 src/main.rs 中。
fn main() { let width1 = 30; let height1 = 50; println!( "The area of the rectangle is {} square pixels.", area(width1, height1) ); } fn area(width: u32, height: u32) -> u32 { width * height }
现在,使用cargo run
:
$ cargo run
Compiling rectangles v0.1.0 (file:///projects/rectangles)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.42s
Running `target/debug/rectangles`
The area of the rectangle is 1500 square pixels.
此代码通过调用area
函数,但我们可以做更多的事情来使这段代码清晰
并且可读性强。
此代码的问题在area
:
fn main() {
let width1 = 30;
let height1 = 50;
println!(
"The area of the rectangle is {} square pixels.",
area(width1, height1)
);
}
fn area(width: u32, height: u32) -> u32 {
width * height
}
这area
函数应该计算一个矩形的面积,但是
函数有两个参数,在我们的
程序,参数相关。它会更具可读性和更多
易于将 width 和 height 组合在一起。我们已经讨论过一种方法
我们可以在 “The Tuple Type” 一节中这样做
第 3 章:通过使用元组。
使用 Tuples 进行重构
示例 5-9 显示了我们程序的另一个使用元组的版本。
fn main() { let rect1 = (30, 50); println!( "The area of the rectangle is {} square pixels.", area(rect1) ); } fn area(dimensions: (u32, u32)) -> u32 { dimensions.0 * dimensions.1 }
在某种程度上,这个程序更好。元组让我们添加一点结构,并且 我们现在只传递一个参数。但从另一个方面来说,这个版本更少 clear: Tuples 不命名它们的元素,因此我们必须对 元组,使我们的计算不那么明显。
混合 width 和 height 对于面积计算无关紧要,但如果
我们想在屏幕上绘制矩形,这很重要!我们将不得不
请记住,width
是元组索引0
和height
是元组
指数1
.这对其他人来说更难弄清楚并坚持下去
介意他们是否使用我们的代码。因为我们没有传达
我们的数据在我们的代码中,现在更容易引入错误。
使用结构体进行重构:添加更多含义
我们使用结构体通过标记数据来添加含义。我们可以将元组 我们正在 use into 一个结构体,该结构体具有整体的名称以及 部分,如示例 5-10 所示。
struct Rectangle { width: u32, height: u32, } fn main() { let rect1 = Rectangle { width: 30, height: 50, }; println!( "The area of the rectangle is {} square pixels.", area(&rect1) ); } fn area(rectangle: &Rectangle) -> u32 { rectangle.width * rectangle.height }
Rectangle
结构在这里,我们定义了一个结构体并将其命名为Rectangle
.卷曲内部
brackets 中,我们将字段定义为width
和height
,两者都具有
类型u32
.然后,在main
中,我们创建了一个特定的Rectangle
的宽度为30
高度为50
.
我们area
function 现在使用一个参数定义,我们将其命名为rectangle
,其类型是结构体的不可变借用Rectangle
实例。如第 4 章所述,我们想要借用结构体,而不是
拥有它的所有权。这边main
保留其所有权并可以继续
用rect1
,这就是我们在函数签名中使用 和
我们调用函数的位置。&
这area
函数访问width
和height
字段的Rectangle
实例(请注意,访问借用的结构实例的字段不会
移动字段值,这就是您经常看到 Borrows of Structs) 的原因。我们
的函数签名area
现在说的正是我们的意思:计算面积
之Rectangle
,使用其width
和height
领域。这传达了
width 和 height 彼此相关,它为
值而不是使用0
和1
.这是一个
赢了才明白。
使用派生特征添加有用的功能
如果能够打印Rectangle
当我们
调试我们的程序并查看其所有字段的值。示例 5-11 次尝试
使用println!
宏正如我们在
前几章。但是,这行不通。
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {}", rect1);
}
Rectangle
实例当我们编译这段代码时,我们会收到一个错误,其中包含以下核心消息:
error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`
这println!
Macro 可以执行多种格式设置,默认情况下,curly
括号表示println!
使用称为Display
:预期输出
供最终用户直接使用。到目前为止我们见过的基元类型
实现Display
因为您只想以一种方式显示
一个1
或任何其他基元类型。但是对于结构体,方式println!
应该格式化输出不太清楚,因为有更多
显示可能性:是否需要逗号?是否要打印
大括号?是否应显示所有字段?由于这种歧义,Rust
不会试图猜测我们想要什么,并且结构体没有提供的
实现Display
搭配使用println!
和占位符。{}
如果我们继续阅读这些错误,我们会发现这个有用的说明:
= help: the trait `std::fmt::Display` is not implemented for `Rectangle`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
让我们试试吧!这println!
宏调用现在将如下所示println!("rect1 is {rect1:?}");
.放置说明符:?
大括号内表示println!
我们想使用一个名为Debug
.这Debug
特性
使我们能够以对开发人员有用的方式打印我们的结构体,因此我们可以
在调试代码时查看其值。
使用此更改编译代码。妈的!我们仍然收到一个错误:
error[E0277]: `Rectangle` doesn't implement `Debug`
但同样,编译器为我们提供了一个有用的说明:
= help: the trait `Debug` is not implemented for `Rectangle`
= note: add `#[derive(Debug)]` to `Rectangle` or manually `impl Debug for Rectangle`
Rust 确实包含打印调试信息的功能,但我们
必须显式选择使该功能可用于我们的结构体。
为此,我们添加了 outer 属性#[derive(Debug)]
就在
struct 定义,如示例 5-12 所示。
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } fn main() { let rect1 = Rectangle { width: 30, height: 50, }; println!("rect1 is {rect1:?}"); }
Debug
trait 并打印Rectangle
使用调试格式的实例现在,当我们运行程序时,我们不会收到任何错误,并且我们将看到 以下输出:
$ cargo run
Compiling rectangles v0.1.0 (file:///projects/rectangles)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s
Running `target/debug/rectangles`
rect1 is Rectangle { width: 30, height: 50 }
好!这不是最漂亮的输出,但它显示了所有字段的值
对于这个实例,这肯定会在调试过程中有所帮助。当我们有
较大的结构体,拥有更易于阅读的输出很有用;在
这些情况,我们可以使用{:#?}
而不是{:?}
在println!
字符串。在
此示例使用{:#?}
style 将输出以下内容:
$ cargo run
Compiling rectangles v0.1.0 (file:///projects/rectangles)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s
Running `target/debug/rectangles`
rect1 is Rectangle {
width: 30,
height: 50,
}
使用Debug
format 是使用dbg!
宏,它获取表达式的所有权(而不是
自println!
,它接受引用),则打印文件和行号
其中dbg!
宏调用与结果值一起发生在代码中
,并返回该值的所有权。
注意:调用dbg!
宏打印到标准错误控制台流
(stderr
),而不是println!
,打印到标准输出
控制台流 (stdout
).我们将详细讨论stderr
和stdout
在 “将错误消息写入标准错误而不是标准输出” 中
第 12 章中的部分。
下面是一个示例,我们对分配给width
field 以及rect1
:
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } fn main() { let scale = 2; let rect1 = Rectangle { width: dbg!(30 * scale), height: 50, }; dbg!(&rect1); }
我们可以将dbg!
围绕表达式30 * scale
而且,因为dbg!
返回表达式值的所有权,width
field 将获得
的值与没有dbg!
打电话到那里。我们不想dbg!
自
取得所有权rect1
,因此我们使用对rect1
在下一次通话中。
此示例的输出如下所示:
$ cargo run
Compiling rectangles v0.1.0 (file:///projects/rectangles)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s
Running `target/debug/rectangles`
[src/main.rs:10:16] 30 * scale = 60
[src/main.rs:14:5] &rect1 = Rectangle {
width: 60,
height: 50,
}
我们可以看到第一部分输出来自 src/main.rs 第 10 行,我们所在的位置
调试表达式30 * scale
,其结果值为60
(Debug
为 Integers 实现的格式是仅打印其值)。这dbg!
在 src/main.rs 的第 14 行调用 output 值&rect1
,即
这Rectangle
结构。此输出使用 prettyDebug
格式化Rectangle
类型。这dbg!
当您尝试
弄清楚你的代码在做什么!
除了Debug
trait 中,Rust 为我们提供了许多 trait
与derive
属性,它可以为我们的自定义
类型。这些特征及其行为在附录 C 中列出。我们将介绍如何使用自定义行为实现这些特征,如
以及如何在第 10 章中创建自己的特征。也有很多
除derive
;有关详细信息,请参阅“属性”
部分。
我们area
函数非常具体:它只计算矩形的面积。
将此行为与我们的Rectangle
结构
因为它不适用于任何其他类型的让我们看看如何继续
通过将area
函数转换为area
方法定义在我们的Rectangle
类型。
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准