Rust 结构体详解:从定义到实例化的指南
文章目录
- [Rust 结构体详解:从定义到实例化的指南](#Rust 结构体详解:从定义到实例化的指南)
-
- [1. 前言](#1. 前言)
- [2. 正文](#2. 正文)
- [3. 小结](#3. 小结)
1. 前言
struct,或者 structure,是一个自定义数据类型,允许你命名和包装多个相关的值,从而形成一个有意义的组合.如果你熟悉一门面向对象语言,struct 就像对象中的数据属性.在本章中,我们会对元组和结构体进行比较和对比,以及演示如何定义和实例化结构体,并讨论如何定义关联函数,特别是被称为方法的那种关联函数,以指定与结构体类型相关的行为.
2. 正文
2.1 如何创建结构体
在创建结构体的时候,需要用一个 struct 关键字并且为整个结构体提供一个名字.结构体的名字需要描述它所组合的数据的意义.接着就是在大括号后中定义数据和类型,每定义一部分数据的名字和类型被称为字段.
创建一个实例往往以结构体的名字开头,而后在大括号中使用 key: value 键值对的方式来提供字段,其中 key 是字段的名字,value 是所需要存的数据值.实例中字段的顺序不需要和在结构体的类型一致.结构体同其他语言如 C++ 一样就像一个通用模板,而实例则会在这个模板中放入特定的数据来创建这个类型的值.
当然,创建结构体不止于此,接下来还要讲述几种算得上是小技巧的点:
变量与字段同名的字段初始化简写语法
rust
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
fn main() {
let user1 = build_user(
String::from("someone@example.com"),
String::from("someusername123"),
);
}
上面的代码中参数名和字段名完全相同,可以用字段初始化的简写语法,这和前面的行为完全相同.
使用结构体更新语法从其他实例创建实例
如果创建结构体的时候,结构体之间有很多相同的字段,如果每一个字段都手动添加就会显得很繁琐.这个时候就可以使用 .. 来指定剩余没有设置的字段给定相应的实例对应的字段了.这又可以省不少力了.
rust
fn main() {
// -- 前面代码相同
let user2 = User {
email: String::from("another@example.com"),
..user1
};
}
需要注意的是,..user1 语法并非复制整个结构体,而是将未显式指定的字段从 user1 中转移过来.对于 String 这类拥有堆内存的数据,这一过程是Move(移动);而 bool 类型的 active 和 u64 类型的 sign_in_count 实现的就是 Copy(拷贝)了,因为它们都是栈分配类型的.
所有权陷阱示例:
rust
fn main() {
let user1 = build_user(
String::from("someone@example.com"),
String::from("someusername123"),
);
let user2 = User {
email: String::from("another@example.com"),
..user1 // username 的所有权被转移给 user2
};
// 下面这行会编译错误!因为 user1.username 已被 Move
// println!("{}", user1.username);
// 但可以访问未被 Move 的字段(Copy 类型)
println!("{}", user1.active); // 输出: true
}
使用没有命名字的元组结构体来创建不同的类型
元组结构体有着结构体名称提供的含义,但是没有具体的字段名,只有字段的类型.给一个元组提供一个名字,让它变成和其他元组不同的类型,这时候再像常规结构体一样命名就显得多余了.
rust
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}
没有任何字段的单元结构体
我们也可以定义一个没有任何字段的结构体!它们被称为单元结构体.它们类似于 unit 类型 ().单元结构体常常在想要实现的某个类型上实现 trait,但是不需要在类型中储存数据发挥作用.
rust
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
}
在 User 结构体的定义中,我们使用了自身拥有所有权的 String 类型而不是 &str 字符串 slice 类型.这是一个有意而为之的选择,因为我们想要这个结构体拥有它所有的数据,为此只要整个结构体是有效的话其数据也是有效的.
可以使结构体存储被其他对象拥有的数据的引用,不过这么做的话需要用上生命周期(lifetime),例如:
rust
> struct UserWithRef<'a> {
> username: &'a str,
> email: &'a str,
> }
> ```
---
2.2 如何使用结构体
创建结构体之后需要按赋值的方式实例化,创建该结构体的实例。
访问和修改字段
从结构体中获取某个特定的值也和 C++ 一样都可以使用 `.` 来获取相应字段的值并作出一些修改。
```rust
fn main() {
let mut user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");
println!("{}", user1.email);
}
但是整个实例要是可变的,只将某个字段标记为可变在 Rust 中是不被允许的.
结构体作为返回值
可以和其他函数体一样在函数的结尾构造一个结构体的实例来隐式返回这个结构体.
rust
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
} // 隐式返回 User 实例
}
2.3 打印结构体
为了方便调试,可以使用 #[derive(Debug)] 属性来自动实现 Debug trait,从而可以用 {:?} 或 {:#?} 格式化打印结构体.
rust
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {:?}", rect1);
// 输出: rect1 is Rectangle { width: 30, height: 50 }
println!("rect1 is {:#?}", rect1);
// 输出:
// rect1 is Rectangle {
// width: 30,
// height: 50,
// }
}
2.4 定义方法
使用 impl(implementation)块可以为结构体定义方法.方法的第一个参数总是 self(或其引用 &self、可变引用 &mut self),代表调用该方法的实例.
rust
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// 方法:计算面积
fn area(&self) -> u32 {
self.width * self.height
}
// 方法:判断是否能容纳另一个矩形
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
// 关联函数(不是方法,因为没有 self 参数)
// 常用于构造函数
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
let rect2 = Rectangle {
width: 10,
height: 40,
};
let sq = Rectangle::square(10); // 用关联函数创建正方形
println!("rect1 的面积: {}", rect1.area());
println!("rect1 能容纳 rect2: {}", rect1.can_hold(&rect2));
println!("正方形: {:?}", sq);
}
方法 vs 关联函数:
- 方法:第一个参数是
self/&self/&mut self,通过实例调用(如rect1.area()). - 关联函数:没有
self参数,通过结构体名调用(如Rectangle::square(10)),常用于构造函数.
2.5 多个 impl 块
每个结构体允许拥有多个 impl 块,这在功能上没有任何区别,但有助于代码组织和可读性.
rust
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
3. 小结
| 特性 | 说明 |
|---|---|
| 命名结构体 | struct Name { field: Type } |
| 元组结构体 | struct Name(Type1, Type2) |
| 单元结构体 | struct Name; |
| 字段初始化简写 | 参数名与字段名相同时可直接写字段名 |
| 结构体更新语法 | ..instance 用于从其他实例复制剩余字段 |
| 所有权 | 更新语法中 String 等堆数据会 Move,i32 等栈数据会 Copy |
| 方法 | impl 块中定义,第一个参数为 self |
| 关联函数 | impl 块中定义,无 self 参数,通过结构体名调用 |
| Debug 打印 | #[derive(Debug)] + {:?} 或 {:#?} |
如果这篇文章帮到了你,不妨:
👍 点赞 ------ 让更多人看到这篇教程
⭐ 收藏 ------ 下次直接翻出来看
💬 评论 ------ 遇到任何问题,评论区交流,我会尽力解答
🔖 关注 -- 继续学习 Rust 相关知识