Rust之结构体(二):结构体示例

开发环境

  • Windows 10
  • Rust 1.57.0

 

   VS Code 1.63.2

Rust之结构体(二):结构体示例_第1张图片

 项目工程

这里继续沿用上次工程rust-demo
Rust之结构体(二):结构体示例_第2张图片

结构体示例

为了理解什么时候可能需要使用结构体,让我们编写一个程序来计算矩形的面积。我们将从单变量开始,然后重构程序,直到使用结构代替。

让我们用`cargo`创建一个名为`rectangles`的新的程序,它将以像素为单位指定矩形的宽度和高度,并计算矩形的面积。代码如下

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

Rust之结构体(二):结构体示例_第3张图片

 尽管上述示例中可以通过调用每个维度的area函数计算出矩形的面积,但我们可以做得更好。宽度和高度是相互关联的,因为它们一起描述了一个矩形。

这个代码的问题在area的签名中很明显:

fn area(width: u32, height: u32) -> u32 {

面积函数area是用来计算一个矩形的面积的,但是我们写的这个函数有两个参数。这些参数是相关的,但在我们的程序中没有表示。将宽度和高度组合在一起将更易于阅读和管理。我们可以使用Tuple类型来重构上述代码。

使用Tuple重构

可以查看下述代码:

fn main() {
    let rect1 = (30, 50);           // 使用Tuple类型

    println!(
        "The area of the rectangle is {} square pixels.",
        area(rect1)
    );
}

fn area(dimensions: (u32, u32)) -> u32 {
    dimensions.0 * dimensions.1
}

在某种程度上,这段代码更好。tuple让我们添加一些结构,现在我们只传递一个参数。但是在另一种方式上,这个版本不太清楚:tuple没有命名它们的元素,因此我们的计算变得更加混乱,因为我们必须对元组的各个部分进行索引。

如果我们在计算面积时混合宽度和高度是没有关系的,但如果我们想在屏幕上绘制矩形,这就有关系了!我们必须记住,width是tuple的下标0height是元组的下标1。如果有其他人在研究这段代码,他们也会发现这一点,并牢记于心。很容易忘记或混淆这些值并导致错误,因为我们没有在代码中表达数据的含义。

使用结构体进行重构:增加更多的意义

我们通过标记数据来使用结构体来增加意义。我们可以将正在使用的tuple转换为具有整体名称和部分名称的数据类型,如下所示:

// 定义Rectangle结构体
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。在花括号内,我们定义了widthheight字段,它们的类型都是u32。然后在main中,我们创建了一个特殊的矩形实例,它的宽度为30,高度为50。

我们的area函数现在定义了一个参数,我们将其命名为rectangle,它的类型是一个不可变的借用struct rectangle实例。正如之前的章节所提到的,我们想借用这个结构,而不是占有它。这样,main保留了它的所有权,并可以继续使用rect1,这就是我们在函数签名中使用&的原因以及我们调用函数的地方。

area函数访问Rectangle实例的widthheight字段。我们对于area的函数签名现在确切地表达了我们的意思:计算矩形的面积,使用其widthheight字段。这表示宽度和高度是相互关联的,它为值提供描述性的名称,而不是使用元组索引值0和1,这样就很清晰了。

使用派生特征添加有用的功能

当我们重新调试程序并看到它所有字段的值时,能够打印一个Rectangle的实例是很好的。如下示例:

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

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

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

编译运行

cargo run

Rust之结构体(二):结构体示例_第4张图片

println!宏可以执行多种格式,默认情况下,大括号告诉println!使用被称为Display的格式化:直接供终端用户使用的输出。到目前为止,我们所看到的基元类型在默认情况下实现了Display,因为只有一种方式可以向用户显示1或任何其他基元类型。但是对于结构,println!输出的格式应该不那么清晰,因为有更多的显示可能性:是否需要逗号?要打印花括号吗?是否应该显示所有字段?由于这种模糊性,Rust不会尝试猜测我们想要什么,结构也没有提供Display的实现。 

可以尝试`println!("rect1 is {:?}", rect1)`这种方式,输入说明符:?在花括号里告诉println!我们希望使用一种名为Debug的输出格式。Debug特性使我们能够以一种对开发人员有用的方式打印结构,这样我们在重新调试代码时就可以看到它的值。更改后的代码如下: 

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

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

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

编译运行

cargo run

Rust之结构体(二):结构体示例_第5张图片

 Rust确实包含了打印调试信息的功能,但是我们必须明确地选择,使该功能对我们的结构可用。为此,我们在结构体定义前添加外部属性`[derive(Debug)]`,代码如下所示:

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

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

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

编译运行

cargo run

Rust之结构体(二):结构体示例_第6张图片

 好了!这不是最完美的输出,但它显示了这个实例的所有字段的值,这在调试期间肯定会有帮助。当我们有更大的结构体时,有更容易阅读的输出是很有用的。可以使用`{:#?}`代替`{:?}`,代码如下:

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

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

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

编译运行

cargo run

Rust之结构体(二):结构体示例_第7张图片

 使用Debug格式打印值的另一种方法是使用dbg!宏。dbg!宏获取表达式的所有权,输出dbg!宏调用与表达式的结果值一起发生在代码中,并返回值的所有权。调用dbg!宏打印到标准错误控制台流(stderr),而不是println!输出到标准输出控制台流(stdout)。这时代码如下:

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

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

    dbg!(&rect1);
}

 编译运行

cargo run

Rust之结构体(二):结构体示例_第8张图片

 我们可以看到第一个输出来自src/main.rs第489行,其中我们重新调试表达式30 * scale,其结果值为60(为整数实现的Debug格式是只打印它们的值)。dbg!src/main.rs的493行输出了Rectangle结构体实例&rect1。这个输出使用了Rectangle类型的完美的调试格式。当试图弄清楚代码正在做什么时,dbg!宏非常有用。

除了Debug特性之外,Rust还提供了许多特性,可以与派生属性一起使用,这些派生属性可以为我们的自定义类型添加有用的行为。

area函数非常具体:它只计算矩形的面积。这将有助于把这个行为更紧密地联系到我们的Rectangle结构,因为它不会与任何其他类型工作。让我们看看如何通过将area函数转换为定义在Rectangle类型上的area方法来继续重构这段代码。

本章重点

  • 结构体使用
  • Debug模式
  • dbg!宏的使用

你可能感兴趣的:(Rust,rust,开发语言,后端)