第三章:命令行交互与流程控制
教学目标
- 掌握 Rust 流程控制语句(if-else、循环)的使用方法
- 理解 IO 操作与用户交互的实现方式
- 掌握 HashMap 集合类型的基本操作
- 实现任务管理系统的基本交互逻辑
核心知识点
1. 流程控制
if-else 条件表达式
Rust 的 if-else 语句用于根据条件执行不同的代码块,条件必须是布尔类型,且不需要用括号包裹条件表达式。if 表达式可以作为值返回,这是 Rust 的特色之一。
fn main() {
let x = 10;
let y = 20;
// 基本if-else结构
if x > y {
println!("x大于y");
} else if x < y {
println!("x小于y");
} else {
println!("x等于y");
}
// if表达式作为值
let result = if x > y {
"x大于y"
} else {
"x不大于y"
};
println!("结果: {}", result);
}
loop/while 循环与 break/continue
Rust 提供了三种循环类型:loop(无限循环)、while(条件循环)和for(迭代循环)。break用于退出循环,continue用于跳过当前迭代。
fn main() {
// loop循环
let mut count = 0;
let result = loop {
count += 1;
if count == 5 {
break count * 2; // 退出循环并返回值
}
};
println!("loop循环结果: {}", result); // 输出: 10
// while循环
let mut number = 10;
while number > 0 {
println!("{}", number);
number -= 1;
}
// continue示例
println!("1-10中的奇数:");
let mut i = 0;
while i < 10 {
i += 1;
if i % 2 == 0 {
continue; // 跳过偶数
}
println!("{}", i);
}
}
for 循环与迭代器
Rust 的 for 循环主要用于遍历可迭代对象(如数组、向量、Range 等),语法简洁且安全,避免了 C 风格循环的越界风险。
fn main() {
// 遍历数组
let numbers = [10, 20, 30, 40, 50];
for num in numbers {
println!("数字: {}", num);
}
// 遍历Range
println!("1-10的数字:");
for i in 1..11 { // 1到10(不包含11)
println!("{}", i);
}
// 遍历并获取索引
let names = ["Alice", "Bob", "Charlie"];
for (index, name) in names.iter().enumerate() {
println!("索引 {}: {}", index, name);
}
}
2. IO 操作与用户输入
std::io 模块基础
Rust 的 IO 操作主要通过std::io模块实现,该模块提供了处理输入输出的各种工具,包括读取用户输入、文件操作等。
use std::io;
fn main() {
println!("请输入你的名字:");
// 创建标准输入的引用
let stdin = io::stdin();
// 读取一行输入,返回Result类型
let mut name = String::new();
stdin.read_line(&mut name)
.expect("读取输入失败");
// 去除输入中的换行符
let name = name.trim();
println!("你好,{}!", name);
}
BufRead trait 与 read_line 方法
BufRead trait 提供了从输入流中读取数据的方法,read_line用于读取一行输入并存储到String中。
use std::io::{self, BufRead};
fn main() {
// 获取标准输入的句柄并转换为BufRead
let stdin = io::stdin();
let mut handle = stdin.lock();
let mut line = String::new();
// 读取一行输入,成功时返回读取的字节数
match handle.read_line(&mut line) {
Ok(bytes) if bytes == 0 => println!("输入结束"),
Ok(bytes) => println!("读取了 {} 个字节: {}", bytes, line),
Err(e) => println!("读取错误: {}", e),
}
}
输入数据的解析与错误处理
用户输入的内容通常是字符串,需要解析成其他类型(如数字),解析过程中需要处理可能的错误。
use std::io;
fn main() {
println!("请输入一个数字:");
let mut input = String::new();
io::stdin().read_line(&mut input)
.expect("读取输入失败");
// 去除换行符
let input = input.trim();
// 解析为i32类型,返回Result<i32, ParseIntError>
match input.parse::<i32>() {
Ok(num) => println!("你输入的数字是: {}", num),
Err(_) => println!("输入不是有效的数字"),
}
}
3. 集合类型:HashMap 的基本使用
HashMap 的创建与插入
HashMap是 Rust 中基于哈希表实现的键值对集合,需要引入std::collections::HashMap。
use std::collections::HashMap;
fn main() {
// 创建空的HashMap
let mut scores = HashMap::new();
// 插入键值对
scores.insert("Alice", 85);
scores.insert("Bob", 92);
scores.insert("Charlie", 78);
// 另一种创建方式
let team_scores = vec![("Alice", 85), ("Bob", 92), ("Charlie", 78)];
let scores: HashMap<_, _> = team_scores.into_iter().collect();
println!("初始分数: {:?}", scores);
}
HashMap 的查询与更新
可以通过get方法查询值,通过insert方法更新或插入键值对,还可以使用entry API 进行更灵活的更新操作。
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert("Alice", 85);
scores.insert("Bob", 92);
// 查询值
if let Some(score) = scores.get("Alice") {
println!("Alice的分数: {}", score);
}
// 更新值
scores.insert("Alice", 90); // 替换现有值
println!("更新后Alice的分数: {}", scores.get("Alice").unwrap());
// 仅在键不存在时插入
scores.entry("Charlie").or_insert(75);
println!("Charlie的分数: {}", scores.get("Charlie").unwrap());
// 根据现有值更新
scores.entry("Bob").and_modify(|score| *score += 5);
println!("Bob的分数: {}", scores.get("Bob").unwrap());
}
HashMap 的遍历
可以通过iter()方法遍历 HashMap 的键值对,也可以单独遍历键或值。
use std::collections::HashMap;
fn main() {
let scores = HashMap::from([
("Alice", 85),
("Bob", 92),
("Charlie", 78),
]);
// 遍历所有键值对
println!("所有分数:");
for (name, score) in &scores {
println!("{}: {}", name, score);
}
// 遍历所有键
println!("学生名单:");
for name in scores.keys() {
println!("{}", name);
}
// 遍历所有值
println!("分数列表:");
for score in scores.values() {
println!("{}", score);
}
}
项目实战:实现任务管理交互逻辑
1. 定义 TaskManager 结构体
在src目录下创建cli.rs文件,定义TaskManager结构体用于管理任务集合,使用 HashMap 存储任务。
// src/cli.rs
use crate::task::{Task, TaskStatus};
use std::collections::HashMap;
use std::time::SystemTime;
pub struct TaskManager {
tasks: HashMap<u32, Task>,
next_id: u32,
}
impl TaskManager {
// 创建新的TaskManager实例
pub fn new() -> Self {
TaskManager {
tasks: HashMap::new(),
next_id: 1,
}
}
}
2. 实现添加任务功能
在TaskManager中添加add_task方法,用于创建并存储新任务。
// src/cli.rs
impl TaskManager {
// ... 已有代码 ...
// 添加新任务
pub fn add_task(&mut self, title: String, description: String, due_date: SystemTime) {
let task = Task::new(
self.next_id,
title,
description,
due_date,
);
self.tasks.insert(self.next_id, task);
println!("任务已添加,ID: {}", self.next_id);
self.next_id += 1;
}
}
3. 实现列出任务功能
添加list_tasks方法,用于遍历并显示所有任务信息。
// src/cli.rs
impl TaskManager {
// ... 已有代码 ...
// 列出所有任务
pub fn list_tasks(&self) {
if self.tasks.is_empty() {
println!("暂无任务");
return;
}
println!("ID\t状态\t标题\t\t截止日期");
println!("----------------------------------------");
for (_, task) in &self.tasks {
let status = match task.status {
TaskStatus::Todo => "待办",
TaskStatus::InProgress => "进行中",
TaskStatus::Completed => "已完成",
};
// 将SystemTime转换为字符串(简化处理,实际项目中应使用更完善的日期格式)
let due_date = match task.due_date.duration_since(SystemTime::UNIX_EPOCH) {
Ok(dur) => format!("{}", dur.as_secs()),
Err(_) => "未知日期".to_string(),
};
println!("{}\t{}\t{}\t{}", task.id, status, task.title, due_date);
}
}
}
4. 实现简单的命令行菜单
在main.rs中编写命令行交互逻辑,实现菜单显示和用户输入处理。
// src/main.rs
mod task;
mod cli;
use cli::TaskManager;
use task::TaskStatus;
use std::io::{self, BufRead};
use std::time::SystemTime;
fn main() {
let mut manager = TaskManager::new();
let stdin = io::stdin();
let mut input = String::new();
loop {
println!("\n===== RustTask 命令行工具 =====");
println!("1. 添加任务");
println!("2. 列出所有任务");
println!("3. 更新任务状态");
println!("4. 退出");
println!("请输入命令 (1-4):");
// 清空input并读取用户输入
input.clear();
stdin.lock().read_line(&mut input).unwrap();
let command = input.trim().parse::<u32>().unwrap_or(0);
match command {
1 => {
// 添加任务
println!("请输入任务标题:");
let mut title = String::new();
stdin.lock().read_line(&mut title).unwrap();
title = title.trim().to_string();
println!("请输入任务描述:");
let mut description = String::new();
stdin.lock().read_line(&mut description).unwrap();
description = description.trim().to_string();
// 简化处理截止日期,实际项目中应使用更完善的日期解析
let due_date = SystemTime::now() + std::time::Duration::from_secs(86400); // 24小时后
manager.add_task(title, description, due_date);
},
2 => {
// 列出任务
manager.list_tasks();
},
3 => {
// 更新任务状态(简化处理,后续章节完善)
println!("请输入任务ID:");
let mut id_str = String::new();
stdin.lock().read_line(&mut id_str).unwrap();
let task_id = id_str.trim().parse::<u32>().unwrap_or(0);
println!("请输入新状态 (1: 待办, 2: 进行中, 3: 已完成):");
let mut status_str = String::new();
stdin.lock().read_line(&mut status_str).unwrap();
let status = status_str.trim().parse::<u32>().unwrap_or(0);
let new_status = match status {
1 => TaskStatus::Todo,
2 => TaskStatus::InProgress,
3 => TaskStatus::Completed,
_ => {
println!("无效状态");
continue;
}
};
// 后续章节实现更新逻辑
println!("更新任务ID {} 状态为: {:?}", task_id, new_status);
},
4 => {
// 退出程序
println!("感谢使用,再见!");
break;
},
_ => {
println!("无效命令,请重试");
}
}
}
}
5. 编译与测试
编译并运行程序,测试添加任务和列出任务功能:
cargo build
cargo run
程序运行示例
===== RustTask 命令行工具 =====
1. 添加任务
2. 列出所有任务
3. 更新任务状态
4. 退出
请输入命令 (1-4):
1
请输入任务标题:
学习Rust
请输入任务描述:
完成第三章内容
===== RustTask 命令行工具 =====
1. 添加任务
2. 列出所有任务
3. 更新任务状态
4. 退出
请输入命令 (1-4):
2
ID 状态 标题 截止日期
----------------------------------------
1 待办 学习Rust 1687593600
===== RustTask 命令行工具 =====
1. 添加任务
2. 列出所有任务
3. 更新任务状态
4. 退出
请输入命令 (1-4):
4
感谢使用,再见!
实践作业
完善命令行交互,添加删除任务功能并处理用户确认操作,具体要求:
- 在TaskManager中添加remove_task方法,根据 ID 删除任务
- 在命令行菜单中添加删除任务选项(命令 5)
- 实现删除任务时的用户确认功能,避免误操作
- 处理删除不存在任务时的错误情况
- 在main.rs中测试删除任务功能
// 在TaskManager中添加remove_task方法
// 在main.rs中添加删除任务的交互逻辑
fn main() {
// 测试删除任务功能
}
通过完成这个作业,你将进一步巩固流程控制、IO 操作和 HashMap 集合的使用,学习如何实现更完善的命令行交互逻辑。