第三章:命令行交互与流程控制

4 阅读8分钟

第三章:命令行交互与流程控制

教学目标

  • 掌握 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
感谢使用,再见!

实践作业

完善命令行交互,添加删除任务功能并处理用户确认操作,具体要求:

  1. 在TaskManager中添加remove_task方法,根据 ID 删除任务
  1. 在命令行菜单中添加删除任务选项(命令 5)
  1. 实现删除任务时的用户确认功能,避免误操作
  1. 处理删除不存在任务时的错误情况
  1. main.rs中测试删除任务功能
// 在TaskManager中添加remove_task方法
// 在main.rs中添加删除任务的交互逻辑
fn main() {
    // 测试删除任务功能
}

通过完成这个作业,你将进一步巩固流程控制、IO 操作和 HashMap 集合的使用,学习如何实现更完善的命令行交互逻辑。