第二章:数据结构与集合
本文是个人用于学习Rust时的笔记和记录。基于Rustlings练习。
1. 向量 (Vec) (§8.1)
参考章节:
1.1 向量(Vec)基础
向量是Rust中最常用的集合类型,类似C++的std::vector。存储在堆上,可以动态增长。
rust
// 创建向量
let v: Vec<i32> = Vec::new(); // 空向量
let v = vec![1, 2, 3]; // 使用宏创建并初始化
// 添加元素
let mut v = Vec::new();
v.push(1);
v.push(2);
v.push(3);
// 访问元素
let third = &v[2]; // 索引访问(可能panic)
let third = v.get(2); // 安全访问,返回Option
// 获取长度
println!("Length: {}", v.len());
向量 vs 数组:
| 特性 | 数组 (Array) | 向量 (Vec) |
|---|---|---|
| 存储位置 | 栈 | 堆 |
| 大小 | 固定,编译时确定 | 动态,可增长 |
| 类型 | [T; N] |
Vec<T> |
| 性能 | 更快 | 略慢 |
1.2 迭代器
Rust的迭代器类似C++的STL迭代器,但更安全。
rust
// 不可变迭代器
for element in &v {
println!("{}", element);
}
// 可变迭代器
for element in v.iter_mut() {
*element *= 2; // 通过解引用修改元素
}
// 迭代器适配器
let doubled: Vec<i32> = v.iter().map(|x| x * 2).collect();
常用迭代器方法:
iter():不可变迭代器iter_mut():可变迭代器map():转换每个元素filter():过滤元素collect():收集为集合take():取前N个元素
1.3 练习解析
vecs1.rs - 创建向量
rust
fn array_and_vec() -> ([i32; 4], Vec<i32>) {
let a = [10, 20, 30, 40]; // 数组
let v = vec![10, 20, 30, 40]; // 向量
(a, v)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_array_and_vec_similarity() {
let (a, v) = array_and_vec();
assert_eq!(a, v[..]); // 向量切片与数组比较
}
}
考点:
vec!宏创建向量- 向量切片
v[..]与数组比较
vecs2.rs - 向量迭代与修改
rust
fn vec_loop(mut v: Vec<i32>) -> Vec<i32> {
for element in v.iter_mut() {
*element *= 2; // 通过解引用修改元素
}
v
}
fn vec_map(v: &Vec<i32>) -> Vec<i32> {
v.iter().map(|element| {
element * 2
}).collect() // 收集迭代器结果
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vec_loop() {
let v: Vec<i32> = (1..).filter(|x| x % 2 == 0).take(5).collect();
let ans = vec_loop(v.clone());
assert_eq!(ans, v.iter().map(|x| x * 2).collect::<Vec<i32>>());
}
}
考点:
iter_mut():可变迭代器,可以修改元素*element:解引用修改值- 迭代器链式调用:
filter(),take(),collect()
2. 字符串 (String) (§8.2)
参考章节:
2.1 字符串类型
Rust有两种字符串类型,类似C++的std::string和const char*。
| 类型 | 说明 | 存储位置 | 所有权 |
|---|---|---|---|
String |
可增长字符串 | 堆 | 拥有所有权 |
&str |
字符串切片 | 栈/堆引用 | 借用 |
2.2 创建字符串
rust
let s = String::new(); // 空字符串
let s = String::from("hello"); // 从字面量创建
let s = "hello".to_string(); // 转换
let s = "hello".to_owned(); // 转换
let s = format!("Hello, {}!", name); // 格式化
2.3 字符串操作
rust
let mut s = String::from("Hello");
s.push_str(" world"); // 追加字符串切片
s.push('!'); // 追加字符
s.replace("Hello", "Hi"); // 替换
s.trim(); // 去除首尾空白
s.to_uppercase(); // 转大写
s.to_lowercase(); // 转小写
2.4 字符串切片
rust
let s = String::from("hello world");
let hello = &s[0..5]; // "hello"
let world = &s[6..11]; // "world"
2.5 练习解析
strings1.rs - 创建String
rust
fn current_favorite_color() -> String {
String::from("blue") // 使用String::from创建
}
考点 :String::from() 从字符串字面量创建String
strings2.rs - String与&str转换
rust
fn main() {
let word = String::from("green");
if is_a_color_word(&word) { // 传递String的引用(&str)
println!("That is a color word I know!");
}
}
fn is_a_color_word(attempt: &str) -> bool {
attempt == "green" || attempt == "blue" || attempt == "red"
}
考点:
&word:将String转换为&str- 函数参数使用
&str可以同时接受String和&str
strings3.rs - 字符串操作
rust
fn trim_me(input: &str) -> String {
let a = input.trim_matches(' ');
a.to_string() // &str转换为String
}
fn compose_me(input: &str) -> String {
let mut s = String::from(input);
s = s + " world!"; // 字符串拼接
s
}
fn replace_me(input: &str) -> String {
let s = String::from(input);
let s1 = s.replace("cars", "balloons"); // 替换
s1
}
考点:
trim_matches():去除指定字符.to_string():&str转String+运算符:字符串拼接(会转移所有权).replace():字符串替换
strings4.rs - String与&str辨析
rust
fn string_slice(arg: &str) {
println!("{}", arg);
}
fn string(arg: String) {
println!("{}", arg);
}
fn main() {
string_slice("blue"); // &str
string("red".to_string()); // String
string(String::from("hi")); // String
string("rust is fun!".to_owned()); // String
string("nice weather".into()); // String
string(format!("Interpolation {}", "Station")); // String
string_slice(&String::from("abc")[0..1]); // &str切片
string_slice(" hello there ".trim()); // &str
string("Happy Monday!".to_string().replace("Mon", "Tues")); // String
string("mY sHiFt KeY iS sTiCkY".to_lowercase()); // String
}
考点:
to_string(),to_owned(),into(),String::from()都可以创建Stringformat!()宏创建String- 字符串切片
&s[0..1]是&str .trim()返回&str.replace(),.to_lowercase()返回String
3. 哈希映射 (HashMap) (§8.3)
参考章节:
3.1 HashMap基础
HashMap存储键值对,类似C++的std::unordered_map。
rust
use std::collections::HashMap;
// 创建HashMap
let mut scores: HashMap<String, i32> = HashMap::new();
// 插入键值对
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
// 访问值
let score = scores.get("Blue"); // 返回Option<&i32>
// 遍历
for (key, value) in &scores {
println!("{}: {}", key, value);
}
// 检查键是否存在
if scores.contains_key("Blue") {
// ...
}
// 获取长度
println!("Length: {}", scores.len());
// 获取所有值的总和
let total: i32 = scores.values().sum();
3.2 Entry API
Entry API是HashMap的高级用法,类似C++的try_emplace。
rust
// 如果键不存在,插入默认值
scores.entry(String::from("Blue")).or_insert(10);
// 如果键不存在,插入并返回可变引用
let count = scores.entry(String::from("Blue")).or_insert(0);
*count += 1;
3.3 练习解析
hashmaps1.rs - 创建HashMap
rust
use std::collections::HashMap;
fn fruit_basket() -> HashMap<String, u32> {
let mut basket = HashMap::new();
basket.insert(String::from("banana"), 2);
basket.insert(String::from("apple"), 4);
basket.insert(String::from("mango"), 2);
basket
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn at_least_three_types_of_fruits() {
let basket = fruit_basket();
assert!(basket.len() >= 3);
}
#[test]
fn at_least_five_fruits() {
let basket = fruit_basket();
assert!(basket.values().sum::<u32>() >= 5);
}
}
考点:
HashMap::new()创建HashMap.insert()插入键值对.len()获取长度.values().sum()获取值的总和
hashmaps2.rs - Entry API
rust
use std::collections::HashMap;
#[derive(Hash, PartialEq, Eq)]
enum Fruit {
Apple,
Banana,
Mango,
Lychee,
Pineapple,
}
fn fruit_basket(basket: &mut HashMap<Fruit, u32>) {
let fruit_kinds = vec![
Fruit::Apple,
Fruit::Banana,
Fruit::Mango,
Fruit::Lychee,
Fruit::Pineapple,
];
for fruit in fruit_kinds {
// 如果键不存在,插入默认值10
basket.entry(fruit).or_insert(10);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_given_fruits_are_not_modified() {
let mut basket = get_fruit_basket();
fruit_basket(&mut basket);
assert_eq!(*basket.get(&Fruit::Apple).unwrap(), 4); // 未修改
}
}
考点:
#[derive(Hash, PartialEq, Eq)]:派生宏,使枚举可用作HashMap的键.entry().or_insert():Entry API,键不存在时插入默认值.get()返回Option<&V>,需要.unwrap()获取值*解引用:将&u32转换为u32
hashmaps3.rs - 复杂HashMap操作
rust
use std::collections::HashMap;
struct Team {
goals_scored: u8,
goals_conceded: u8,
}
fn build_scores_table(results: String) -> HashMap<String, Team> {
let mut scores: HashMap<String, Team> = HashMap::new();
for r in results.lines() {
let v: Vec<&str> = r.split(',').collect();
let team_1_name = v[0].to_string();
let team_1_score: u8 = v[2].parse().unwrap();
let team_2_name = v[1].to_string();
let team_2_score: u8 = v[3].parse().unwrap();
// Entry API + 可变引用修改值
let count = scores.entry(team_1_name.clone()).or_insert(Team{goals_scored:0, goals_conceded:0});
count.goals_scored += team_1_score;
count.goals_conceded += team_2_score;
let count = scores.entry(team_2_name.clone()).or_insert(Team{goals_scored:0, goals_conceded:0});
count.goals_scored += team_2_score;
count.goals_conceded += team_1_score;
}
scores
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_scores() {
let scores = build_scores_table(get_results());
let mut keys: Vec<&String> = scores.keys().collect();
keys.sort(); // 排序
assert_eq!(
keys,
vec!["England", "France", "Germany", "Italy", "Poland", "Spain"]
);
}
}
考点:
- 字符串解析:
.split(','),.collect(),.parse().unwrap() .lines():按行分割字符串.keys():获取所有键.sort():排序向量- Entry API + 可变引用:修改已存在的值
常见错误与避坑指南
❌ 错误1:字符串切片边界错误
rust
let s = String::from("hello");
let slice = &s[0..10]; // ❌ panic! 越界
修复:确保切片范围在字符串长度内
❌ 错误2:HashMap键类型错误
rust
let mut map = HashMap::new();
map.insert("key", 1);
map.get("ke"); // ❌ 类型不匹配
修复 :使用 map.get(&"key".to_string()) 或确保类型一致
❌ 错误3:向量索引越界
rust
let v = vec![1, 2, 3];
let x = v[5]; // ❌ panic! 越界
修复 :使用 .get() 方法,返回 Option<&T>
❌ 错误4:字符串拼接所有权转移
rust
let s1 = String::from("hello");
let s2 = s1 + " world";
println!("{}", s1); // ❌ 编译错误!s1已被移动
修复 :使用 format!() 宏或先克隆