以下是关于 Rust 编程语言中结构体(struct)的教程。内容基于 Rust 官方文档(The Rust Book)的相关章节,提供简明解释、代码示例和关键注意事项。结构体是 Rust 中用于创建自定义数据类型的核心机制,它允许将多个相关值组合成一个有意义的数据单元。Rust 的结构体强调所有权、借用和可变性,以确保内存安全。
1. 定义结构体(Defining Structs)
结构体使用 struct
关键字定义,后面跟随结构体名称和用大括号 {}
包围的字段列表。每个字段包括名称和类型。
-
语法:
#![allow(unused)] fn main() { struct User { active: bool, username: String, email: String, sign_in_count: u64, } }
-
关键点:
- 结构体通常拥有其数据,使用如
String
的拥有类型,以确保数据在结构体存在期间有效。 - 如果使用引用(如
&str
),需要指定生命周期(lifetime),以避免悬垂引用(dangling references)。 - 字段不能单独标记为可变,整个结构体实例必须是可变的才能修改字段。
- 结构体通常拥有其数据,使用如
-
元组结构体(Tuple Structs):无命名字段,仅用类型定义,类似于命名元组。访问字段用索引(如
.0
)。 示例:#![allow(unused)] fn main() { struct Color(i32, i32, i32); let black = Color(0, 0, 0); println!("R: {}", black.0); }
-
单元结构体(Unit-Like Structs):无字段,用于不需要数据的类型。 示例:
#![allow(unused)] fn main() { struct AlwaysEqual; let subject = AlwaysEqual; }
2. 实例化结构体(Instantiating Structs)
创建结构体实例时,使用大括号指定每个字段的值。字段顺序不需匹配定义顺序。
-
基本实例化: 示例:
#![allow(unused)] fn main() { let user1 = User { active: true, username: String::from("someusername123"), email: String::from("someone@example.com"), sign_in_count: 1, }; }
-
字段初始化简写(Field Init Shorthand):当参数名与字段名相同时,可省略字段名。 示例(在函数中):
#![allow(unused)] fn main() { fn build_user(email: String, username: String) -> User { User { active: true, username, email, sign_in_count: 1, } } }
-
结构体更新语法(Struct Update Syntax):使用
..
从另一个实例复制剩余字段。 示例:#![allow(unused)] fn main() { let user2 = User { email: String::from("another@example.com"), ..user1 }; }
注意:这会移动
user1
的拥有值(如String
),除非那些字段实现了 Copy trait。 -
访问和更新字段:使用点号
.
访问字段。要更新,需要可变实例(mut
)。 示例:#![allow(unused)] fn main() { let mut user1 = User { /* ... */ }; user1.email = String::from("newemail@example.com"); }
3. 示例程序:使用结构体计算矩形面积
这是一个经典示例,展示如何使用结构体组织数据、借用和计算。
-
定义和实例化:
#[derive(Debug)] // 启用调试打印 struct Rectangle { width: u32, height: u32, } fn main() { let rect1 = Rectangle { width: 30, height: 50, }; println!("rect1 是 {:?}", rect1); // 使用 {:?} 打印调试信息 }
-
更新字段和调试:使用
dbg!
宏调试表达式值。 示例:#![allow(unused)] fn main() { let scale = 2; let rect1 = Rectangle { width: dbg!(30 * scale), // 打印并赋值 60 height: 50, }; dbg!(&rect1); // 调试引用,避免移动所有权 }
-
借用处理:函数借用结构体以避免所有权转移。 示例(计算面积函数):
fn area(rectangle: &Rectangle) -> u32 { rectangle.width * rectangle.height } fn main() { let rect1 = Rectangle { width: 30, height: 50 }; println!("面积: {} 平方像素", area(&rect1)); }
注意:使用
&
借用,确保main
保留所有权。
4. 方法语法(Method Syntax)
方法是与结构体关联的函数,使用 impl
块定义。方法总是以 self
为第一个参数,表示调用实例。
-
定义方法: 使用
impl StructName { }
块。&self
表示不可变借用。 示例:impl Rectangle { fn area(&self) -> u32 { self.width * self.height } } fn main() { let rect1 = Rectangle { width: 30, height: 50 }; println!("面积: {}", rect1.area()); // 调用方法 }
-
可变性和额外参数:
&mut self
:允许修改实例。- 示例(额外参数):
#![allow(unused)] fn main() { impl Rectangle { fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height } } }
-
关联函数(Associated Functions):不带
self
,常用于构造函数。使用::
调用。 示例:#![allow(unused)] fn main() { impl Rectangle { fn square(size: u32) -> Self { Self { width: size, height: size } } } let sq = Rectangle::square(3); }
-
多个 impl 块:允许将方法分散定义,但等价于单个块。
-
关键差异与 OOP:
- Rust 无自动 getter/setter,需要手动定义。
- 方法名可与字段名相同(基于语法区分)。
- 强调借用规则,与 OOP 的封装不同。
- Rust 自动处理引用/解引用,无需
->
操作符。
注意事项
- 所有权:结构体字段若为拥有类型(如
String
),实例移动时会转移所有权。 - 借用:优先使用借用(
&
)以避免不必要的移动。 - 调试:使用
#[derive(Debug)]
注解结构体,以启用{:?}
打印。 - 实践:通过
cargo new
创建项目,在main.rs
中测试这些示例。