====== 第七章 结构体与枚举 ======
===== 7.1 结构体(Struct)=====
结构体是Rust中创建自定义类型的方式,允许你将多个相关的值组合在一起。
==== 定义结构体 ====
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
==== 创建实例 ====
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
==== 访问字段 ====
println!("用户名:{}", user1.username);
println!("邮箱:{}", user1.email);
==== 可变实例 ====
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不允许只将某个字段标记为可变,整个实例必须是可变的。
==== 字段初始化简写 ====
当变量名与字段名相同时,可以简写:
fn build_user(email: String, username: String) -> User {
User {
email, // 等价于 email: email
username, // 等价于 username: username
active: true,
sign_in_count: 1,
}
}
==== 结构体更新语法 ====
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
..user1 // 其余字段从user1获取
};
**注意:** 使用..语法会发生移动,如果user1中有被移动的值,user1将失效。
===== 7.2 元组结构体 =====
没有命名字段的结构体,称为元组结构体:
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
**访问:**
println!("R: {}, G: {}, B: {}", black.0, black.1, black.2);
**用途:**
* 给元组起个有意义的名字
* 区分不同类型的同构数据
===== 7.3 单元结构体 =====
没有任何字段的结构体:
struct AlwaysEqual;
let subject = AlwaysEqual;
**用途:** 用于实现trait而不需要存储数据。
===== 7.4 结构体的方法 =====
==== 定义方法 ====
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
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!("面积是{}平方像素", rect1.area());
}
**&self参数:**
* &self:不可变借用
* &mut self:可变借用
* self:获取所有权(很少使用)
==== 关联函数 ====
不带self参数的函数,通常用于构造函数:
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
// 使用::调用
let sq = Rectangle::square(3);
==== 多个impl块 ====
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
}
}
===== 7.5 枚举(Enum)=====
枚举允许你定义一个类型,它的值可以是几个可能的变体之一。
==== 定义枚举 ====
enum IpAddrKind {
V4,
V6,
}
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
==== 带数据的枚举 ====
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
==== 带命名数据的枚举 ====
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
==== 在枚举上定义方法 ====
impl Message {
fn call(&self) {
match self {
Message::Quit => println!("Quit"),
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
Message::Write(text) => println!("Write: {}", text),
Message::ChangeColor(r, g, b) => println!("Color: R{}, G{}, B{}", r, g, b),
}
}
}
let m = Message::Write(String::from("hello"));
m.call();
===== 7.6 Option枚举 =====
Option是标准库中定义的最常用的枚举:
enum Option {
Some(T),
None,
}
**使用Option:**
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option = None;
**Option与null的区别:**
* Rust没有null
* Option和T是不同的类型
* 必须处理Some和None两种情况
let x: i8 = 5;
let y: Option = Some(5);
// let sum = x + y; // 错误!不能相加
**处理Option:**
fn plus_one(x: Option) -> Option {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
**常用方法:**
let x: Option = Some(5);
// unwrap(危险)
let val = x.unwrap(); // 如果是None会panic
// unwrap_or(安全)
let val = x.unwrap_or(0); // None时返回默认值
// unwrap_or_else
let val = x.unwrap_or_else(|| 0);
// map
let y = x.map(|v| v * 2); // Some(10)
// is_some/is_none
if x.is_some() {
println!("有值");
}
// if let
if let Some(v) = x {
println!("值是{}", v);
}
===== 7.7 Result枚举 =====
Result用于可能失败的操作:
enum Result {
Ok(T),
Err(E),
}
**使用Result:**
use std::fs::File;
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => {
panic!("打开文件失败:{:?}", error);
}
};
**匹配不同错误:**
use std::fs::File;
use std::io::ErrorKind;
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("创建文件失败:{:?}", e),
},
other_error => panic!("打开文件失败:{:?}", other_error),
},
};
**快捷方法:**
// unwrap(危险)
let f = File::open("hello.txt").unwrap();
// expect(带自定义消息)
let f = File::open("hello.txt")
.expect("hello.txt应该存在");
// unwrap_or_else
let f = File::open("hello.txt").unwrap_or_else(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("hello.txt").unwrap_or_else(|error| {
panic!("创建文件失败:{:?}", error);
})
} else {
panic!("打开文件失败:{:?}", error);
}
});
===== 7.8 if let和while let =====
==== if let简化模式匹配 ====
// match写法
let some_value = Some(3);
match some_value {
Some(3) => println!("是三"),
_ => (),
}
// if let写法
if let Some(3) = some_value {
println!("是三");
}
**带else:**
if let Some(3) = some_value {
println!("是三");
} else {
println!("不是三");
}
==== while let ====
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
// 弹出所有元素
while let Some(top) = stack.pop() {
println!("{}", top);
}
===== 练习题 =====
==== 练习题7.1:实现矩形结构体 ====
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn perimeter(&self) -> u32 {
2 * (self.width + self.height)
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width >= other.width && self.height >= other.height
}
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 rect3 = Rectangle { width: 60, height: 45 };
println!("rect1的面积:{}", rect1.area());
println!("rect1的周长:{}", rect1.perimeter());
println!("rect1能容纳rect2:{}", rect1.can_hold(&rect2));
println!("rect1能容纳rect3:{}", rect1.can_hold(&rect3));
let sq = Rectangle::square(10);
println!("正方形面积:{}", sq.area());
}
==== 练习题7.2:实现链表节点 ====
enum List {
Cons(i32, Box),
Nil,
}
use List::{Cons, Nil};
fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
// 遍历链表
fn print_list(list: &List) {
match list {
Cons(val, next) => {
println!("{}", val);
print_list(next);
}
Nil => println!("结束"),
}
}
print_list(&list);
}
==== 练习题7.3:实现扑克牌枚举 ====
#[derive(Debug)]
enum Suit {
Hearts,
Diamonds,
Clubs,
Spades,
}
#[derive(Debug)]
enum Rank {
Ace,
Number(u8),
Jack,
Queen,
King,
}
#[derive(Debug)]
struct Card {
suit: Suit,
rank: Rank,
}
impl Card {
fn value(&self) -> u8 {
match &self.rank {
Rank::Ace => 11,
Rank::Number(n) => *n,
Rank::Jack | Rank::Queen | Rank::King => 10,
}
}
}
fn main() {
let card = Card {
suit: Suit::Hearts,
rank: Rank::Ace,
};
println!("{:?}", card);
println!("牌面值:{}", card.value());
}
==== 练习题7.4:实现安全除法 ====
fn safe_divide(a: f64, b: f64) -> Option {
if b == 0.0 {
None
} else {
Some(a / b)
}
}
fn main() {
match safe_divide(10.0, 2.0) {
Some(result) => println!("结果:{}", result),
None => println!("除数不能为零"),
}
if let Some(result) = safe_divide(10.0, 0.0) {
println!("结果:{}", result);
} else {
println!("除数不能为零");
}
}
===== 本章小结 =====
本章学习了Rust的结构体和枚举:
* **结构体**:命名字段的数据结构
* 命名字段结构体
* 元组结构体
* 单元结构体
* **方法**:使用impl块为类型定义方法
* **枚举**:可以有不同类型数据的变体
* **Option**:表示可能存在或不存在的值
* **Result**:表示可能成功或失败的操作
* **if let/while let**:简化模式匹配的语法糖
结构体和枚举是Rust类型系统的核心,掌握它们对于编写Rust程序至关重要。