外观
第 7 章 宠物收藏册
小螃蟹说
第 4 章的宠物图鉴有个"缺陷":一次只能登记三只宠物,程序一关,宠物们就散了。
这一章我们开一家真正的收藏册:想加几只就加几只,随时查看、按种类找宠物、还能出一份统计报告。宠物们终于有个能一直住下去的家了!
秘诀是三个新朋友:Vec(能长大的收藏柜)、HashMap(贴标签的分类账),还有把 String 好好用起来的各种招数。它们合起来叫集合(collection)——专门管"一堆东西"的类型。
我们还会顺手干一件大事:把第 4 章的 Pet 结构体原样搬过来用。结构体装"一只宠物",集合装"一堆宠物"——两者配合,才叫一个完整的程序。
7.1 预览:我们要做什么
- 添加宠物:想加几只加几只
- 查看所有宠物:整本收藏册一页页翻
- 按种类找宠物:比如"看看我养了几只猫"
- 统计报告:一共有几只?每种几只?
运行结果
text
欢迎来到宠物收藏册!
== 菜单 ==
1. 添加宠物
2. 查看所有宠物
3. 按种类找宠物
4. 统计报告
输入 0 退出
选一个:1
宠物的名字:小咪
宠物的种类:猫
宠物的年龄:2
可爱度(1~10):9
已加入收藏册!现在一共有 1 只宠物。
选一个:1
宠物的名字:旺财
宠物的种类:狗
宠物的年龄:0
可爱度(1~10):7
已加入收藏册!现在一共有 2 只宠物。
选一个:3
想找什么种类的宠物?猫
小咪:2 岁,可爱度 9/10
选一个:4
一共有 2 只宠物。
狗:1 只
猫:1 只
选一个:0
再见!欢迎下次再来玩!这一章你会学到:
| 知识 | 是什么 | 会用在哪儿 |
|---|---|---|
Vec<T> | 能长大的收藏柜 | Vec<Pet> 装下所有宠物 |
push / len | 往柜子里放 / 数一数 | 添加宠物、报告总数 |
iter + enumerate | 一件件看 / 顺便编号 | 翻收藏册、找宠物 |
HashMap | 贴标签的分类账 | 每种宠物有几只 |
entry / or_insert | "没有就放一个新的" | 统计报告的计数字典 |
7.2 动手做
步骤一:新建项目
老规矩,这一章也不用请外部帮手:
bash
cargo new pet_collection
cd pet_collection步骤二:认识收藏柜 Vec
先做一个热身小实验。把 src/main.rs 替换成:
rust
fn main() {
let mut scores: Vec<u32> = Vec::new();
scores.push(9);
scores.push(7);
scores.push(5);
println!("一共有 {} 个分数。", scores.len());
}运行:
运行结果
text
一共有 3 个分数。Vec 读作"维克",是 vector 的缩写,它是能长大的数组。回忆第 1 章的数组 ["石头", "剪刀", "布"]——它的大小是固定的,造出来几个格子就是几个格子。Vec 不一样:它可以慢慢长大,想放几个放几个。
三个新动作:
Vec::new():造一个空柜子。旁边的: Vec<u32>是"柜子里装什么"的标签——<u32>就是"里面是 u32"的意思。这个尖括号叫泛型(generics),我们第 9 章专门学,现在先认个脸熟scores.push(9):把 9 放进柜子,推到队尾。push 就是"放进去"scores.len():问柜子"你现在装了几个?"(length 的缩写)
数组 vs Vec,一句话记住
数组:固定格子的玩具架。Vec:会变长的收藏柜。 数组在第 1 章帮我们存了"石头剪刀布"三个选项;但宠物想加多少只没人知道,这种时候就得用 Vec。
步骤三:收藏柜装满宠物
现在把第 4 章的 Pet 结构体搬过来,并写第一个功能:添加宠物。
把 src/main.rs 替换成:
rust
use std::io;
struct Pet {
name: String,
species: String,
age: u32,
cuteness: u32,
}
fn main() {
let mut pets: Vec<Pet> = Vec::new();
add_pet(&mut pets);
add_pet(&mut pets);
add_pet(&mut pets);
println!("收藏册里有 {} 只宠物。", pets.len());
}
fn add_pet(pets: &mut Vec<Pet>) {
println!();
let name = read_input("宠物的名字:");
let species = read_input("宠物的种类:");
let age = read_number("宠物的年龄:");
let cuteness = read_number("可爱度(1~10):");
pets.push(Pet {
name,
species,
age,
cuteness,
});
println!("已加入收藏册!现在一共有 {} 只宠物。", pets.len());
}
fn read_number(prompt: &str) -> u32 {
loop {
let input = read_input(prompt);
match input.parse() {
Ok(number) => return number,
Err(_) => println!("要输入数字哦。"),
}
}
}
fn read_input(prompt: &str) -> String {
println!("{}", prompt);
let mut input = String::new();
io::stdin().read_line(&mut input).expect("读取输入失败");
input.trim().to_string()
}老朋友和新朋友一起说:
Pet 结构体:和第 4 章一模一样,原样搬来。发现没有?第 4 章我们用了 pet1、pet2、pet3 三个变量——现在有了 Vec<Pet>,多少个都装得下,再也不用 pet4、pet5 了。
add_pet(pets: &mut Vec<Pet>):参数是"借来的、可修改的收藏柜"。&mut 是第 3 章的可变借用——add_pet 要把宠物放进去,所以不但要借,还要能改。
read_number 升级版:第 2 章我们用 .expect()(输错就崩溃),第 5 章我们用 match(输错就提示)。今天升级成"输错就一直问":loop 转圈,parse 成功就 return 出去,失败就再说一遍"要输入数字哦"。这是三个版本的终极形态——以后我们写输入数字,都用这一招。
运行,输三只宠物:
运行结果
text
宠物的名字:小咪
宠物的种类:猫
宠物的年龄:2
可爱度(1~10):9
已加入收藏册!现在一共有 1 只宠物。
宠物的名字:旺财
宠物的种类:狗
宠物的年龄:0
可爱度(1~10):7
已加入收藏册!现在一共有 2 只宠物。
宠物的名字:圆圆
宠物的种类:仓鼠
宠物的年龄:1
可爱度(1~10):5
已加入收藏册!现在一共有 3 只宠物。
收藏册里有 3 只宠物。步骤四:翻翻收藏册
收藏了宠物,得能看。加一个 list_all 函数,并把 main 改成菜单版(这一步先把菜单骨架搭好):
rust
fn main() {
let mut pets: Vec<Pet> = Vec::new();
println!("欢迎来到宠物收藏册!");
loop {
println!();
println!("== 菜单 ==");
println!("1. 添加宠物");
println!("2. 查看所有宠物");
println!("输入 0 退出");
let choice = read_input("选一个:");
match choice.as_str() {
"1" => add_pet(&mut pets),
"2" => list_all(&pets),
"0" => break,
_ => println!("没有这个选项哦。"),
}
}
}在 add_pet 下面加:
rust
fn list_all(pets: &Vec<Pet>) {
println!();
if pets.is_empty() {
println!("收藏册还是空的,先添加一只宠物吧。");
return;
}
for (index, pet) in pets.iter().enumerate() {
println!(
"{}. {} 是 {} 岁的小{},可爱度 {}/10",
index + 1,
pet.name,
pet.age,
pet.species,
pet.cuteness
);
}
}两个新动作:
pets.iter() 是"一件件看"。第 3 章我们说过迭代器:排队窗口,一件一件递出来。iter() 返回一个迭代器,配 for 循环,一只宠物一只宠物地看。
.enumerate() 是"顺便编号"。它让每次递出来的东西变成一对:(编号, 宠物)。这种"一对"在 Rust 里叫元组(tuple)——用小括号装起来的几个值。for (index, pet) in ... 就是"拆开这对:编号给 index,宠物给 pet"。编号从 0 开始,所以打印时 index + 1。
pets.is_empty() 是"是不是空的",空柜子就别翻了,提醒一句就回家。
运行,添加几只再查看:
运行结果
text
欢迎来到宠物收藏册!
== 菜单 ==
1. 添加宠物
2. 查看所有宠物
输入 0 退出
选一个:1
宠物的名字:小咪
宠物的种类:猫
宠物的年龄:2
可爱度(1~10):9
已加入收藏册!现在一共有 1 只宠物。
选一个:2
1. 小咪 是 2 岁的小猫,可爱度 9/10
选一个:0
再见!欢迎下次再来玩!步骤五:按种类找宠物
收藏册越来越大,想找猫,得一只只翻。加一个搜索功能:
rust
fn find_by_species(pets: &Vec<Pet>) {
println!();
let species = read_input("想找什么种类的宠物?");
let mut found = 0;
for pet in pets.iter() {
if pet.species == species {
found += 1;
println!("{}:{} 岁,可爱度 {}/10", pet.name, pet.age, pet.cuteness);
}
}
if found == 0 {
println!("没有找到 {} 呢。", species);
}
}菜单里加上第 3 项:
rust
println!("3. 按种类找宠物");rust
"3" => find_by_species(&pets),搜索的逻辑就是"挨个检查":for 遍历每一只宠物,if pet.species == species 看看种类对不对得上,对得上就打印。found 记个数,一个都没找到就老实告诉你。
小贴士
注意我们比较的是 pet.species == species:pet.species 是 String,species 是 &str。还记得第 1 章猜拳里 choice == "石头" 吗?Rust 很贴心,String 和 &str 可以直接比——这是 String 的"免费功能",背后其实在比较文字内容,不用担心。
步骤六:统计报告(认识 HashMap)
最神的一步来了。我们要统计"每种宠物有几只",比如:猫 3 只,狗 2 只,仓鼠 1 只。
怎么记?可以开一排盒子,每个盒子上贴个标签:盒子"猫"装 3,盒子"狗"装 2……这就是**HashMap**(哈希映射):贴标签的盒子,标签(键)对应内容(值)。
加一个函数:
rust
use std::collections::HashMap;
fn show_stats(pets: &Vec<Pet>) {
println!();
println!("一共有 {} 只宠物。", pets.len());
let mut counts: HashMap<String, u32> = HashMap::new();
for pet in pets.iter() {
let count = counts.entry(pet.species.clone()).or_insert(0);
*count += 1;
}
for (species, count) in &counts {
println!("{}:{} 只", species, count);
}
}别忘了文件最上面加 use std::collections::HashMap;——HashMap 住在标准库的 collections(集合)区,不是默认请进门的,要 use。
一行一行看这个"计数魔法":
rust
let mut counts: HashMap<String, u32> = HashMap::new();一个空账本:标签是 String(种类),记的是 u32(数量)。
rust
let count = counts.entry(pet.species.clone()).or_insert(0);entry 是"找到这个标签的格子"。or_insert(0) 是"没有就放一个新的":如果"猫"这个格子还不存在,就新建一个,装 0。然后不管新旧,都把这个格子交给你。
rust
*count += 1;格子交给你的是一个"遥控器",*count 是"看遥控器指向的格子里的东西",+= 1 就是加一。* 叫解引用(dereference):"顺着遥控器,去拿真东西"。第 14 章我们会和它天天见,现在先混个脸熟。
最后遍历账本,一对一对打印:
rust
for (species, count) in &counts {
println!("{}:{} 只", species, count);
}注意!HashMap 不排队
账本上的格子顺序不固定!可能先打印"狗"再打印"猫",也可能反过来。这是 HashMap 的天性:它是"翻牌子找格子",不是"排队"。所以别指望统计报告的打印顺序固定——内容对就行。
步骤七:组装完整菜单
把菜单补全,main 函数最终长这样:
rust
fn main() {
let mut pets: Vec<Pet> = Vec::new();
println!("欢迎来到宠物收藏册!");
loop {
println!();
println!("== 菜单 ==");
println!("1. 添加宠物");
println!("2. 查看所有宠物");
println!("3. 按种类找宠物");
println!("4. 统计报告");
println!("输入 0 退出");
let choice = read_input("选一个:");
match choice.as_str() {
"1" => add_pet(&mut pets),
"2" => list_all(&pets),
"3" => find_by_species(&pets),
"4" => show_stats(&pets),
"0" => break,
_ => println!("没有这个选项哦。"),
}
}
println!("再见!欢迎下次再来玩!");
}运行,完整地玩一遍:
运行结果
text
欢迎来到宠物收藏册!
== 菜单 ==
1. 添加宠物
2. 查看所有宠物
3. 按种类找宠物
4. 统计报告
输入 0 退出
选一个:1
宠物的名字:小咪
宠物的种类:猫
宠物的年龄:2
可爱度(1~10):9
已加入收藏册!现在一共有 1 只宠物。
选一个:1
宠物的名字:旺财
宠物的种类:狗
宠物的年龄:0
可爱度(1~10):7
已加入收藏册!现在一共有 2 只宠物。
选一个:1
宠物的名字:圆圆
宠物的种类:猫
宠物的年龄:1
可爱度(1~10):5
已加入收藏册!现在一共有 3 只宠物。
选一个:3
想找什么种类的宠物?猫
小咪:2 岁,可爱度 9/10
圆圆:1 岁,可爱度 5/10
选一个:4
一共有 3 只宠物。
狗:1 只
猫:2 只
选一个:0
再见!欢迎下次再来玩!宠物收藏册完工!这一章你完成了最重要的一个进步:程序终于能"攒东西"了。前六章的程序,数据都是一次性的;有了 Vec,数据可以越攒越多,程序才真正开始像"软件"。
下一章,我们要让收藏册学会存档:程序关了,宠物们也不散——这就要用到 Rust 最讲究的错误处理了。
7.3 知识深挖
7.3.1 Vec:会变长的收藏柜
Vec<T> 是 Rust 最常用的集合:一个可以长大的列表。
rust
let mut scores: Vec<u32> = Vec::new(); // 空柜子
scores.push(9); // 放进去(队尾)
scores.push(7);
let total = scores.len(); // 几个?
let first = scores[0]; // 第 1 个(编号从 0 开始)
let empty = scores.is_empty(); // 空的吗?常用招数:
| 方法 | 作用 |
|---|---|
Vec::new() | 造一个空柜子 |
push(值) | 把值放到队尾 |
pop() | 把队尾的值拿出来(柜子里就少一个) |
len() | 有几个 |
is_empty() | 是不是空的 |
scores[i] | 按编号拿(越界会崩溃!) |
scores.get(i) | 按编号拿,但给 Option(越界给 None,不崩溃) |
scores[i] 和 scores.get(i) 的区别特别重要:[i] 拿不到就"砰"地崩溃,get(i) 拿不到就给你一个 None(第 5 章的老朋友)。规矩和 .expect() vs match 一样:确定有,用 [i];可能有,用 get。
Vec::new() 造空柜子时,通常要写明类型 Vec<u32> 或 Vec<Pet>,不然编译器不知道柜子装什么。也有个偷懒写法:
rust
let mut scores = vec![9, 7, 5]; // 一开始就装好三个vec! 宏直接"进货",省得 push 三次。
7.3.2 iter 与 enumerate:一件件看
for 循环不能直接遍历 Vec(那是把它整个拆了),要先用 iter() 借来看:
rust
for pet in pets.iter() {
println!("{}", pet.name);
}iter() 递出来的是一只一只的引用——只借不看,不动收藏册(第 3 章精神)。
enumerate() 是"边看边编号":
rust
for (index, pet) in pets.iter().enumerate() {
println!("{}. {}", index + 1, pet.name);
}(index, pet) 是元组(tuple):用括号装的"一对"。Rust 的元组可以装 2 个、3 个甚至更多值:("小咪", 2, 9)。for 里的 (index, pet) 是在"解包"——把一对拆成两个变量。我们还会经常见到元组,它是 Rust 装"几个相关小值"的轻量方式。
7.3.3 HashMap:贴标签的盒子
HashMap<K, V> 是"键 → 值"的账本:标签(键 K)指向内容(值 V)。
rust
use std::collections::HashMap;
let mut counts = HashMap::new();
counts.insert(String::from("猫"), 3); // 贴标签:猫 → 3
counts.insert(String::from("狗"), 2);
let cats = counts.get("猫"); // 用标签找:Some(&3)insert 是"贴标签放东西"。get 是"按标签找",找到给 Some,找不到给 None——和 Vec 的 get 一个脾气。
最常用的组合拳是计数:
rust
let count = counts.entry(String::from("猫")).or_insert(0);
*count += 1;entry 找格子,or_insert(0) 说"没有就放个 0 进去"。配合 *count += 1,一个完美的计数器就完成了。它是 HashMap 最常用的场景之一:"数一数每个东西出现几次"。
为什么叫"哈希"?
HashMap 找格子不是排队翻的,而是用哈希(hash)算法直接算出格子位置——就像图书馆按书号直接走到书架,不用一本本找。所以它快,但格子顺序乱。这就是"不排队"的原因。
7.3.4 String:活着的文字
String 其实是一种特殊的"能长大的文字柜"。把文字的操作集中复习一下:
rust
let mut text = String::from("你好");
text.push('!'); // 加一个字
text.push_str(",世界"); // 加一串字
let long = format!("{}{}", text, "!"); // 拼出新文字(第 4 章)还有这一章用到的 join:把一串文字用"、"连起来:
rust
let names = vec![String::from("小咪"), String::from("旺财")];
let line = names.join("、"); // "小咪、旺财"join 是"串珠子":珠子(Vec 里的文字)用一根线("、")串起来。
文字还有个老问题要记住:String 是活的(可以变长),&str 是借来看的(第 3 章)。所以把 String 塞进 HashMap 当键,有时要 clone() 一份——比如 pet.species.clone(),不然 HashMap 就把宠物的种类"抢走"了(还记得移动吗?)。要"既用又不给",就得复印一份。
7.3.5 三种集合,一张表
Vec<T> | HashMap<K, V> | 数组 | |
|---|---|---|---|
| 是什么 | 会变长的列表 | 键值对账本 | 固定大小的列表 |
| 拿东西 | 按编号 [i] / get(i) | 按标签 get(键) | 按编号 [i] |
| 放东西 | push(值) | insert(键, 值) | 造的时候定死 |
| 会长大吗 | 会 | 会 | 不会 |
| 顺序 | 排队,按放的顺序 | 不排队 | 排队 |
| 什么时候用 | 一堆同类的值 | 拿标签找值 | 大小固定的一堆值 |
以后看到"一堆东西",先想:要不要按编号拿?→ Vec;要不要按名字/标签拿?→ HashMap;大小永远不会变?→ 数组。
7.4 动脑筋练习
三个练习都是给收藏册加新功能,每个都要动两处:菜单加一项 + 写新函数。
练习一:删除宠物
加一个 remove_pet 功能:输入名字,把那只宠物从收藏册里删掉。
提示:用 enumerate 找到名字相符的编号,存在 Option<usize> 里(还记得第 5 章的 Option 吗?),然后用 pets.remove(编号) 删掉。remove 会把删掉的那只宠物还给你(返回值),可以打印它的名字。
点开看答案
菜单加一项:
rust
println!("5. 删除宠物");match 加一个分支:
rust
"5" => remove_pet(&mut pets),新函数:
rust
fn remove_pet(pets: &mut Vec<Pet>) {
println!();
let name = read_input("要删掉哪只宠物(名字)?");
let mut remove_index: Option<usize> = None;
for (index, pet) in pets.iter().enumerate() {
if pet.name == name {
remove_index = Some(index);
}
}
match remove_index {
Some(index) => {
let removed = pets.remove(index);
println!("已删掉 {}.现在一共有 {} 只宠物。", removed.name, pets.len());
}
None => println!("没有叫 {} 的宠物哦。", name),
}
}思路:先找(enumerate 逐个比对,找到记下编号),再删(remove 按编号删,并拿回被删的宠物)。
小脑筋:如果收藏册里有两只都叫"小咪"的猫,程序会删哪一只?答案是——for 循环一路记下来,remove_index 最后记的是最后一只。如果想删第一只,在 Some 之后立刻 break 出循环就行。想一想怎么写?
练习二:最可爱宠物奖
加一个 most_cute 功能:遍历收藏册,找出可爱度最高的宠物,宣布它是"最可爱的宠物"。
提示:这是第 3 章"擂台赛"的回归——不过这次对手不是句子,而是一柜子宠物。用 enumerate 记下"擂主"的编号和分数,比完了用 &pets[编号] 把冠军拿出来。别忘了处理"收藏册是空的"的情况。
点开看答案
菜单加一项:
rust
println!("5. 最可爱宠物奖");match 加一个分支:
rust
"5" => most_cute(&pets),新函数:
rust
fn most_cute(pets: &Vec<Pet>) {
println!();
if pets.is_empty() {
println!("收藏册是空的,没有宠物参赛。");
return;
}
let mut best_index = 0;
let mut best_cuteness = 0;
for (index, pet) in pets.iter().enumerate() {
if pet.cuteness > best_cuteness {
best_cuteness = pet.cuteness;
best_index = index;
}
}
let winner = &pets[best_index];
println!("最可爱的宠物是 {}!可爱度 {}/10", winner.name, winner.cuteness);
}&pets[best_index] 是按编号拿宠物,& 表示借出来用——比赛结束就还回去,收藏册毫发无损。
小脑筋:如果两只宠物可爱度都是 9,谁会赢?答案:先被遍历到的那只(> 不会换擂主)。想改成"后来的平局者上位"?把 > 换成 >= 试试。
练习三:收藏成员名单
加一个 roll_call 功能:把所有宠物的名字串成一句"收藏成员:小咪、旺财、圆圆"。
提示:先用一个 Vec<String> 把名字都收进来(clone 复印,别把宠物的名字抢走),然后用这一章学的 join("、") 串起来。
点开看答案
菜单加一项:
rust
println!("5. 收藏成员名单");match 加一个分支:
rust
"5" => roll_call(&pets),新函数:
rust
fn roll_call(pets: &Vec<Pet>) {
println!();
if pets.is_empty() {
println!("收藏册还是空的。");
return;
}
let mut names: Vec<String> = Vec::new();
for pet in pets.iter() {
names.push(pet.name.clone());
}
println!("收藏成员:{}", names.join("、"));
}pet.name.clone() 为什么必须 clone?因为 pet.name 是宠物自己的名字(第 3 章的所有权!),把它 push 进 names 就相当于抢走了宠物的名字——编译器会拦住你。clone 复印一份,宠物和名单各有一份,皆大欢喜。
试试把 clone() 去掉,看看编译器怎么教育你。这又是一个"撞墙→明白"的好机会。
7.5 完整代码清单
项目结构:
text
pet_collection/
├── Cargo.toml
└── src/
└── main.rs文件:Cargo.toml
toml
[package]
name = "pet_collection"
version = "0.1.0"
edition = "2024"文件:src/main.rs
rust
use std::collections::HashMap;
use std::io;
struct Pet {
name: String,
species: String,
age: u32,
cuteness: u32,
}
fn main() {
let mut pets: Vec<Pet> = Vec::new();
println!("欢迎来到宠物收藏册!");
loop {
println!();
println!("== 菜单 ==");
println!("1. 添加宠物");
println!("2. 查看所有宠物");
println!("3. 按种类找宠物");
println!("4. 统计报告");
println!("输入 0 退出");
let choice = read_input("选一个:");
match choice.as_str() {
"1" => add_pet(&mut pets),
"2" => list_all(&pets),
"3" => find_by_species(&pets),
"4" => show_stats(&pets),
"0" => break,
_ => println!("没有这个选项哦。"),
}
}
println!("再见!欢迎下次再来玩!");
}
fn add_pet(pets: &mut Vec<Pet>) {
println!();
let name = read_input("宠物的名字:");
let species = read_input("宠物的种类:");
let age = read_number("宠物的年龄:");
let cuteness = read_number("可爱度(1~10):");
pets.push(Pet {
name,
species,
age,
cuteness,
});
println!("已加入收藏册!现在一共有 {} 只宠物。", pets.len());
}
fn list_all(pets: &Vec<Pet>) {
println!();
if pets.is_empty() {
println!("收藏册还是空的,先添加一只宠物吧。");
return;
}
for (index, pet) in pets.iter().enumerate() {
println!(
"{}. {} 是 {} 岁的小{},可爱度 {}/10",
index + 1,
pet.name,
pet.age,
pet.species,
pet.cuteness
);
}
}
fn find_by_species(pets: &Vec<Pet>) {
println!();
let species = read_input("想找什么种类的宠物?");
let mut found = 0;
for pet in pets.iter() {
if pet.species == species {
found += 1;
println!("{}:{} 岁,可爱度 {}/10", pet.name, pet.age, pet.cuteness);
}
}
if found == 0 {
println!("没有找到 {} 呢。", species);
}
}
fn show_stats(pets: &Vec<Pet>) {
println!();
println!("一共有 {} 只宠物。", pets.len());
let mut counts: HashMap<String, u32> = HashMap::new();
for pet in pets.iter() {
let count = counts.entry(pet.species.clone()).or_insert(0);
*count += 1;
}
for (species, count) in &counts {
println!("{}:{} 只", species, count);
}
}
fn read_number(prompt: &str) -> u32 {
loop {
let input = read_input(prompt);
match input.parse() {
Ok(number) => return number,
Err(_) => println!("要输入数字哦。"),
}
}
}
fn read_input(prompt: &str) -> String {
println!("{}", prompt);
let mut input = String::new();
io::stdin().read_line(&mut input).expect("读取输入失败");
input.trim().to_string()
}怎么运行:
bash
cd pet_collection
cargo run运行检查单:
能添加任意多只宠物,总数不断增长
查看所有宠物,有编号、有完整信息
空收藏册查看时,有礼貌的提示而不是崩溃
按种类能找出所有同种宠物,找不到有提示
统计报告能数出每种宠物几只(顺序乱没关系)
输入 abc 当年龄,程序会一直问,不崩溃