rust-结构体使用示例

发布于:2025-07-25 ⋅ 阅读:(22) ⋅ 点赞:(0)

使用结构体的示例程序

为了理解何时需要使用结构体,让我们编写一个计算矩形面积的程序。我们将先用单个变量实现,然后重构程序,直到改用结构体。

让我们用 Cargo 创建一个名为 rectangles 的新二进制项目,该项目接收以像素为单位指定的矩形宽度和高度,并计算该矩形的面积。清单 5-8 展示了在项目 src/main.rs 中实现这一功能的一种简短方式。

文件名: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
}

清单 5-8:通过分别指定宽度和高度变量来计算矩形面积
现在,运行此程序:

$ 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 area(width: u32, height: u32) -> u32 {

area 函数本应计算一个矩形的面积,但它有两个参数,而且在程序中并没有明确说明这两个参数是相关联的。将宽度和高度组合起来会使代码更具可读性和可维护性。我们已经在第3章“元组类型”部分讨论过一种方法:使用元组。

使用元组重构

清单 5-9 显示了另一个版本,采用了元组。

文件名:src/main.rs

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
}

清单 5-9:用元组指定矩形宽高

从某种意义上说,这个程序更好一些。元组给数据增加了一些结构,我们只传递了一个参数。但另一方面,这个版本不够直观,因为元组元素没有名字,我们必须通过索引访问各部分,使得计算过程不那么明显。

对于面积计算来说,混淆宽度和高度无关紧要,但如果想把这个矩形绘制到屏幕上就很重要!你必须记住宽度对应的是索引0,高度对应的是索引1。如果别人要使用我们的代码,要理解并牢记这些信息会更加困难。因为我们的代码未能表达数据含义,更容易导致错误产生。

用结构体重构:赋予更多含义

我们使用结构体,通过给数据加标签来赋予其含义。可以将之前用到的元组转换成具有整体名称及各部分名称的结构体,如清单5-10所示。

文件名:src/main.rs

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
}

清单5-10:定义 Rectangle 结构体

这里,我们定义了一个叫做 Rectangle 的结构体。在大括号内定义字段 width 和 height,它们都是类型为u32。在 main 中创建了具体实例 rect1,其宽为30,高为50。

area 函数现在只有一个参数,命名为 rectangle,其类型是对 Rectangle 实例的不变借用。如第4章所述,我们希望借用该 struct 而非取得所有权,这样 main 保留所有权,可以继续使用 rect1。这也是函数签名与调用处都用了 & 的原因所在。

area 函数访问 Rectangle 实例中的 width 和 height 字段(注意,对借用 struct 实例字段进行访问不会移动字段值,因此经常看到对 struct 的借用)。函数签名准确表达意图——利用其宽高字段计算 Rectangle 面积。这表明宽高彼此关联,并且比起直接按索引访问 tuple 元素(如 .0、.1),这种方式提供了描述性的名称,有助于提升代码可读性,这是一次胜利。

使用派生特性添加有用功能

在调试程序时,能够打印 Rectangle 实例并查看其所有字段的值会很有用。清单 5-11 尝试像前几章那样使用 println! 宏,但这行不通。

文件名:src/main.rs

struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!("rect1 is {}", rect1);
}

清单 5-11:尝试打印一个 Rectangle 实例
编译此代码时,会出现如下核心错误信息:

error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`

println! 宏可以进行多种格式化,默认情况下,大括号告诉 println! 使用称为 Display 的格式化方式:即面向最终用户的输出。到目前为止,我们见过的基本类型默认都实现了 Display,因为显示数字或其他基本类型给用户只有一种合理方式。但对于结构体来说,println! 应该如何格式化输出就不那么明确了,因为显示方式可能多种多样:是否需要逗号?是否要打印大括号?是否展示所有字段?由于这种歧义,Rust 不会猜测我们的意图,并且结构体没有提供用于 {} 占位符和 println! 的 Display 实现。

如果继续阅读错误信息,会看到这样一条提示:

   = 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:?}”);。将 :? 放入大括号内表示我们想用 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 确实包含了用于打印调试信息的功能,但必须显式选择让该功能可用于我们的结构体。方法是在 struct 定义之前加上外部属性 #[derive(Debug)],如清单 5-12 所示。

文件名:src/main.rs

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!("rect1 is {rect1:?}");
}

清单 5-12:添加属性以派生 Debug 特性,并使用调试格式打印 Rectangle 实例
现在运行程序,不会再出错,会看到如下输出:

$ cargo run
   Compiling rectangles v0.1.0 (file:///projects/rectangles)
    Finished dev [unoptimized + debuginfo] target(s) in 0.48s
     Running target/debug/rectangles
rect1 is Rectangle { width: 30, height: 50 }

不错!虽然不是最漂亮的输出,但它显示了实例所有字段的值,这在调试时非常有帮助。当结构体较大时,更易读的输出更实用;这时候可以在 println! 字符串中使用 {:#?} 替代 {:?} 。例如,本例若采用 {:#?} 风格,将得到以下结果:

$ cargo run
   Compiling rectangles v0.1.0 (file:///projects/rectangles)
    Finished dev [unoptimized + debuginfo] target(s) in 0.48s
     Running target/debug/rectangles
rect1 is Rectangle {
    width: 30,
    height: 50,
}

另一种利用 Debug 格式打印值的方法是 dbg! 宏,它接管表达式所有权(与接受引用参数的 println! 不同),同时打印调用处所在文件及行号以及表达式计算结果,然后返回该值所有权。

注意:调用 dbg! 会向标准错误流(stderr)写入,而非像 println! 那样写入标准输出流(stdout)。关于 stderr 和 stdout,我们将在第12章“将错误消息写入标准错误而非标准输出”部分详细讨论。

下面是一个示例,我们关注赋给宽度字段的值,以及整个 rect1 的值:

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height:u32,
}

fn main() {
	let scale =2;
	let rect_01=Rectangl{
		width :dbg!(30*scale),
		height :50 ,
	};

	dbg !(&rec t_01 );
}

我们把 dbg!( ) 包裹住表达式 30 * scale ,因为 dbg!( ) 返回的是表达式本身,所以宽度字段获得与未调用 dbg!( ) 时相同数值。不希望 dbg!( ) 接管 rect_01 所有权,因此下一次调用传递的是 &rec t_01 引用。这段代码执行后的输出来看:

$ cargo run 
Compiling rectangles v0 .10(file:// / projects / rectangles )
Finished dev[ unoptimized+debuginfo ]target( s)in O .61 s 
Running target/debug/rectangle s 
[src/main.rs:lO:l6]3O*scale=60 
[src/main.rs:l4:S]& rec t _Ol= Rectangl e{width :60,height :SO , }

第一条来自 src/main.rs 第10 行,是对表达式 3O * scale 调用了dbg!, 输出结果是60 (整数类型实现 Debug 是直接只打出数 值);第二条来自第14行,对 &rec t _Ol 调用了dbg!, 打印出了矩形实例内容,该内容采用美观版 Debug 格 式 。当你想弄明白代码做什么的时候,dbg宏特别好使!

除了 Debug 特性之外,Rust还提供了一些可通过 derive 属性自动生成行为特性的列表,这些特 性及其行为详见附录C。在第10章里,我们将讲解如何自定义这些特性的具体行为以及如何创建自己的特性。此外,还有许多除 derive 外其它属性,有关详情请参阅 Rust Reference 中“Attributes”章节 。

我们的 area 函数用途很专一,只计算矩形面积。如果能把这个行为更紧密地绑定到 Rectan gle 类型上,那就更好了,因为它不能作用于其他任何类型。接下来,让我们看看怎样通过 将 area 函数转变成定义在 Rectang le 类型上的 area 方法来继续重构这段代码吧。


网站公告

今日签到

点亮在社区的每一天
去签到