Rust 结构体

本文详细介绍了Rust编程语言中的结构体,包括如何定义、创建和访问结构体,以及结构体的更新、元组结构体、单元结构体和结构体数据的所有权规则。同时,讲解了如何使用`#[derive(Debug)]`进行结构体信息的打印,以及结构体实例的调试方法。
摘要由CSDN通过智能技术生成

Rust 结构体

结构体与元组相像,由多种类型组合而成;不同的是,结构体可以为内部的每个字段起一个富有含义的名称,不用按照字段的顺序来访问和解析。

结构体语法

定义结构体

一个结构体由几部分组成:

  • 通过关键字 struct 定义
  • 一个清晰明确的结构体 名称
  • 几个有名字的结构体 字段
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

该结构体名称是 User,拥有 4 个字段,且每个字段都有对应的字段名及类型声明,例如 username 代表了用户名,是一个可变的 String 类型。

创建结构体实例

为了使用上述结构体,我们需要创建 User 结构体的实例:

    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };
  1. 初始化实例时,每个字段都需要进行初始化
  2. 初始化时的字段顺序不需要和结构体定义时的顺序一致

访问

通过 . 操作符即可访问结构体实例内部的字段值,也可以修改它们:

    let mut user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };

    user1.email = String::from("anotheremail@example.com");

当然,必须要将结构体实例声明为可变的,才能修改其中的字段,Rust 不支持将某个结构体某个字段标记为可变。

简化结构体构建

下面的函数类似一个构建函数,返回了 User 结构体的实例:

fn build_user(email: String, username: String) -> User {
    User {
        email: email,
        username: username,
        active: true,
        sign_in_count: 1,
    }
}

它接收两个字符串参数: email 和 username,然后使用它们来创建一个 User 结构体,并且返回。

email: emailusername: username 比较啰嗦,下面也可以:

fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}

如上所示,当函数参数和结构体字段同名时,可以直接使用缩略的方式进行初始化。

结构体更新

在实际场景中,有一种情况很常见:根据已有的结构体实例,创建新的结构体实例,例如根据已有的 user1 实例来构建 user2:

  let user2 = User {
        active: user1.active,
        username: user1.username,
        email: String::from("another@example.com"),
        sign_in_count: user1.sign_in_count,
    };

手动把 user1 的三个字段逐个赋值给 user2 很啰嗦,Rust 为提供了 结构体更新语法:

  let user2 = User {
        email: String::from("another@example.com"),
        ..user1
    };

因为 user2 仅仅在 email 上与 user1 不同,因此我们只需要对 email 进行赋值,剩下的通过结构体更新语法 …user1 即可完成。

… 语法表明凡是我们没有显示声明的字段,全部从 user1 中自动获取。需要注意的是 …user1 必须在结构体的尾部使用。

结构体更新语法跟赋值语句 = 非常相像,因此在上面代码中,user1 的部分字段所有权被转移到 user2 中:username 字段发生了所有权转移,作为结果,user1 无法再被使用。但是,user1 内部的其它字段能被继续使用。

元组结构体

结构体必须要有名称,但是结构体的字段可以没有名称,这种结构体长得很像元组,因此被称为元组结构体,例如:


    struct Color(i32, i32, i32);
    struct Point(i32, i32, i32);

    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);

元组结构体在你希望有一个整体名称,但是又不关心里面字段的名称时将非常有用。例如上面的 Point 元组结构体,众所周知 3D 点是 (x, y, z) 形式的坐标点,因此我们无需再为内部的字段逐一命名为:x, y, z。

单元结构体

单元结构体就跟单元类型很像,没有任何字段和属性。
如果你定义一个类型,但是不关心该类型的内容, 只关心它的行为时,就可以使用 单元结构体:

struct AlwaysEqual;

let subject = AlwaysEqual;

// 我们不关心 AlwaysEqual 的字段数据,只关心它的行为,因此将它声明为单元结构体,然后再为它实现某个特征
impl SomeTrait for AlwaysEqual {

}

结构体数据的所有权

也可以让 User 结构体从其它对象借用数据,不过这么做,就需要引入生命周期 (lifetimes) 这个新概念,简而言之,生命周期能确保结构体的作用范围要比它所借用的数据的作用范围要小。
总之,如果你想在结构体中使用一个引用,就必须加上生命周期,否则就会报错。

struct User {
    username: &str,             // 引用类型
    email: &str,
    sign_in_count: u64,
    active: bool,
}

fn main() {
    let user1 = User {
        email: "someone@example.com",
        username: "someusername123",
        active: true,
        sign_in_count: 1,
    };
}

使用 #[derive(Debug)] 来打印结构体的信息

使用 #[derive(Debug)] 对结构体进行了标记,这样可以使用 println!(“{:?}”, s); 的方式对其进行打印输出,如果不加,会提示

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

Rectangle 没有实现 Display 特征,这是因为如果我们使用 {} 来格式化输出,那对应的类型就必须实现 Display 特征,以前学习的基本类型,都默认实现了该特征

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

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

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

输出如下:

$ cargo run
rect1 is Rectangle { width: 30, height: 50 }

当结构体较大时,我们可能希望能够有更好的输出表现,此时可以使用 {:#?} 来替代 {:?},输出如下:

rect1 is Rectangle {
    width: 30,
    height: 50,
}

此时结构体的输出跟我们创建时候的代码几乎一模一样了!当然,如果大家还是不满足,那最好还是自己实现 Display 特征,以向用户更美的展示你的私藏结构体。

还有一个简单的输出 debug 信息的方法,那就是使用 dbg! 宏,它会拿走表达式的所有权,然后打印出相应的文件名、行号等 debug 信息,当然还有我们需要的表达式的求值结果。除此之外,它最终还会把表达式值的所有权返回!

dbg! 输出到标准错误输出 stderr,而 println! 输出到标准输出 stdout

下面的例子中清晰的展示了 dbg! 如何在打印出信息的同时,还把表达式的值赋给了 width:

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

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

    dbg!(&rect1);
}

最终的 debug 输出如下:

$ cargo run
[src/main.rs:10] 30 * scale = 60
[src/main.rs:14] &rect1 = Rectangle {
    width: 60,
    height: 50,
}

debug 信息几乎都有了:代码所在的文件名、行号、表达式以及表达式的值。

Rust 中,你可以使用结构体和数组的初始化语法来创建和初始化实例。下面是一些示例: ### 结构体初始化 ```rust struct Point { x: i32, y: i32, } fn main() { // 直接初始化结构体的字段 let p1 = Point { x: 10, y: 20 }; // 部分字段初始化,其他字段使用默认值 let p2 = Point { x: 5, ..p1 }; // 通过解构元组来初始化结构体字段 let (x, y) = (15, 25); let p3 = Point { x, y }; println!("p1: x = {}, y = {}", p1.x, p1.y); println!("p2: x = {}, y = {}", p2.x, p2.y); println!("p3: x = {}, y = {}", p3.x, p3.y); } ``` 在上面的示例中,我们定义了一个名为 `Point` 的结构体,它有两个字段 `x` 和 `y`。然后,我们使用不同的方式来初始化结构体实例 `p1`、`p2` 和 `p3`。最后,我们打印每个实例的字段值。 ### 数组初始化 ```rust fn main() { // 声明并初始化一个数组 let arr1: [i32; 5] = [1, 2, 3, 4, 5]; // 使用重复值初始化数组 let arr2 = [0; 3]; // 等同于 [0, 0, 0] println!("arr1: {:?}", arr1); println!("arr2: {:?}", arr2); } ``` 在上面的示例中,我们声明了一个包含 5 个 `i32` 类型元素的数组 `arr1`,并使用具体的值来初始化它。另外,我们还使用 `[0; 3]` 的语法来初始化一个包含三个 0 的数组 `arr2`。最后,我们打印两个数组的值。 需要注意的是,Rust 的数组长度是固定的,一旦定义就无法改变。如果你需要动态大小的数组,可以考虑使用 `Vec` 类型。 这些是在 Rust 中初始化结构体和数组的基本方法。你可以根据需要进行适当的调整和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值