文章目录
-
- 一、回顾与导入
- 二、定义与实例化结构体
-
- [2.1 定义结构体](#2.1 定义结构体)
- [2.2 创建实例](#2.2 创建实例)
- [2.3 修改字段(需要 mut)](#2.3 修改字段(需要 mut))
- 三、字段初始化简写
- [四、结构体更新语法(Struct Update Syntax)](#四、结构体更新语法(Struct Update Syntax))
- [五、元组结构体(Tuple Struct)](#五、元组结构体(Tuple Struct))
- [六、类单元结构体(Unit-Like Struct)](#六、类单元结构体(Unit-Like Struct))
- 七、为结构体添加方法
-
- [7.1 定义方法](#7.1 定义方法)
- [7.2 方法参数类型](#7.2 方法参数类型)
- [八、关联函数(Associated Functions)](#八、关联函数(Associated Functions))
- [九、多个 impl 块](#九、多个 impl 块)
- 十、完整代码示例
- 十一、常见陷阱与最佳实践
-
- [陷阱 1:忘记使用 mut 导致无法修改](#陷阱 1:忘记使用 mut 导致无法修改)
- [陷阱 2:结构体更新导致所有权转移](#陷阱 2:结构体更新导致所有权转移)
- 最佳实践
- [添加 Debug 输出](#添加 Debug 输出)
- 十二、总结与速查
- 十三、思考题
- 参考链接
掌握 Rust 中的自定义数据类型 ------ 结构体定义、实例化、方法以及关联函数
一、回顾与导入
前面八篇文章我们学习了 Rust 的基础语法:
- 变量、数据类型、函数、控制流
- 所有权、引用、借用、切片
这些都是 Rust 的"基石"。从今天开始,我们将进入 Rust 的面向对象特性 ------ 首先是结构体(Struct)。
💡 结构体是一种自定义数据类型,允许你将多个相关的值组合成一个有意义的整体。
二、定义与实例化结构体
2.1 定义结构体
使用 struct 关键字,花括号内定义字段(field):
rust
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
2.2 创建实例
rust
fn main() {
let user1 = User {
active: true,
username: String::from("rustacean"),
email: String::from("rust@example.com"),
sign_in_count: 1,
};
println!("用户名: {}", user1.username);
}
2.3 修改字段(需要 mut)
rust
fn main() {
let mut user1 = User {
active: true,
username: String::from("rustacean"),
email: String::from("rust@example.com"),
sign_in_count: 1,
};
user1.email = String::from("newemail@example.com");
println!("新邮箱: {}", user1.email);
}
⚠️ Rust 不支持单独字段的可变性,要么整个实例可变,要么整个不可变。
三、字段初始化简写
当变量名与字段名相同时,可以使用简写:
rust
fn build_user(email: String, username: String) -> User {
User {
active: true,
username, // 等同于 username: username
email, // 等同于 email: email
sign_in_count: 1,
}
}
四、结构体更新语法(Struct Update Syntax)
从已有实例创建新实例,未指定的字段使用原实例的值:
rust
fn main() {
let user1 = User {
active: true,
username: String::from("user1"),
email: String::from("user1@example.com"),
sign_in_count: 1,
};
// 创建 user2,email 是新值,其他字段从 user1 复制
let user2 = User {
email: String::from("user2@example.com"),
..user1 // 剩余字段使用 user1 的值
};
println!("user2: {}, {}", user2.username, user2.email);
// println!("user1.username: {}", user1.username); // ❌ user1 的 username 已被移动!
}
⚠️ 注意所有权 :
..user1语法会移动字段的所有权。上面的例子中,user1.username(String 类型)被移动到了user2,所以user1之后不能再使用username字段。但active和sign_in_count是Copy类型,仍然可用。
五、元组结构体(Tuple Struct)
没有字段名,只有字段类型,适合用于创建类型别名:
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);
// 类型不同,即使内部结构相同也不能混用
// let p = black; // ❌ 编译错误!
println!("Color: ({}, {}, {})", black.0, black.1, black.2);
println!("Point: ({}, {}, {})", origin.0, origin.1, origin.2);
}
六、类单元结构体(Unit-Like Struct)
没有字段的结构体,适用于需要在某个类型上实现 trait 但不需要存储数据:
rust
struct AlwaysEqual; // 不需要花括号
fn main() {
let subject = AlwaysEqual;
// 通常配合 trait 使用
}
七、为结构体添加方法
使用 impl 块(implementation)为结构体定义方法。
7.1 定义方法
rust
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// &self 是 self: &Self 的语法糖
fn area(&self) -> u32 {
self.width * self.height
}
// 带参数的方法
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
fn main() {
let rect = Rectangle {
width: 30,
height: 50,
};
println!("面积: {}", rect.area());
let rect2 = Rectangle { width: 20, height: 40 };
let rect3 = Rectangle { width: 40, height: 60 };
println!("rect 能容纳 rect2? {}", rect.can_hold(&rect2));
println!("rect 能容纳 rect3? {}", rect.can_hold(&rect3));
}
7.2 方法参数类型
| 接收者 | 简写 | 完整形式 | 所有权变化 |
|---|---|---|---|
| 不可变引用 | &self |
self: &Self |
只读借用 |
| 可变引用 | &mut self |
self: &mut Self |
可变借用 |
| 获取所有权 | self |
self: Self |
转移所有权,方法结束后被 drop |
rust
impl Rectangle {
// 只读方法
fn area(&self) -> u32 {
self.width * self.height
}
// 修改方法
fn scale(&mut self, factor: u32) {
self.width *= factor;
self.height *= factor;
}
// 消耗方法(调用后实例不再有效)
fn destroy(self) {
println!("销毁一个 {}x{} 的矩形", self.width, self.height);
// self 在这里被 drop
}
}
八、关联函数(Associated Functions)
在 impl 块中定义不带 self 参数的函数,称为关联函数。通常用作构造函数。
rust
impl Rectangle {
// 关联函数,通常用于构造新实例
fn square(size: u32) -> Self {
Self {
width: size,
height: size,
}
}
}
fn main() {
// 使用 :: 语法调用关联函数
let sq = Rectangle::square(25);
println!("正方形面积: {}", sq.area());
}
九、多个 impl 块
Rust 允许将方法分散在多个 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
}
}
虽然可以分开写,但通常用单个
impl块更清晰。多个impl块主要用于泛型和 trait 实现(后续章节)。
十、完整代码示例
rust
#[derive(Debug)]
struct User {
username: String,
email: String,
age: u32,
active: bool,
}
impl User {
// 构造函数关联函数
fn new(username: String, email: String, age: u32) -> Self {
Self {
username,
email,
age,
active: true,
}
}
// 方法:不可变引用
fn greet(&self) -> String {
format!("你好,我是 {},{} 岁", self.username, self.age)
}
// 方法:可变引用
fn deactivate(&mut self) {
self.active = false;
}
// 方法:获取所有权(消耗)
fn into_string(self) -> String {
format!("{} ({})", self.username, self.email)
}
}
// 元组结构体
struct Point(i32, i32);
impl Point {
fn distance_from_origin(&self) -> f64 {
((self.0.pow(2) + self.1.pow(2)) as f64).sqrt()
}
}
// 类单元结构体
struct Logger;
impl Logger {
fn log(&self, msg: &str) {
println!("[LOG] {}", msg);
}
}
fn main() {
println!("=== 1. 结构体实例化 ===");
let mut user = User::new(
String::from("alice"),
String::from("alice@example.com"),
25,
);
println!("{}", user.greet());
println!("\n=== 2. 修改字段 ===");
user.age = 26;
println!("修改年龄后: {}", user.greet());
println!("\n=== 3. 方法调用 ===");
user.deactivate();
println!("激活状态: {}", user.active);
println!("\n=== 4. 消耗方法 ===");
let user_info = user.into_string();
println!("{}", user_info);
// println!("{}", user.greet()); // ❌ user 已经被消耗
println!("\n=== 5. 元组结构体 ===");
let p = Point(3, 4);
println!("点 ({}, {}) 到原点距离: {:.2}", p.0, p.1, p.distance_from_origin());
println!("\n=== 6. 类单元结构体 ===");
let logger = Logger;
logger.log("程序启动");
println!("\n=== 7. 结构体更新语法 ===");
let user1 = User::new(
String::from("bob"),
String::from("bob@example.com"),
30,
);
let user2 = User {
email: String::from("bob_new@example.com"),
..user1
};
println!("user2 邮箱: {}", user2.email);
println!("user2 用户名: {}", user2.username);
// user1.username 已经被移动到 user2,不能再使用
}
十一、常见陷阱与最佳实践
陷阱 1:忘记使用 mut 导致无法修改
rust
struct Point { x: i32, y: i32 }
fn main() {
let p = Point { x: 0, y: 0 };
// p.x = 10; // ❌ 编译错误:p 不可变
}
陷阱 2:结构体更新导致所有权转移
rust
struct Person {
name: String,
age: i32,
}
fn main() {
let p1 = Person { name: String::from("Alice"), age: 20 };
let p2 = Person { age: 21, ..p1 };
println!("{}", p1.name); // ❌ p1.name 已被移动
println!("{}", p1.age); // ✅ age 是 Copy 类型,仍然可用
}
最佳实践
| 场景 | 推荐做法 |
|---|---|
| 打印结构体 | 添加 #[derive(Debug)],使用 {:?} 或 {:#?} |
| 构造函数 | 使用关联函数 new 或 from |
| 不需要修改的方法 | 使用 &self |
| 需要修改的方法 | 使用 &mut self |
| 需要消耗实例的方法 | 使用 self |
添加 Debug 输出
rust
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect = Rectangle { width: 30, height: 50 };
// 调试打印
println!("{:?}", rect); // 单行
println!("{:#?}", rect); // 多行(pretty print)
}
十二、总结与速查
| 概念 | 语法 | 示例 |
|---|---|---|
| 定义结构体 | struct Name { fields } |
struct User { name: String } |
| 创建实例 | Name { fields } |
User { name: "Alice".into() } |
| 访问字段 | instance.field |
user.name |
| 更新语法 | Name { field: val, ..other } |
User { age: 18, ..user1 } |
| 元组结构体 | struct Name(Types) |
struct Color(i32, i32, i32) |
| 类单元结构体 | struct Name; |
struct AlwaysEqual; |
| 方法 | impl Name { fn (&self) } |
impl User { fn greet(&self) } |
| 关联函数 | fn new() -> Self |
User::new() |
核心要点:
- ✅ 结构体将相关数据组合在一起
- ✅ 使用
mut才能使实例可变 - ✅ 方法定义在
impl块中 - ✅
&self是只读方法,&mut self是可写方法 - ✅ 关联函数(如
new)通常用作构造函数 - ✅ 使用
#[derive(Debug)]可以方便地打印结构体
十三、思考题
- 下面的代码能否编译?为什么?
rust
struct Book {
title: String,
pages: u32,
}
fn main() {
let book = Book {
title: String::from("Rust Book"),
pages: 500,
};
let book2 = Book { pages: 600, ..book };
println!("{}", book.title);
}
-
方法和关联函数有什么区别? -
为什么 Rust 要区分
self、&self和&mut self?