learning-rust/part5/enum练习题.md

1651 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Rust 枚举Enum练习题
> 建议先手动写出每道题的答案,再运行代码验证。
## 目录
- [一、基础题:判断正误](#一基础题判断正误)
- [二、填空题:补充代码](#二填空题补充代码)
- [三、找出并修复错误](#三找出并修复错误)
- [四、编程题](#四编程题)
- [五、综合思考题](#五综合思考题)
- [参考答案](#参考答案)
***
## 一、基础题:判断正误
判断以下代码能否通过编译。如果可以,说明原因;如果不能,指出错误原因。
### 题目 1-1
```rust
enum Direction {
North,
South,
East,
West,
}
fn main() {
let d = Direction::North;
println!("{:?}", d);
}
```
> 不能,缺少#\[derive(Debug)]
### 题目 1-2
```rust
enum IpAddrKind {
V4,
V6,
}
struct IpAddr {
kind: IpAddrKind,
address: String,
}
fn main() {
let home = IpAddr {
kind: IpAddrKind::V4,
address: String::from("127.0.0.1"),
};
println!("home kind: {:?}, address: {}", home.kind, home.address);
}
```
> 不能IpAddrKind缺少#\[derive(Debug)]
### 题目 1-3
```rust
enum IpAddr {
V4(String),
V6(String),
}
fn main() {
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
}
```
> 可以通过
### 题目 1-4
```rust
fn main() {
let some_number = Some(5);
let some_string = Some("hello");
let absent_number: Option<i32> = None;
println!("{:?}", some_number);
println!("{:?}", some_string);
println!("{:?}", absent_number);
}
```
> 可以通过
### 题目 1-5
```rust
fn main() {
let x: Option<i32> = Some(5);
let y = x + 1;
println!("{}", y);
}
```
> 不能Option枚举类型不能直接用+需要实现add方法
### 题目 1-6
```rust
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
}
}
fn main() {
println!("{}", value_in_cents(Coin::Quarter));
}
```
> 不能match需要匹配所有可能出现的类型
### 题目 1-7
```rust
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let m = Message::Write(String::from("hello"));
let n = Message::Move { x: 10, y: 20 };
let q = Message::ChangeColor(255, 0, 0);
}
```
> 可以
### 题目 1-8
```rust
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn main() {
let coin = Coin::Dime;
let mut count = 0;
if let Coin::Quarter = coin {
println!("State quarter!");
} else {
count += 1;
}
println!("count: {}", count);
}
```
> 可以
***
## 二、填空题:补充代码
补全下列代码使其能通过编译并达到期望输出。
### 题目 2-1定义简单枚举
```rust
// 填空:定义一个名为 TrafficLight 的枚举,包含 Red、Yellow、Green 三个变体
________ TrafficLight {
Red,
Yellow,
Green,
}
fn main() {
let light = TrafficLight::________;
match light {
TrafficLight::Red => println!("停止"),
TrafficLight::Yellow => println!("减速"),
TrafficLight::Green => println!("通行"),
}
// 期望输出:停止
}
```
```rust
enum TrafficLight {
Red,
Yellow,
Green,
}
fn main() {
let light = TrafficLight::Red;
match light {
TrafficLight::Red => println!("停止"),
TrafficLight::Yellow => println!("减速"),
TrafficLight::Green => println!("通行"),
}
}
```
### 题目 2-2带数据的枚举
```rust
// 填空:定义一个名为 WebEvent 的枚举
// - PageLoad: 无数据
// - KeyPress: 包含一个 char
// - Click: 包含两个 i32x 和 y 坐标)
enum WebEvent {
________,
KeyPress(char),
Click { x: i32, y: i32 },
}
fn main() {
let load = WebEvent::PageLoad;
let key = WebEvent::________('a');
let click = WebEvent::Click { x: 100, y: 200 };
println!("ok");
}
```
```rust
enum WebEvent {
PageLoad,
KeyPress(char),
Click { x: i32, y: i32 },
}
fn main() {
let load = WebEvent::PageLoad;
let key = WebEvent::KeyPress('a');
let click = WebEvent::Click { x: 100, y: 200 };
println!("ok");
}
```
### 题目 2-3Option 的使用
```rust
fn main() {
let v1 = Some(10);
let v2: Option<i32> = ________;
// 填空:使用 match 安全地提取 Option 中的值None 时返回 0
let val1 = match v1 {
Some(n) => n,
None => 0,
};
let val2 = match v2 {
Some(n) => n,
________ => 0,
};
println!("val1: {}, val2: {}", val1, val2);
// 期望输出val1: 10, val2: 0
}
```
```rust
fn main() {
let v1 = Some(10);
let v2: Option<i32> = None;
let val1 = match v1 {
Some(n) => n,
None => 0,
};
let val2 = match v2 {
Some(n) => n,
None => 0,
};
println!("val1: {}, val2: {}", val1, val2);
}
```
### 题目 2-4Result 的使用
```rust
fn divide(a: i32, b: i32) -> ________ {
if b == 0 {
Err(String::from("除数不能为零"))
} else {
________(a / b)
}
}
fn main() {
let result1 = divide(10, 2);
let result2 = divide(10, 0);
// 填空:使用 match 处理 Result
match result1 {
Ok(val) => println!("结果: {}", val),
Err(e) => println!("错误: {}", e),
}
match result2 {
Ok(val) => println!("结果: {}", val),
________ => println!("错误: {}", e),
}
// 期望输出:
// 结果: 5
// 错误: 除数不能为零
}
```
```rust
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("除数不能为零"))
} else {
Ok(a / b)
}
}
fn main() {
let result1 = divide(10, 2);
let result2 = divide(10, 0);
match result1 {
Ok(val) => println!("结果: {}", val),
Err(e) => println!("错误: {}", e),
}
match result2 {
Ok(val) => println!("结果: {}", val),
Err(e) => println!("错误: {}", e),
}
}
```
### 题目 2-5枚举上的方法
```rust
enum Message {
Quit,
Write(String),
Move { x: i32, y: i32 },
}
impl Message {
// 填空:定义一个 call 方法,打印消息内容
fn ________(&self) {
match self {
Message::Quit => println!("退出"),
Message::Write(text) => println!("写入: {}", text),
Message::Move { x, y } => println!("移动到 ({}, {})", x, y),
}
}
}
fn main() {
let m = Message::Write(String::from("你好世界"));
m.call(); // 期望输出:写入: 你好世界
let n = Message::Move { x: 10, y: 20 };
n.call(); // 期望输出:移动到 (10, 20)
}
```
```rust
enum Message {
Quit,
Write(String),
Move { x: i32, y: i32 },
}
impl Message {
fn call(&self) {
match self {
Message::Quit => println!("退出"),
Message::Write(text) => println!("写入: {}", text),
Message::Move { x, y } => println!("移动到 ({}, {})", x, y),
}
}
}
fn main() {
let m = Message::Write(String::from("你好世界"));
m.call();
let n = Message::Move { x: 10, y: 20 };
n.call();
}
```
### 题目 2-6if let 语法
```rust
fn main() {
let config_max = Some(3u8);
// 填空:使用 if let 简化 match
________ = config_max {
println!("最大值为: {}", max);
}
// 等价于以下 match 写法:
// match config_max {
// Some(max) => println!("最大值为: {}", max),
// _ => (),
// }
// 期望输出:最大值为: 3
}
```
```rust
fn main() {
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("最大值为: {}", max);
}
}
```
### 题目 2-7嵌套 Option 处理
```rust
fn main() {
let opt: Option<Option<i32>> = Some(Some(42));
// 填空:使用模式匹配提取最内层的值
match opt {
Some(Some(inner)) => println!("值是: {}", ________),
Some(None) => println!("内层是 None"),
None => println!("外层是 None"),
}
// 期望输出:值是: 42
}
```
```rust
fn main() {
let opt: Option<Option<i32>> = Some(Some(42));
match opt {
Some(Some(inner)) => println!("值是: {}", inner),
Some(None) => println!("内层是 None"),
None => println!("外层是 None"),
}
}
```
### 题目 2-8match 的多种模式
```rust
fn main() {
let x = 3;
// 填空:使用 match 的多模式匹配和范围匹配
match x {
________ => println!("是 1 或 2"),
3..=8 => println!("在 3 到 8 之间"),
_ => println!("其他值"),
}
// 期望输出:在 3 到 8 之间
}
```
```rust
fn main() {
let x = 3;
match x {
1 | 2 => println!("是 1 或 2"),
3..=8 => println!("在 3 到 8 之间"),
_ => println!("其他值"),
}
}
```
***
## 三、找出并修复错误
以下每段代码都有编译错误,请指出错误并写出修正后的代码。
### 题目 3-1
```rust
enum Status {
Active,
Inactive,
}
fn main() {
let s = Status::Active;
println!("{}", s);
}
```
```rust
#[derive(Debug)]
enum Status {
Active,
Inactive,
}
fn main() {
let s = Status::Active;
println!("{:?}", s);
}
```
### 题目 3-2
```rust
fn main() {
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y;
println!("{}", sum);
}
```
### 题目 3-3
```rust
enum Number {
Integer(i32),
Float(f64),
}
fn main() {
let n = Number::Integer(10);
match n {
Number::Integer => println!("整数: {}", 0),
Number::Float => println!("浮点数: {}", 0.0),
}
}
```
### 题目 3-4
```rust
#[derive(Debug)]
enum Coin {
Penny,
Nickel,
Dime,
}
fn main() {
let coin = Coin::Penny;
let val = match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
};
println!("{}", val);
}
```
### 题目 3-5
```rust
enum Action {
Move(i32, i32),
Jump,
}
fn main() {
let action = Action::Move(10, 20);
if let Action::Move(x, y) = action {
println!("移动: x={}, y={}", x, y);
}
// 此处 action 的所有权是否还在?
match action {
Action::Jump => println!("跳跃"),
_ => (),
}
}
```
### 题目 3-6
```rust
enum Shape {
Circle(f64),
Rectangle(f64, f64),
}
fn area(shape: Shape) -> f64 {
match shape {
Shape::Circle(r) => 3.14 * r * r,
Shape::Rectangle(w, h) => w * h,
}
}
fn main() {
let s = Shape::Circle(5.0);
println!("面积: {}", area(s));
println!("面积: {}", area(s)); // 第二次调用
}
```
### 题目 3-7
```rust
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let target = 10;
let result = numbers.iter().find(|&&x| x == target);
println!("找到: {}", result);
}
```
### 题目 3-8
```rust
enum Color {
Rgb(u8, u8, u8),
Named(String),
}
fn main() {
let c = Color::Rgb(255, 0, 0);
let color_str = match c {
Color::Rgb(r, g, b) => format!("rgb({}, {}, {})", r, g, b),
Color::Named(name) => name,
};
println!("{}", color_str);
}
```
***
## 四、编程题
### 题目 4-1文件系统节点
设计一个枚举表示文件系统中的节点(文件或目录),并实现相应方法。
```rust
// 定义枚举 FsNode
// - File: 包含文件名(String)和文件大小(u64)
// - Directory: 包含目录名(String)和子节点列表(Vec<FsNode>)
// 你的代码:定义 FsNode 枚举
impl FsNode {
// 1. 获取节点名称
fn name(&self) -> &str {
// 你的代码
}
// 2. 计算节点占用的总大小
// 文件直接返回大小;目录递归累加所有子节点大小
fn total_size(&self) -> u64 {
// 你的代码
}
// 3. 打印文件树结构(带缩进)
// print_tree(&self, depth: usize)
// 每层缩进 2 个空格
fn print_tree(&self, depth: usize) {
// 你的代码
}
// 4. 统计文件数量(不含目录)
fn file_count(&self) -> usize {
// 你的代码
}
}
fn main() {
let mut root = FsNode::Directory {
name: String::from("/"),
children: vec![
FsNode::File { name: String::from("readme.txt"), size: 1024 },
FsNode::Directory {
name: String::from("src"),
children: vec![
FsNode::File { name: String::from("main.rs"), size: 2048 },
FsNode::File { name: String::from("lib.rs"), size: 4096 },
],
},
FsNode::File { name: String::from("config.toml"), size: 512 },
],
};
root.print_tree(0);
println!("总大小: {} 字节", root.total_size());
println!("文件数量: {}", root.file_count());
// 期望输出:
// / (目录)
// readme.txt (1024 字节)
// src (目录)
// main.rs (2048 字节)
// lib.rs (4096 字节)
// config.toml (512 字节)
// 总大小: 7680 字节
// 文件数量: 3
}
```
### 题目 4-2计算器
使用枚举实现一个简单的命令式计算器。
```rust
// 定义枚举 Operation
// - Add(i32, i32)
// - Subtract(i32, i32)
// - Multiply(i32, i32)
// - Divide(i32, i32)
// 你的代码:定义 Operation 枚举
impl Operation {
// 1. 执行运算并返回 Result<f64, String>
// 除法时检查除数是否为 0
fn execute(&self) -> Result<f64, String> {
// 你的代码
}
// 2. 返回该运算的符号字符串
fn symbol(&self) -> &str {
// 你的代码
}
// 3. 返回该运算的算式描述,如 "3 + 4"
fn describe(&self) -> String {
// 你的代码
}
}
fn main() {
let ops = vec![
Operation::Add(10, 5),
Operation::Subtract(10, 5),
Operation::Multiply(10, 5),
Operation::Divide(10, 2),
Operation::Divide(10, 0),
];
for op in &ops {
print!("{} = ", op.describe());
match op.execute() {
Ok(result) => println!("{}", result),
Err(e) => println!("错误: {}", e),
}
}
// 期望输出:
// 10 + 5 = 15
// 10 - 5 = 5
// 10 * 5 = 50
// 10 / 2 = 5
// 10 / 0 = 错误: 除数不能为零
}
```
### 题目 4-3JSON 值类型
定义一个枚举表示简化的 JSON 值类型。
```rust
// 定义枚举 JsonValue
// - Null
// - Bool(bool)
// - Number(f64)
// - String(String)
// - Array(Vec<JsonValue>)
// - Object: 包含一个 Vec<(String, JsonValue)> 作为键值对
// 你的代码:定义 JsonValue 枚举
impl JsonValue {
// 1. 返回 JSON 类型的名称字符串,如 "Null", "Bool" 等
fn type_name(&self) -> &str {
// 你的代码
}
// 2. 将 JsonValue 转为格式化的字符串(递归实现)
// 格式要求:
// - Null -> "null"
// - Bool(b) -> "true"/"false"
// - Number(n) -> 格式化数字(保留有意义的小数)
// - String(s) -> "\"s\""
// - Array(arr) -> "[元素1, 元素2, ...]"
// - Object(pairs) -> "{\n key: value,\n ...\n}"
fn to_string(&self) -> String {
// 你的代码
}
}
fn main() {
let json = JsonValue::Object(vec![
(String::from("name"), JsonValue::String(String::from("张三"))),
(String::from("age"), JsonValue::Number(25.0)),
(String::from("is_student"), JsonValue::Bool(false)),
(String::from("scores"), JsonValue::Array(vec![
JsonValue::Number(85.5),
JsonValue::Number(92.0),
JsonValue::Number(78.5),
])),
(String::from("address"), JsonValue::Null),
]);
println!("类型: {}", json.type_name());
println!("{}", json.to_string());
// 期望输出(格式大致如下):
// 类型: Object
// {
// name: "张三",
// age: 25,
// is_student: false,
// scores: [85.5, 92, 78.5],
// address: null
// }
}
```
### 题目 4-4状态机
使用枚举实现一个简单的订单状态机。
```rust
// 定义枚举 OrderState
// - Pending: 待支付
// - Paid { amount: f64 }: 已支付,记录支付金额
// - Shipped { tracking_id: String }: 已发货,记录快递单号
// - Delivered: 已签收
// - Cancelled { reason: String }: 已取消,记录取消原因
// 你的代码:定义 OrderState 枚举
impl OrderState {
// 1. 判断订单是否可以取消(只有 Pending 和 Paid 状态可以取消)
fn can_cancel(&self) -> bool {
// 你的代码
}
// 2. 支付订单Pending -> Paid
// 返回 Result<OrderState, String>
// 如果当前状态不是 Pending返回错误
fn pay(self, amount: f64) -> Result<OrderState, String> {
// 你的代码
}
// 3. 发货Paid -> Shipped
// 返回 Result<OrderState, String>
// 如果当前状态不是 Paid返回错误
fn ship(self, tracking_id: String) -> Result<OrderState, String> {
// 你的代码
}
// 4. 签收Shipped -> Delivered
// 返回 Result<OrderState, String>
fn deliver(self) -> Result<OrderState, String> {
// 你的代码
}
// 5. 取消订单,返回新的状态
fn cancel(self, reason: String) -> Result<OrderState, String> {
// 你的代码
}
// 6. 获取状态描述
fn describe(&self) -> String {
// 你的代码
}
}
fn main() {
let order = OrderState::Pending;
println!("初始状态: {}", order.describe());
let order = match order.pay(299.0) {
Ok(state) => state,
Err(e) => { println!("{}", e); return; }
};
println!("支付后: {}", order.describe());
let order = match order.ship(String::from("SF1234567890")) {
Ok(state) => state,
Err(e) => { println!("{}", e); return; }
};
println!("发货后: {}", order.describe());
let order = match order.deliver() {
Ok(state) => state,
Err(e) => { println!("{}", e); return; }
};
println!("签收后: {}", order.describe());
// 测试异常路径:已签收的订单不能再取消
match order.cancel(String::from("不需要了")) {
Ok(_) => println!("取消成功"),
Err(e) => println!("取消失败: {}", e),
}
// 期望输出:
// 初始状态: 待支付
// 支付后: 已支付 299 元
// 发货后: 已发货 (SF1234567890)
// 签收后: 已签收
// 取消失败: 当前状态 已签收 不允许取消
}
```
### 题目 4-5表达式求值器
使用枚举定义一个简单的数学表达式树并实现求值。
```rust
// 定义枚举 Expr
// - Number(f64): 数字字面量
// - Add(Box<Expr>, Box<Expr>): 加法
// - Subtract(Box<Expr>, Box<Expr>): 减法
// - Multiply(Box<Expr>, Box<Expr>): 乘法
// - Divide(Box<Expr>, Box<Expr>): 除法
// - Negate(Box<Expr>): 取负
// 你的代码:定义 Expr 枚举
impl Expr {
// 1. 求值,返回 Result<f64, String>
fn eval(&self) -> Result<f64, String> {
// 你的代码
}
// 2. 返回表达式的中缀表示字符串,包含必要的括号
// 例如:(1 + 2) * 3
fn to_string(&self) -> String {
// 你的代码
}
}
fn main() {
// 构建表达式: (3 + 4) * (10 - 2) / -5
let expr = Expr::Divide(
Box::new(Expr::Multiply(
Box::new(Expr::Add(
Box::new(Expr::Number(3.0)),
Box::new(Expr::Number(4.0)),
)),
Box::new(Expr::Subtract(
Box::new(Expr::Number(10.0)),
Box::new(Expr::Number(2.0)),
)),
)),
Box::new(Expr::Negate(Box::new(Expr::Number(5.0)))),
);
println!("表达式: {}", expr.to_string());
match expr.eval() {
Ok(result) => println!("结果: {}", result),
Err(e) => println!("错误: {}", e),
}
// 期望输出:
// 表达式: ((3 + 4) * (10 - 2)) / (-5)
// 结果: -11.2
}
```
***
## 五、综合思考题
### 题目 5-1
分析以下代码中 `match` 的匹配过程。`some_u8_value` 的值为 `3u8`,在 match 中匹配到了哪条分支Rust 的 `|` 模式和 `..=` 范围模式分别适用于什么场景?
```rust
fn main() {
let some_u8_value = 3u8;
match some_u8_value {
1 => println!("one"),
2 | 3 | 4 => println!("two, three, or four"),
5..=10 => println!("five through ten"),
_ => println!("anything"),
}
}
```
### 题目 5-2
对比 `match`、`if let` 和 `matches!` 宏。在什么场景下分别应该使用哪种?以下三段代码分别适合用哪种方式改写?
```rust
// 场景 A两个分支都需要处理
enum Coin { Penny, Nickel, Dime, Quarter }
fn value(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
// 场景 B只关心一种模式其余忽略
let opt = Some(42);
// 打印 opt 中的值(如果存在)
// 场景 C只需要布尔判断
let x = Some(5);
// 判断 x 是否为 Some 且值大于 3
```
### 题目 5-3
`Option<T>``Result<T, E>` 都用于处理"可能存在或不存在"的情况,但语义不同。请说明:
1. `Option<T>` 适用于什么场景?`Result<T, E>` 适用于什么场景?
2. 如何将 `Option<T>` 转换为 `Result<T, E>`?(写出一段示例代码)
3. 如何将 `Result<T, E>` 转换为 `Option<T>`?(写出一段示例代码)
### 题目 5-4
以下代码中,`msg` 在第一次 `match` 后和第二次 `match` 后分别发生了什么?为什么 Rust 需要 match 的所有权机制?
```rust
#[derive(Debug)]
enum Message {
Write(String),
Quit,
}
fn main() {
let msg = Message::Write(String::from("hello"));
match msg {
Message::Write(s) => println!("写入: {}", s),
Message::Quit => println!("退出"),
}
// println!("{:?}", msg); // 取消注释会怎样?为什么?
}
```
### 题目 5-5
Rust 没有传统的 `null`,而是用 `Option<T>`。分析以下两种设计的优劣:
```rust
// 设计 AC 风格,用特殊值表示"无"
struct User {
name: String,
middle_name: String, // 没有中间名时用空字符串 ""
age: Option<u32>, // 未知年龄时用 None
}
// 设计 B全部使用 Option
struct User {
name: String,
middle_name: Option<String>,
age: Option<u32>,
}
```
1. `middle_name` 在两种设计中分别如何表示"没有中间名"
2. 哪种设计更符合 Rust 的惯用风格?为什么?
3. 如果用户同时有 `Option<String>``Option<String>` 类型的字段,它们各自为 None 的语义可能不同(如"未提供" vs "明确为空"),你会如何设计来区分这些情况?
***
## 参考答案
> 请独立完成再查看答案。
<details>
<summary>点击展开答案</summary>
### 一、基础题
**1-1**:❌ 编译错误。枚举变体不会自动实现任何 trait。需要 `#[derive(Debug)]` 或手动实现 `Debug` 才能使用 `{:?}` 打印。
**1-2**:❌ 编译错误。`IpAddrKind` 没有 derive `Debug`,所以 `{:?}` 无法打印 `home.kind`。需要加上 `#[derive(Debug)]`
**1-3**:✅ 通过编译。枚举变体可以附带不同类型的数据,此例中 `V4``V6` 都附带 `String`
**1-4**:✅ 通过编译。`Option<T>` 是 Rust 标准库预置的枚举,`Some` 和 `None` 可以直接使用,`Option` 实现了 `Debug`。`None` 需要类型标注因为它无法自动推断 `T` 的类型。
**1-5**:❌ 编译错误。`Option<i32>` 和 `i32` 是不同的类型,不能直接相加。需要先用 match 或 `unwrap()` 等方法提取值。
**1-6**:❌ 编译错误。match 必须穷尽所有可能exhaustive。`Coin` 有 4 个变体但只匹配了 3 个,缺少 `Coin::Quarter` 分支。需要补全或添加 `_ =>` 通配分支。
**1-7**:✅ 通过编译。枚举变体可以使用不同的数据绑定方式:无数据(`Quit`)、命名结构体式(`Move { x, y }`)、元组式(`Write(String)`、`ChangeColor(i32, i32, i32)`)。
**1-8**:✅ 通过编译。`if let` 是 `match` 的语法糖,只匹配一种模式并忽略其余。这里匹配失败走 `else` 分支,`count` 变为 1。
### 二、填空题
**2-1**
```rust
enum TrafficLight {
Red,
Yellow,
Green,
}
let light = TrafficLight::Red;
```
**2-2**
```rust
enum WebEvent {
PageLoad,
KeyPress(char),
Click { x: i32, y: i32 },
}
let key = WebEvent::KeyPress('a');
```
**2-3**
```rust
let v2: Option<i32> = None;
let val2 = match v2 {
Some(n) => n,
None => 0,
};
```
**2-4**
```rust
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("除数不能为零"))
} else {
Ok(a / b)
}
}
match result2 {
Ok(val) => println!("结果: {}", val),
Err(e) => println!("错误: {}", e),
}
```
**2-5**
```rust
fn call(&self) {
match self {
Message::Quit => println!("退出"),
Message::Write(text) => println!("写入: {}", text),
Message::Move { x, y } => println!("移动到 ({}, {})", x, y),
}
}
```
**2-6**
```rust
if let Some(max) = config_max {
println!("最大值为: {}", max);
}
```
**2-7**
```rust
match opt {
Some(Some(inner)) => println!("值是: {}", inner),
Some(None) => println!("内层是 None"),
None => println!("外层是 None"),
}
```
**2-8**
```rust
match x {
1 | 2 => println!("是 1 或 2"),
3..=8 => println!("在 3 到 8 之间"),
_ => println!("其他值"),
}
```
### 三、修复错误
**3-1**`Display` trait 未实现。修复:
```rust
#[derive(Debug)]
enum Status { Active, Inactive }
fn main() {
let s = Status::Active;
println!("{:?}", s); // 使用 Debug 格式
}
```
或者手动实现 `Display`
**3-2**`Option<i8>` 和 `i8` 不能直接相加。修复:
```rust
fn main() {
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y.unwrap_or(0);
println!("{}", sum);
}
```
或用 match
```rust
let sum = match y {
Some(val) => x + val,
None => x,
};
```
**3-3**match 分支没有绑定内部的值。修复:
```rust
match n {
Number::Integer(v) => println!("整数: {}", v),
Number::Float(v) => println!("浮点数: {}", v),
}
```
**3-4**match 不穷尽,缺少 `Coin::Dime` 分支。Rust 要求 match 覆盖所有可能。修复:
```rust
let val = match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
// 或使用 _ => 10
};
```
**3-5**:✅ 编译通过。此题是陷阱——实际上能编译通过。`if let` 不会移动所有权(因为类型实现了 Copy 的话)。在这个例子中 `i32` 实现了 Copy`Action::Move` 中的 `x``y` 是 i32 类型,所以 `if let` 只做了解构引用,不影响后续使用。
但如果 Action 包含非 Copy 类型(如 String`if let` 会部分移动,导致后续不能使用 action。例如
```rust
enum Action { Move(i32, String), Jump }
let action = Action::Move(10, String::from("up"));
if let Action::Move(x, s) = action { ... }
// match action { ... } ← 此时编译错误,因为 String 已被移出
```
**3-6**`area(s)` 第一次调用后 `s` 的所有权被移入函数而消费,第二次调用时 `s` 已失效。修复:
-`area` 改为 `area(shape: &Shape) -> f64`(借用),调用时传 `&s`
- 或在第一次调用前 `clone`
```rust
fn area(shape: &Shape) -> f64 {
match shape {
Shape::Circle(r) => 3.14 * r * r,
Shape::Rectangle(w, h) => w * h,
}
}
// 调用: area(&s)
```
**3-7**`find` 返回 `Option<&i32>`,不是 `i32`。修复:
```rust
let result = numbers.iter().find(|&&x| x == target);
match result {
Some(v) => println!("找到: {}", v),
None => println!("未找到"),
}
```
**3-8**:✅ 通过编译。此题是陷阱——实际上编译通过。`format!` 返回 `String``name` 也是 `String`,返回类型一致。`match` 的两个分支都返回 `String`,类型匹配。
### 四、编程题
**4-1**
```rust
enum FsNode {
File { name: String, size: u64 },
Directory { name: String, children: Vec<FsNode> },
}
impl FsNode {
fn name(&self) -> &str {
match self {
FsNode::File { name, .. } => name,
FsNode::Directory { name, .. } => name,
}
}
fn total_size(&self) -> u64 {
match self {
FsNode::File { size, .. } => *size,
FsNode::Directory { children, .. } => {
children.iter().map(|c| c.total_size()).sum()
}
}
}
fn print_tree(&self, depth: usize) {
let indent = " ".repeat(depth);
match self {
FsNode::File { name, size } => {
println!("{}{} ({} 字节)", indent, name, size);
}
FsNode::Directory { name, children } => {
println!("{}{} (目录)", indent, name);
for child in children {
child.print_tree(depth + 1);
}
}
}
}
fn file_count(&self) -> usize {
match self {
FsNode::File { .. } => 1,
FsNode::Directory { children, .. } => {
children.iter().map(|c| c.file_count()).sum()
}
}
}
}
```
**4-2**
```rust
enum Operation {
Add(i32, i32),
Subtract(i32, i32),
Multiply(i32, i32),
Divide(i32, i32),
}
impl Operation {
fn execute(&self) -> Result<f64, String> {
match self {
Operation::Add(a, b) => Ok((a + b) as f64),
Operation::Subtract(a, b) => Ok((a - b) as f64),
Operation::Multiply(a, b) => Ok((a * b) as f64),
Operation::Divide(a, b) => {
if *b == 0 {
Err(String::from("除数不能为零"))
} else {
Ok(*a as f64 / *b as f64)
}
}
}
}
fn symbol(&self) -> &str {
match self {
Operation::Add(..) => "+",
Operation::Subtract(..) => "-",
Operation::Multiply(..) => "*",
Operation::Divide(..) => "/",
}
}
fn describe(&self) -> String {
match self {
Operation::Add(a, b) => format!("{} + {}", a, b),
Operation::Subtract(a, b) => format!("{} - {}", a, b),
Operation::Multiply(a, b) => format!("{} * {}", a, b),
Operation::Divide(a, b) => format!("{} / {}", a, b),
}
}
}
```
**4-3**
```rust
enum JsonValue {
Null,
Bool(bool),
Number(f64),
String(String),
Array(Vec<JsonValue>),
Object(Vec<(String, JsonValue)>),
}
impl JsonValue {
fn type_name(&self) -> &str {
match self {
JsonValue::Null => "Null",
JsonValue::Bool(_) => "Bool",
JsonValue::Number(_) => "Number",
JsonValue::String(_) => "String",
JsonValue::Array(_) => "Array",
JsonValue::Object(_) => "Object",
}
}
fn to_string(&self) -> String {
match self {
JsonValue::Null => String::from("null"),
JsonValue::Bool(b) => b.to_string(),
JsonValue::Number(n) => {
if *n == n.trunc() {
format!("{}", *n as i64)
} else {
n.to_string()
}
}
JsonValue::String(s) => format!("\"{}\"", s),
JsonValue::Array(arr) => {
let items: Vec<String> = arr.iter().map(|v| v.to_string()).collect();
format!("[{}]", items.join(", "))
}
JsonValue::Object(pairs) => {
let items: Vec<String> = pairs
.iter()
.map(|(k, v)| format!(" {}: {}", k, v.to_string()))
.collect();
format!("{{\n{}\n}}", items.join(",\n"))
}
}
}
}
```
**4-4**
```rust
enum OrderState {
Pending,
Paid { amount: f64 },
Shipped { tracking_id: String },
Delivered,
Cancelled { reason: String },
}
impl OrderState {
fn can_cancel(&self) -> bool {
matches!(self, OrderState::Pending | OrderState::Paid { .. })
}
fn pay(self, amount: f64) -> Result<OrderState, String> {
match self {
OrderState::Pending => Ok(OrderState::Paid { amount }),
_ => Err(format!("当前状态 {} 不允许支付", self.describe())),
}
}
fn ship(self, tracking_id: String) -> Result<OrderState, String> {
match self {
OrderState::Paid { .. } => Ok(OrderState::Shipped { tracking_id }),
_ => Err(format!("当前状态 {} 不允许发货", self.describe())),
}
}
fn deliver(self) -> Result<OrderState, String> {
match self {
OrderState::Shipped { .. } => Ok(OrderState::Delivered),
_ => Err(format!("当前状态 {} 不允许签收", self.describe())),
}
}
fn cancel(self, reason: String) -> Result<OrderState, String> {
match self {
OrderState::Pending | OrderState::Paid { .. } => {
Ok(OrderState::Cancelled { reason })
}
_ => Err(format!("当前状态 {} 不允许取消", self.describe())),
}
}
fn describe(&self) -> String {
match self {
OrderState::Pending => String::from("待支付"),
OrderState::Paid { amount } => format!("已支付 {} 元", amount),
OrderState::Shipped { tracking_id } => format!("已发货 ({})", tracking_id),
OrderState::Delivered => String::from("已签收"),
OrderState::Cancelled { reason } => format!("已取消 ({})", reason),
}
}
}
```
**4-5**
```rust
enum Expr {
Number(f64),
Add(Box<Expr>, Box<Expr>),
Subtract(Box<Expr>, Box<Expr>),
Multiply(Box<Expr>, Box<Expr>),
Divide(Box<Expr>, Box<Expr>),
Negate(Box<Expr>),
}
impl Expr {
fn eval(&self) -> Result<f64, String> {
match self {
Expr::Number(n) => Ok(*n),
Expr::Add(a, b) => Ok(a.eval()? + b.eval()?),
Expr::Subtract(a, b) => Ok(a.eval()? - b.eval()?),
Expr::Multiply(a, b) => Ok(a.eval()? * b.eval()?),
Expr::Divide(a, b) => {
let divisor = b.eval()?;
if divisor == 0.0 {
Err(String::from("除数不能为零"))
} else {
Ok(a.eval()? / divisor)
}
}
Expr::Negate(a) => Ok(-a.eval()?),
}
}
fn to_string(&self) -> String {
match self {
Expr::Number(n) => n.to_string(),
Expr::Add(a, b) => format!("({} + {})", a.to_string(), b.to_string()),
Expr::Subtract(a, b) => format!("({} - {})", a.to_string(), b.to_string()),
Expr::Multiply(a, b) => format!("({} * {})", a.to_string(), b.to_string()),
Expr::Divide(a, b) => format!("({} / {})", a.to_string(), b.to_string()),
Expr::Negate(a) => format!("(-{})", a.to_string()),
}
}
}
```
### 五、综合思考题
**5-1**
- `some_u8_value``3u8`,匹配到 `2 | 3 | 4 =>` 分支,输出 "two, three, or four"。
- `|` 模式or pattern适用场景是多个离散值共享同一段处理逻辑`1 | 2 | 3`
- `..=` 范围模式range pattern适用场景是连续的数值范围`1..=100`。只能在 `char` 和数值类型上使用。
**5-2**
- 场景 A使用 `match`。需要穷尽所有分支并针对每种情况返回值,`match` 最清晰且编译器会检查穷尽性。
- 场景 B使用 `if let`
```rust
if let Some(val) = opt {
println!("{}", val);
}
```
- 场景 C使用 `matches!` 宏(单次布尔判断最简洁)。
```rust
if matches!(x, Some(n) if n > 3) {
println!("满足条件");
}
```
- 选型总结:
- `match`:需要穷尽所有模式,或处理多种不同模式时
- `if let`:只关心一种匹配,其余情况忽略
- `matches!`:只需要一个布尔判断结果,不需要提取内部值
**5-3**
1. 适用场景:
- `Option<T>`:值**可能不存在**,但不存在不是"错误"。例如:查找元素可能找不到、配置项可能未设置。
- `Result<T, E>`:操作**可能失败**,失败时需要知道原因。例如:文件读取、网络请求、解析数据等 I/O 操作。
2. `Option<T>``Result<T, E>`
```rust
let opt = Some(42);
let result = opt.ok_or("值不存在");
// result: Result<i32, &str> = Ok(42)
let opt: Option<i32> = None;
let result = opt.ok_or("值不存在");
// result: Result<i32, &str> = Err("值不存在")
```
3. `Result<T, E>``Option<T>`
```rust
let res: Result<i32, &str> = Ok(42);
let opt = res.ok();
// opt: Option<i32> = Some(42)
let res: Result<i32, &str> = Err("出错");
let opt = res.ok();
// opt: Option<i32> = None丢弃错误信息
```
**5-4**
- 第一次 `match` 时:
- `Message::Write(s)` 分支将 `msg` 中的 `String` 绑定到 `s`**值被移动**
- `Message::Quit` 分支没有内部数据,不会移动
- 无论匹配到哪个分支,`match` 会获取所有权
- 取消注释 `println!("{:?}", msg)` 后:编译错误,因为 `msg` 的所有权已在第一次 match 中被消费(特别是当匹配到 Write 分支时 String 被移出)。
- **Rust 需要 match 的所有权机制**:因为 match 分支可能需要将枚举内部的数据绑定到变量并移动出来。如果允许 match 后继续使用,枚举内部可能已被部分移动。解决方案是使用 `match &msg` 借用来只做模式匹配而不移动所有权。
- 正确写法(不使用所有权):
```rust
match &msg {
Message::Write(s) => println!("写入: {}", s),
Message::Quit => println!("退出"),
}
// msg 仍然可用
```
**5-5**
1. "没有中间名"的表示:
- 设计 A空字符串 `""`(特殊值表示"无"
- 设计 B`None`
2. 设计 B 更符合 Rust 惯用风格。原因:
- 类型系统明确区分"有值"和"无值",编译器强制检查,避免空字符串被误用为有效数据
- 避免了"特殊值"的含义歧义(`""` 到底是"没有中间名"还是"中间名叫空字符串"
- 调用方必须显式处理 `None` 情况,减少空值错误
3. 区分"未提供"和"明确为空"的方案——定义自定义枚举:
```rust
enum Maybe<T> {
NotProvided, // 未提供
Empty, // 明确为空
Value(T), // 有值
}
struct User {
name: String,
middle_name: Maybe<String>,
}
```
这样在语义上可以区分三种状态:未提供(表单没填这个字段)、明确为空(用户选了"我没有中间名")、有值。
</details>