1234 lines
30 KiB
Markdown
1234 lines
30 KiB
Markdown
# Rust Vector 练习题
|
||
|
||
> 建议先手动写出每道题的答案,再运行代码验证。
|
||
|
||
## 目录
|
||
|
||
- [一、概念辨析题](#一概念辨析题)
|
||
- [二、填空题:补充代码](#二填空题补充代码)
|
||
- [三、找出并修复错误](#三找出并修复错误)
|
||
- [四、编程题](#四编程题)
|
||
- [五、综合思考题](#五综合思考题)
|
||
- [参考答案](#参考答案)
|
||
|
||
---
|
||
|
||
## 一、概念辨析题
|
||
|
||
判断以下说法是否正确,并简要说明原因。
|
||
|
||
### 题目 1-1
|
||
|
||
> `Vec<T>` 只能存储相同类型的数据。
|
||
|
||
### 题目 1-2
|
||
|
||
> 从空 Vec 中 `pop()` 会引发 panic。
|
||
|
||
### 题目 1-3
|
||
|
||
> 以下两种创建 Vec 的方式完全等价,没有性能差异:
|
||
> ```rust
|
||
> let v1 = Vec::new();
|
||
> let v2 = vec![];
|
||
> ```
|
||
|
||
### 题目 1-4
|
||
|
||
> 使用索引 `v[3]` 访问 Vec 元素和使用 `v.get(3)` 的行为完全一致。
|
||
|
||
### 题目 1-5
|
||
|
||
> 对 Vec 进行 `for` 循环遍历会消耗 Vec 的所有权,遍历后 Vec 不再可用。
|
||
|
||
### 题目 1-6
|
||
|
||
> 当 Vec 的容量满了再 `push` 新元素时,Rust 会自动重新分配一块更大的内存并将原数据拷贝过去。
|
||
|
||
### 题目 1-7
|
||
|
||
> `Vec::with_capacity(n)` 创建的 Vec 的长度也为 `n`。
|
||
|
||
### 题目 1-8
|
||
|
||
> 可以在遍历 Vec 的同时向其中添加元素。
|
||
|
||
---
|
||
|
||
## 二、填空题:补充代码
|
||
|
||
补全下列代码使其能通过编译并达到期望输出。
|
||
|
||
### 题目 2-1:基本创建和操作
|
||
|
||
```rust
|
||
fn main() {
|
||
// 填空:创建一个包含元素 1, 2, 3, 4, 5 的 Vec
|
||
let mut v = ________;
|
||
|
||
// 填空:向末尾添加元素 6
|
||
v.________(6);
|
||
|
||
// 填空:移除并返回最后一个元素
|
||
let last = v.________();
|
||
println!("移除的元素是: {}", last);
|
||
|
||
// 填空:获取当前长度
|
||
println!("当前长度: {}", ________);
|
||
|
||
// 填空:获取指定位置元素的值(安全方式)
|
||
match v.________(2) {
|
||
Some(value) => println!("索引 2 的值是: {}", value),
|
||
None => println!("没有该元素"),
|
||
}
|
||
}
|
||
// 期望输出:
|
||
// 移除的元素是: 6
|
||
// 当前长度: 5
|
||
// 索引 2 的值是: 3
|
||
```
|
||
|
||
### 题目 2-2:遍历 Vec
|
||
|
||
```rust
|
||
fn main() {
|
||
let v = vec![10, 20, 30, 40, 50];
|
||
|
||
// 填空:不可变遍历,打印每个元素
|
||
for ________ in ________ {
|
||
print!("{} ", i);
|
||
}
|
||
println!();
|
||
|
||
// 填空:可变遍历,每个元素翻倍
|
||
let mut v2 = vec![1, 2, 3, 4, 5];
|
||
for ________ in ________ {
|
||
*i ________ 2;
|
||
}
|
||
println!("翻倍后: {:?}", v2);
|
||
}
|
||
// 期望输出:
|
||
// 10 20 30 40 50
|
||
// 翻倍后: [2, 4, 6, 8, 10]
|
||
```
|
||
|
||
### 题目 2-3:插入和删除
|
||
|
||
```rust
|
||
fn main() {
|
||
let mut v = vec![1, 2, 4, 5];
|
||
|
||
// 填空:在索引 2 处插入元素 3
|
||
v.________(2, 3);
|
||
|
||
// 填空:删除索引 4 处的元素
|
||
v.________(4);
|
||
// 填空:删除索引 0 处的元素
|
||
v.________(0);
|
||
|
||
println!("结果: {:?}", v);
|
||
}
|
||
// 期望输出:结果: [2, 3, 4]
|
||
```
|
||
|
||
### 题目 2-4:枚举实现不同类型存储
|
||
|
||
```rust
|
||
// 填空:定义一个枚举,包含整数、浮点数、文本三种变体
|
||
________ Value {
|
||
Integer(i32),
|
||
Float(f64),
|
||
________(String),
|
||
}
|
||
|
||
fn main() {
|
||
// 填空:使用宏创建一个包含不同变体的 Vec
|
||
let row = ________;
|
||
Value::Integer(3),
|
||
Value::Float(3.14),
|
||
Value::Text(String::from("Hello")),
|
||
];
|
||
|
||
for v in &row {
|
||
match v {
|
||
Value::Integer(n) => print!("Int:{} ", n),
|
||
Value::Float(f) => print!("Float:{} ", f),
|
||
Value::Text(s) => print!("Text:{} ", s),
|
||
}
|
||
}
|
||
}
|
||
// 期望输出:Int:3 Float:3.14 Text:Hello
|
||
```
|
||
|
||
### 题目 2-5:切片操作
|
||
|
||
```rust
|
||
fn main() {
|
||
let v = vec![1, 2, 3, 4, 5, 6, 7, 8];
|
||
|
||
// 填空:获取 v[1..4] 的切片
|
||
let slice1 = ________;
|
||
println!("slice1: {:?}", slice1);
|
||
|
||
// 填空:获取前 3 个元素的切片
|
||
let slice2 = ________;
|
||
println!("slice2: {:?}", slice2);
|
||
|
||
// 填空:获取从索引 5 到末尾的切片
|
||
let slice3 = ________;
|
||
println!("slice3: {:?}", slice3);
|
||
}
|
||
// 期望输出:
|
||
// slice1: [2, 3, 4]
|
||
// slice2: [1, 2, 3]
|
||
// slice3: [6, 7, 8]
|
||
```
|
||
|
||
### 题目 2-6:容量管理
|
||
|
||
```rust
|
||
fn main() {
|
||
// 填空:预分配容量为 100 的空 Vec
|
||
let mut v: Vec<i32> = Vec::________(100);
|
||
|
||
println!("创建后 - 长度: {},容量: {}", v.len(), v.capacity());
|
||
|
||
for i in 0..50 {
|
||
v.push(i);
|
||
}
|
||
println!("添加 50 个元素后 - 长度: {},容量: {}", v.len(), v.capacity());
|
||
|
||
// 填空:收缩容量以匹配长度(使用合适的方法)
|
||
v.________();
|
||
println!("收缩后 - 长度: {},容量: {}", v.len(), v.capacity());
|
||
}
|
||
```
|
||
|
||
### 题目 2-7:常用方法组合
|
||
|
||
```rust
|
||
fn main() {
|
||
let mut v = vec![5, 2, 8, 1, 9, 3];
|
||
|
||
// 填空:排序(升序)
|
||
v.________();
|
||
|
||
// 填空:检查是否包含某个值
|
||
let has_five = v.________(&5);
|
||
|
||
// 填空:反转顺序
|
||
v.________();
|
||
|
||
println!("排序并反转后: {:?}", v);
|
||
println!("是否包含 5: {}", has_five);
|
||
}
|
||
// 期望输出:
|
||
// 排序并反转后: [9, 8, 5, 3, 2, 1]
|
||
// 是否包含 5: true
|
||
```
|
||
|
||
### 题目 2-8:extend 和 drain
|
||
|
||
```rust
|
||
fn main() {
|
||
let mut v1 = vec![1, 2, 3];
|
||
let v2 = vec![4, 5, 6];
|
||
|
||
// 填空:将 v2 的所有元素追加到 v1
|
||
v1.________(&v2);
|
||
println!("追加后: {:?}", v1);
|
||
|
||
let mut v = vec![10, 20, 30, 40, 50];
|
||
// 填空:移除索引 1..4 的元素,返回被移除元素的迭代器
|
||
let drained: Vec<_> = v.________(1..4).collect();
|
||
println!("被移除的: {:?}", drained);
|
||
println!("剩余的: {:?}", v);
|
||
}
|
||
// 期望输出:
|
||
// 追加后: [1, 2, 3, 4, 5, 6]
|
||
// 被移除的: [20, 30, 40]
|
||
// 剩余的: [10, 50]
|
||
```
|
||
|
||
### 题目 2-9:迭代器适配器
|
||
|
||
```rust
|
||
fn main() {
|
||
let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||
|
||
// 填空:过滤出偶数,映射为平方,收集到新 Vec
|
||
let result: Vec<i32> = v.________()
|
||
.________(|x| x % 2 == 0)
|
||
.________(|x| x * x)
|
||
.collect();
|
||
|
||
println!("偶数的平方: {:?}", result);
|
||
|
||
// 填空:求和
|
||
let sum: i32 = v.iter().________();
|
||
println!("总和: {}", sum);
|
||
}
|
||
// 期望输出:
|
||
// 偶数的平方: [4, 16, 36, 64, 100]
|
||
// 总和: 55
|
||
```
|
||
|
||
---
|
||
|
||
## 三、找出并修复错误
|
||
|
||
以下每段代码都有编译错误,请指出错误并写出修正后的代码。
|
||
|
||
### 题目 3-1
|
||
|
||
```rust
|
||
fn main() {
|
||
let mut v = vec![1, 2, 3];
|
||
let first = &v[0]; // 不可变借用
|
||
v.push(4); // 可变借用
|
||
println!("{}", first); // 使用不可变借用
|
||
}
|
||
```
|
||
|
||
### 题目 3-2
|
||
|
||
```rust
|
||
fn main() {
|
||
let v = vec![1, 2, 3, 4, 5];
|
||
let third = v[10];
|
||
println!("{}", third);
|
||
}
|
||
```
|
||
|
||
### 题目 3-3
|
||
|
||
```rust
|
||
fn main() {
|
||
let mut v = Vec::new();
|
||
v.push("hello");
|
||
v.push(42);
|
||
println!("{:?}", v);
|
||
}
|
||
```
|
||
|
||
### 题目 3-4
|
||
|
||
```rust
|
||
fn main() {
|
||
let mut v = vec![1, 2, 3];
|
||
for i in v.iter_mut() {
|
||
v.push(4); // 同时修改
|
||
}
|
||
println!("{:?}", v);
|
||
}
|
||
```
|
||
|
||
### 题目 3-5
|
||
|
||
```rust
|
||
fn main() {
|
||
let mut v = vec![1, 2, 3, 4, 5];
|
||
for i in 0..v.len() {
|
||
if v[i] % 2 == 0 {
|
||
v.remove(i);
|
||
}
|
||
}
|
||
println!("{:?}", v);
|
||
}
|
||
```
|
||
|
||
### 题目 3-6
|
||
|
||
```rust
|
||
fn main() {
|
||
let v = vec![String::from("hello")];
|
||
let s = &v[0]; // 借用了 v 中的元素
|
||
drop(v); // 显式释放 v
|
||
println!("{}", s); // 使用了 s
|
||
}
|
||
```
|
||
|
||
### 题目 3-7
|
||
|
||
```rust
|
||
fn main() {
|
||
let v = vec![1, 2, 3];
|
||
// 想要获得 [1, 4, 9]
|
||
let squared: Vec<i32> = v.iter().map(|x| x * x);
|
||
println!("{:?}", squared);
|
||
}
|
||
```
|
||
|
||
### 题目 3-8
|
||
|
||
```rust
|
||
fn main() {
|
||
let mut v: Vec<u32> = Vec::new();
|
||
v.push(1);
|
||
let popped = v.pop();
|
||
println!("{:?}", popped); // 期望打印 1
|
||
let popped2 = v.pop(); // Vec 已空
|
||
println!("{}", popped2); // 这行会怎样?
|
||
}
|
||
```
|
||
|
||
### 题目 3-9
|
||
|
||
```rust
|
||
fn main() {
|
||
let v = vec![1, 2, 3, 4, 5];
|
||
for item in v {
|
||
println!("{}", item);
|
||
}
|
||
println!("v 还有 {} 个元素", v.len()); // 再次使用 v
|
||
}
|
||
```
|
||
|
||
### 题目 3-10
|
||
|
||
```rust
|
||
fn main() {
|
||
let mut v = vec![1, 2, 3];
|
||
let slice = &mut v[0..2]; // 可变借用切片
|
||
v.push(4); // 尝试修改原 Vec
|
||
slice[0] = 10;
|
||
println!("{:?}", v);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 四、编程题
|
||
|
||
请在 `main.rs` 中编写以下程序,并实际运行验证。
|
||
|
||
### 题目 4-1:成绩统计分析器
|
||
|
||
编写一个程序,完成以下功能:
|
||
|
||
1. 创建一个包含 10 个学生成绩的 `Vec<i32>`(成绩范围 0~100)
|
||
2. 计算并打印:最高分、最低分、平均分
|
||
3. 找出所有不及格(< 60)的成绩,存入新 Vec 并打印
|
||
4. 判断是否有满分(100 分),打印结果
|
||
5. 将所有成绩从高到低排序后打印
|
||
|
||
**期望输出示例(成绩不同则输出不同):**
|
||
```
|
||
成绩列表: [85, 42, 96, 78, 55, 88, 91, 63, 100, 74]
|
||
最高分: 100
|
||
最低分: 42
|
||
平均分: 77.2
|
||
不及格成绩: [42, 55]
|
||
有满分: 是
|
||
排序后(高到低): [100, 96, 91, 88, 85, 78, 74, 63, 55, 42]
|
||
```
|
||
|
||
### 题目 4-2:斐波那契数列生成器
|
||
|
||
编写一个函数 `fibonacci(n: usize) -> Vec<u64>`,接收项数 `n`,返回一个包含前 `n` 项斐波那契数列的 Vec。
|
||
|
||
- n = 0 时返回空 Vec
|
||
- n = 1 时返回 `[1]`
|
||
- n = 2 时返回 `[1, 1]`
|
||
- n >= 3 时正常生成
|
||
|
||
在 `main` 中调用并打印前 20 项。
|
||
|
||
**期望输出(前 20 项):**
|
||
```
|
||
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
|
||
```
|
||
|
||
### 题目 4-3:购物车模拟
|
||
|
||
编写一个模拟购物车的程序:
|
||
|
||
1. 定义一个 `Item` 结构体,包含 `name: String` 和 `price: f64`
|
||
2. 创建一个购物车 `Vec<Item>`,提供以下功能:
|
||
- `add_item(cart: &mut Vec<Item>, name: &str, price: f64)` — 添加商品
|
||
- `remove_item(cart: &mut Vec<Item>, name: &str)` — 按名称移除商品(只移除第一个匹配项)
|
||
- `total_price(cart: &Vec<Item>) -> f64` — 计算总价
|
||
- `list_items(cart: &Vec<Item>)` — 列出所有商品
|
||
3. 在 `main` 中演示完整流程:添加 5 个商品,列出清单,计算总价,移除一个商品,再次列出清单和计算总价
|
||
|
||
**期望输出示例:**
|
||
```
|
||
购物车清单:
|
||
- 苹果: ¥3.50
|
||
- 香蕉: ¥2.00
|
||
- 牛奶: ¥6.80
|
||
- 面包: ¥5.00
|
||
- 鸡蛋: ¥12.00
|
||
当前总价: ¥29.30
|
||
|
||
移除"香蕉"后:
|
||
购物车清单:
|
||
- 苹果: ¥3.50
|
||
- 牛奶: ¥6.80
|
||
- 面包: ¥5.00
|
||
- 鸡蛋: ¥12.00
|
||
当前总价: ¥27.30
|
||
```
|
||
|
||
### 题目 4-4:单词统计器
|
||
|
||
编写一个程序,统计一段文本中每个单词出现的次数。
|
||
|
||
1. 定义输入文本:`"the quick brown fox jumps over the lazy dog the fox was quick"`
|
||
2. 将文本按空格分割成单词,存入 `Vec<&str>`
|
||
3. 使用一个辅助的 Vec(或使用 `HashMap`,但核心流程用 Vec 实现)统计每个不重复单词及其出现次数
|
||
4. 打印所有不重复单词及其出现次数(按单词字母顺序排序)
|
||
|
||
**提示:**
|
||
- 可以用 `Vec<(String, i32)>` 存储"单词-次数"对
|
||
- 遍历单词 Vec,如果在统计 Vec 中找到该单词则计数+1,否则追加新纪录
|
||
|
||
**期望输出:**
|
||
```
|
||
单词统计结果:
|
||
brown: 1
|
||
dog: 1
|
||
fox: 2
|
||
jumps: 1
|
||
lazy: 1
|
||
over: 1
|
||
quick: 2
|
||
the: 3
|
||
was: 1
|
||
```
|
||
|
||
### 题目 4-5:矩阵转置
|
||
|
||
使用 `Vec<Vec<i32>>` 表示一个二维矩阵,编写一个函数 `transpose(matrix: &Vec<Vec<i32>>) -> Vec<Vec<i32>>` 实现矩阵转置。
|
||
|
||
- 输入:一个 m×n 的矩阵
|
||
- 输出:一个 n×m 的转置矩阵
|
||
- 使用迭代器方法实现,避免使用显式的 C 风格循环
|
||
|
||
在 `main` 中测试:
|
||
```
|
||
原始矩阵:
|
||
[1, 2, 3]
|
||
[4, 5, 6]
|
||
[7, 8, 9]
|
||
|
||
转置后:
|
||
[1, 4, 7]
|
||
[2, 5, 8]
|
||
[3, 6, 9]
|
||
```
|
||
|
||
### 题目 4-6:简易栈(Stack)实现
|
||
|
||
只使用 `Vec` 实现一个 `Stack<T>` 结构体,封装栈操作:
|
||
|
||
- `fn new() -> Self`
|
||
- `fn push(&mut self, item: T)`
|
||
- `fn pop(&mut self) -> Option<T>`
|
||
- `fn peek(&self) -> Option<&T>`
|
||
- `fn is_empty(&self) -> bool`
|
||
- `fn len(&self) -> usize`
|
||
|
||
在 `main` 中测试括号匹配功能:编写函数 `is_balanced(s: &str) -> bool`,利用你实现的 `Stack` 判断字符串中的 `()`、`[]`、`{}` 是否正确匹配。
|
||
|
||
测试用例:
|
||
```
|
||
测试用例:
|
||
"()" -> true
|
||
"()[]{}" -> true
|
||
"(]" -> false
|
||
"([)]" -> false
|
||
"{[]}" -> true
|
||
"(((" -> false
|
||
"" -> true
|
||
```
|
||
|
||
---
|
||
|
||
## 五、综合思考题
|
||
|
||
### 题目 5-1
|
||
|
||
阅读以下代码:
|
||
|
||
```rust
|
||
fn main() {
|
||
let mut v = vec![String::from("Rust"), String::from("is"), String::from("awesome")];
|
||
|
||
let first = &v[0]; // ①
|
||
v.push(String::from("!")); // ②
|
||
println!("{}", first); // ③
|
||
}
|
||
```
|
||
|
||
1. 这段代码能编译吗?为什么?
|
||
2. 如果改成 `let first = &v[0].clone();`,会怎样?为什么?
|
||
3. `v[0]` 和 `v.get(0)` 的区别在哪里?分别适用于什么场景?
|
||
4. 如果 `v` 的类型是 `Vec<i32>`,问题 1 的代码是否能编译?为什么?
|
||
|
||
### 题目 5-2
|
||
|
||
分析以下两种删除 Vec 中所有偶数元素的方式:
|
||
|
||
**方式 A:**
|
||
```rust
|
||
fn remove_evens_a(v: &mut Vec<i32>) {
|
||
let mut i = 0;
|
||
while i < v.len() {
|
||
if v[i] % 2 == 0 {
|
||
v.remove(i);
|
||
} else {
|
||
i += 1;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**方式 B:**
|
||
```rust
|
||
fn remove_evens_b(v: &mut Vec<i32>) {
|
||
v.retain(|x| x % 2 != 0);
|
||
}
|
||
```
|
||
|
||
1. 两种方式的时间复杂度分别是多少?为什么?
|
||
2. 方式 A 中的 `i += 1` 放在 `else` 分支中,如果去掉这个条件(每次循环都 `i += 1`)会发生什么?
|
||
3. `retain` 方法的底层原理是什么?它比手动 `remove` 高效在哪里?
|
||
|
||
### 题目 5-3
|
||
|
||
```rust
|
||
fn first_n_even(n: usize) -> Vec<i32> {
|
||
let mut result = Vec::new();
|
||
let mut num = 2;
|
||
while result.len() < n {
|
||
if num % 2 == 0 {
|
||
result.push(num);
|
||
}
|
||
num += 1;
|
||
}
|
||
result
|
||
}
|
||
```
|
||
|
||
1. 上述代码有没有更好的 Rust 风格写法?请使用迭代器重写。
|
||
2. 如果 `n` 非常大(比如 10000000),从性能角度应该如何优化?(提示:`Vec::with_capacity` 和迭代器)
|
||
|
||
### 题目 5-4
|
||
|
||
阅读以下代码,回答:
|
||
|
||
```rust
|
||
fn append_vector(v: &mut Vec<i32>, other: &mut Vec<i32>) {
|
||
v.append(other);
|
||
}
|
||
|
||
fn main() {
|
||
let mut v1 = vec![1, 2, 3];
|
||
let mut v2 = vec![4, 5, 6];
|
||
|
||
append_vector(&mut v1, &mut v2);
|
||
|
||
println!("v1: {:?}", v1);
|
||
println!("v2: {:?}", v2);
|
||
}
|
||
```
|
||
|
||
1. `v2` 在 `append` 调用后会变成什么?为什么?
|
||
2. `append` 和 `extend` 有什么区别?各适用于什么场景?
|
||
3. 如果希望 `v2` 在追加后保持不变(即"复制追加"),应该使用什么方法?
|
||
|
||
### 题目 5-5
|
||
|
||
假设你正在实现一个游戏中的物品栏系统:
|
||
|
||
```rust
|
||
#[derive(Debug)]
|
||
struct Inventory {
|
||
items: Vec<Item>,
|
||
capacity: usize,
|
||
}
|
||
|
||
#[derive(Debug, Clone)]
|
||
struct Item {
|
||
name: String,
|
||
count: u32,
|
||
}
|
||
|
||
impl Inventory {
|
||
fn new(capacity: usize) -> Self {
|
||
Inventory {
|
||
items: Vec::new(),
|
||
capacity,
|
||
}
|
||
}
|
||
|
||
fn add_item(&mut self, item: Item) -> Result<(), String> {
|
||
// 待实现
|
||
todo!()
|
||
}
|
||
|
||
fn remove_item(&mut self, name: &str, count: u32) -> Result<(), String> {
|
||
// 待实现
|
||
todo!()
|
||
}
|
||
}
|
||
```
|
||
|
||
1. 实现 `add_item`:如果物品已存在则叠加数量;如果背包已满则返回错误信息。注意借用检查器的约束。
|
||
2. 实现 `remove_item`:如果数量足够则减少,如果降至 0 则移除整个条目;如果物品不存在或数量不足则返回错误信息。
|
||
3. 这个设计中有一个潜在的问题:当背包容量已满但要添加的是已有物品时,叠加数量并不需要新增槽位。如何修改 `add_item` 的逻辑来正确处理这种情况?
|
||
|
||
---
|
||
|
||
## 参考答案
|
||
|
||
> 请独立完成再查看答案。
|
||
|
||
<details>
|
||
<summary>点击展开答案</summary>
|
||
|
||
### 一、概念辨析题
|
||
|
||
**1-1**:✅ 正确。`Vec<T>` 是泛型容器,所有元素必须是相同类型 `T`。如果要存储不同类型,需要使用枚举(如 `Vec<MyEnum>`)或 trait 对象(`Vec<Box<dyn MyTrait>>`)。
|
||
|
||
**1-2**:❌ 错误。空 Vec 调用 `pop()` 不会 panic,而是返回 `None`。`pop` 的返回类型是 `Option<T>`,在空 Vec 上返回 `None`。
|
||
|
||
**1-3**:❌ 错误。`Vec::new()` 创建一个容量为 0 的 Vec(不分配堆内存),`vec![]` 同样不分配。两者在这种情况下等价。但如果使用 `vec![1, 2, 3]` 和手动 `new` 后 `push` 三次,`vec![]` 可能预先分配合适的容量,性能略好。
|
||
|
||
**1-4**:❌ 错误。`v[3]` 索引越界时会 **panic**(程序崩溃),而 `v.get(3)` 返回 `Option<&T>`,越界时返回 `None`,不会崩溃。`get` 是安全的访问方式。
|
||
|
||
**1-5**:❌ 错误。`for item in v` 会消耗 Vec 的所有权,但 `for item in &v` 仅不可变借用,`for item in &mut v` 仅可变借用。只有转移所有权的遍历才会使 Vec 之后不可用。
|
||
|
||
**1-6**:✅ 正确。当 `len == capacity` 时再 push,Vec 会自动分配一块更大的内存(通常是当前容量的两倍),将旧元素移动/拷贝到新内存,然后释放旧内存。
|
||
|
||
**1-7**:❌ 错误。`Vec::with_capacity(n)` 创建的 Vec **长度**为 0,**容量**为 n。需要通过 `push` 等方法添加元素后长度才会增加。
|
||
|
||
**1-8**:❌ 错误。在遍历 Vec 时不能同时修改 Vec 本身(添加或删除元素),这违反了借用规则。遍历时持有的是对 Vec 或其元素的借用,而修改需要可变借用,两者不能共存。
|
||
|
||
### 二、填空题
|
||
|
||
**2-1**:
|
||
```rust
|
||
let mut v = vec![1, 2, 3, 4, 5];
|
||
v.push(6);
|
||
let last = v.pop().unwrap();
|
||
println!("当前长度: {}", v.len());
|
||
match v.get(2) {
|
||
```
|
||
|
||
**2-2**:
|
||
```rust
|
||
for i in &v {
|
||
print!("{} ", i);
|
||
}
|
||
// 可变遍历
|
||
for i in &mut v2 {
|
||
*i *= 2;
|
||
}
|
||
```
|
||
|
||
**2-3**:
|
||
```rust
|
||
v.insert(2, 3);
|
||
v.remove(4);
|
||
v.remove(0);
|
||
```
|
||
|
||
**2-4**:
|
||
```rust
|
||
enum Value {
|
||
Integer(i32),
|
||
Float(f64),
|
||
Text(String),
|
||
}
|
||
let row = vec![
|
||
Value::Integer(3),
|
||
Value::Float(3.14),
|
||
Value::Text(String::from("Hello")),
|
||
];
|
||
```
|
||
|
||
**2-5**:
|
||
```rust
|
||
let slice1 = &v[1..4];
|
||
let slice2 = &v[..3];
|
||
let slice3 = &v[5..];
|
||
```
|
||
|
||
**2-6**:
|
||
```rust
|
||
let mut v: Vec<i32> = Vec::with_capacity(100);
|
||
// ... 添加元素后
|
||
v.shrink_to_fit();
|
||
```
|
||
|
||
**2-7**:
|
||
```rust
|
||
v.sort();
|
||
let has_five = v.contains(&5);
|
||
v.reverse();
|
||
```
|
||
|
||
**2-8**:
|
||
```rust
|
||
v1.extend(&v2);
|
||
let drained: Vec<_> = v.drain(1..4).collect();
|
||
```
|
||
|
||
**2-9**:
|
||
```rust
|
||
let result: Vec<i32> = v.iter()
|
||
.filter(|x| *x % 2 == 0)
|
||
.map(|x| x * x)
|
||
.collect();
|
||
let sum: i32 = v.iter().sum();
|
||
```
|
||
|
||
### 三、修复错误
|
||
|
||
**3-1**:不可变借用和可变借用冲突。`first` 是 `v` 的不可变引用,而 `push` 需要 `v` 的可变引用。修复:调整顺序,先使用 `first` 再 `push`,或在 push 前不保留引用。
|
||
```rust
|
||
let mut v = vec![1, 2, 3];
|
||
let first = v[0]; // 直接拷贝值(i32 实现了 Copy)
|
||
v.push(4);
|
||
println!("{}", first);
|
||
```
|
||
对于非 Copy 类型,可以:
|
||
```rust
|
||
let first = v[0].clone(); // 克隆值
|
||
v.push(4);
|
||
println!("{}", first);
|
||
```
|
||
|
||
**3-2**:索引越界,Vec 只有 5 个元素(索引 0~4),访问 `v[10]` 会 panic。修复:
|
||
```rust
|
||
let third = v.get(10); // 返回 None,不会 panic
|
||
match third {
|
||
Some(val) => println!("{}", val),
|
||
None => println!("索引越界"),
|
||
}
|
||
```
|
||
|
||
**3-3**:`Vec::new()` 无法推断类型,需要显式标注。且同一个 Vec 不能混装 `&str` 和 `i32`。修复:使用枚举:
|
||
```rust
|
||
enum Value<'a> {
|
||
Text(&'a str),
|
||
Number(i32),
|
||
}
|
||
let mut v: Vec<Value> = Vec::new();
|
||
v.push(Value::Text("hello"));
|
||
v.push(Value::Number(42));
|
||
```
|
||
|
||
**3-4**:在遍历 Vec 的同时修改 Vec 的大小,违反借用规则。修复:先收集要添加的元素,再 `extend`:
|
||
```rust
|
||
let mut v = vec![1, 2, 3];
|
||
v.extend(vec![4, 4, 4]);
|
||
println!("{:?}", v);
|
||
```
|
||
|
||
**3-5**:一边按索引遍历一边删除元素会导致跳过元素(因为删除后后续元素前移)。修复:使用 `retain` 或从后往前遍历:
|
||
```rust
|
||
let mut v = vec![1, 2, 3, 4, 5];
|
||
v.retain(|x| x % 2 != 0);
|
||
println!("{:?}", v); // [1, 3, 5]
|
||
```
|
||
|
||
**3-6**:`s` 借用了 `v` 中的元素,`drop(v)` 释放 `v` 后 `s` 变成悬垂引用。修复:先 `clone` 再释放:
|
||
```rust
|
||
let v = vec![String::from("hello")];
|
||
let s = v[0].clone(); // 获取独立的所有权
|
||
drop(v);
|
||
println!("{}", s);
|
||
```
|
||
|
||
**3-7**:`map` 返回迭代器,需要 `.collect()` 消费才能得到 Vec。修复:
|
||
```rust
|
||
let squared: Vec<i32> = v.iter().map(|x| x * x).collect();
|
||
```
|
||
|
||
**3-8**:`pop` 返回 `Option<u32>`,不能直接用 `println!("{}", popped2)` 打印(类型不匹配,且值为 `None`)。修复:
|
||
```rust
|
||
println!("{:?}", popped); // Some(1)
|
||
println!("{:?}", popped2); // None
|
||
```
|
||
|
||
**3-9**:`for item in v` 消耗了 `v` 的所有权,之后不能再用 `v`。修复:使用引用遍历:
|
||
```rust
|
||
for item in &v {
|
||
println!("{}", item);
|
||
}
|
||
println!("v 还有 {} 个元素", v.len());
|
||
```
|
||
|
||
**3-10**:`slice` 是 `v` 的可变借用,`push` 需要另一个可变借用,冲突。修复:先操作切片,释放后再 push:
|
||
```rust
|
||
let mut v = vec![1, 2, 3];
|
||
{
|
||
let slice = &mut v[0..2];
|
||
slice[0] = 10;
|
||
} // slice 的借用在此结束
|
||
v.push(4);
|
||
println!("{:?}", v); // [10, 2, 3, 4]
|
||
```
|
||
|
||
### 四、编程题
|
||
|
||
**4-1 成绩统计分析器:**
|
||
|
||
```rust
|
||
fn main() {
|
||
let scores = vec![85, 42, 96, 78, 55, 88, 91, 63, 100, 74];
|
||
println!("成绩列表: {:?}", scores);
|
||
|
||
let max = scores.iter().max().unwrap();
|
||
let min = scores.iter().min().unwrap();
|
||
let avg = scores.iter().sum::<i32>() as f64 / scores.len() as f64;
|
||
|
||
println!("最高分: {}", max);
|
||
println!("最低分: {}", min);
|
||
println!("平均分: {:.1}", avg);
|
||
|
||
let failing: Vec<i32> = scores.iter().filter(|&&s| s < 60).copied().collect();
|
||
println!("不及格成绩: {:?}", failing);
|
||
|
||
let has_perfect = scores.contains(&100);
|
||
println!("有满分: {}", if has_perfect { "是" } else { "否" });
|
||
|
||
let mut sorted = scores.clone();
|
||
sorted.sort_by(|a, b| b.cmp(a));
|
||
println!("排序后(高到低): {:?}", sorted);
|
||
}
|
||
```
|
||
|
||
**4-2 斐波那契数列生成器:**
|
||
|
||
```rust
|
||
fn fibonacci(n: usize) -> Vec<u64> {
|
||
match n {
|
||
0 => vec![],
|
||
1 => vec![1],
|
||
_ => {
|
||
let mut fib = vec![1, 1];
|
||
for _ in 2..n {
|
||
let next = fib[fib.len() - 1] + fib[fib.len() - 2];
|
||
fib.push(next);
|
||
}
|
||
fib
|
||
}
|
||
}
|
||
}
|
||
|
||
fn main() {
|
||
let fib = fibonacci(20);
|
||
println!("{:?}", fib);
|
||
}
|
||
```
|
||
|
||
**4-3 购物车模拟:**
|
||
|
||
```rust
|
||
struct Item {
|
||
name: String,
|
||
price: f64,
|
||
}
|
||
|
||
fn add_item(cart: &mut Vec<Item>, name: &str, price: f64) {
|
||
cart.push(Item {
|
||
name: name.to_string(),
|
||
price,
|
||
});
|
||
}
|
||
|
||
fn remove_item(cart: &mut Vec<Item>, name: &str) {
|
||
if let Some(pos) = cart.iter().position(|item| item.name == name) {
|
||
cart.remove(pos);
|
||
}
|
||
}
|
||
|
||
fn total_price(cart: &Vec<Item>) -> f64 {
|
||
cart.iter().map(|item| item.price).sum()
|
||
}
|
||
|
||
fn list_items(cart: &Vec<Item>) {
|
||
for item in cart {
|
||
println!(" - {}: ¥{:.2}", item.name, item.price);
|
||
}
|
||
}
|
||
|
||
fn main() {
|
||
let mut cart = Vec::new();
|
||
|
||
add_item(&mut cart, "苹果", 3.50);
|
||
add_item(&mut cart, "香蕉", 2.00);
|
||
add_item(&mut cart, "牛奶", 6.80);
|
||
add_item(&mut cart, "面包", 5.00);
|
||
add_item(&mut cart, "鸡蛋", 12.00);
|
||
|
||
println!("购物车清单:");
|
||
list_items(&cart);
|
||
println!("当前总价: ¥{:.2}", total_price(&cart));
|
||
|
||
remove_item(&mut cart, "香蕉");
|
||
|
||
println!("\n移除\"香蕉\"后:");
|
||
println!("购物车清单:");
|
||
list_items(&cart);
|
||
println!("当前总价: ¥{:.2}", total_price(&cart));
|
||
}
|
||
```
|
||
|
||
**4-4 单词统计器:**
|
||
|
||
```rust
|
||
fn main() {
|
||
let text = "the quick brown fox jumps over the lazy dog the fox was quick";
|
||
let words: Vec<&str> = text.split_whitespace().collect();
|
||
|
||
let mut stats: Vec<(String, i32)> = Vec::new();
|
||
|
||
for word in &words {
|
||
if let Some(pos) = stats.iter().position(|(w, _)| w == word) {
|
||
stats[pos].1 += 1;
|
||
} else {
|
||
stats.push((word.to_string(), 1));
|
||
}
|
||
}
|
||
|
||
stats.sort_by(|a, b| a.0.cmp(&b.0));
|
||
|
||
println!("单词统计结果:");
|
||
for (word, count) in &stats {
|
||
println!(" {}: {}", word, count);
|
||
}
|
||
}
|
||
```
|
||
|
||
**4-5 矩阵转置:**
|
||
|
||
```rust
|
||
fn transpose(matrix: &Vec<Vec<i32>>) -> Vec<Vec<i32>> {
|
||
if matrix.is_empty() {
|
||
return vec![];
|
||
}
|
||
|
||
let rows = matrix.len();
|
||
let cols = matrix[0].len();
|
||
|
||
(0..cols)
|
||
.map(|col| {
|
||
(0..rows)
|
||
.map(|row| matrix[row][col])
|
||
.collect()
|
||
})
|
||
.collect()
|
||
}
|
||
|
||
fn main() {
|
||
let matrix = vec![
|
||
vec![1, 2, 3],
|
||
vec![4, 5, 6],
|
||
vec![7, 8, 9],
|
||
];
|
||
|
||
println!("原始矩阵:");
|
||
for row in &matrix {
|
||
println!("{:?}", row);
|
||
}
|
||
|
||
let transposed = transpose(&matrix);
|
||
|
||
println!("\n转置后:");
|
||
for row in &transposed {
|
||
println!("{:?}", row);
|
||
}
|
||
}
|
||
```
|
||
|
||
**4-6 简易栈实现:**
|
||
|
||
```rust
|
||
struct Stack<T> {
|
||
data: Vec<T>,
|
||
}
|
||
|
||
impl<T> Stack<T> {
|
||
fn new() -> Self {
|
||
Stack { data: Vec::new() }
|
||
}
|
||
|
||
fn push(&mut self, item: T) {
|
||
self.data.push(item);
|
||
}
|
||
|
||
fn pop(&mut self) -> Option<T> {
|
||
self.data.pop()
|
||
}
|
||
|
||
fn peek(&self) -> Option<&T> {
|
||
self.data.last()
|
||
}
|
||
|
||
fn is_empty(&self) -> bool {
|
||
self.data.is_empty()
|
||
}
|
||
|
||
fn len(&self) -> usize {
|
||
self.data.len()
|
||
}
|
||
}
|
||
|
||
fn is_balanced(s: &str) -> bool {
|
||
let mut stack = Stack::new();
|
||
|
||
for ch in s.chars() {
|
||
match ch {
|
||
'(' | '[' | '{' => stack.push(ch),
|
||
')' => {
|
||
if stack.pop() != Some('(') {
|
||
return false;
|
||
}
|
||
}
|
||
']' => {
|
||
if stack.pop() != Some('[') {
|
||
return false;
|
||
}
|
||
}
|
||
'}' => {
|
||
if stack.pop() != Some('{') {
|
||
return false;
|
||
}
|
||
}
|
||
_ => {}
|
||
}
|
||
}
|
||
|
||
stack.is_empty()
|
||
}
|
||
|
||
fn main() {
|
||
let tests = [
|
||
("()", true),
|
||
("()[]{}", true),
|
||
("(]", false),
|
||
("([)]", false),
|
||
("{[]}", true),
|
||
("(((", false),
|
||
("", true),
|
||
];
|
||
|
||
println!("括号匹配测试:");
|
||
for (input, expected) in tests {
|
||
let result = is_balanced(input);
|
||
let status = if result == expected { "✓" } else { "✗" };
|
||
println!(" {} \"{}\" -> {} (期望: {})", status, input, result, expected);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 五、综合思考题
|
||
|
||
**5-1**:
|
||
|
||
1. 不能编译。`first` 是 `v[0]` 的不可变引用,而 `v.push()` 需要可变引用。即便 `push` 触发的内存重分配不影响索引 0 的元素(逻辑上 String 的数据在堆上),但 Rust 的借用规则在编译期不能精确判断这一点,因此直接拒绝在持有不可变引用时进行可变操作。
|
||
|
||
2. `v[0].clone()` 会创建一个新的 `String`,`first` 拥有这个新 String 的所有权,不再依赖 `v`。因此 `v.push()` 不受影响,可以编译。
|
||
|
||
3. `v[0]` 返回 `&T`,越界时 panic;`v.get(0)` 返回 `Option<&T>`,越界时返回 `None`。`v[0]` 适用于确定索引有效的场景,`v.get(0)` 适用于索引可能越界的场景。
|
||
|
||
4. 可以编译。因为 `i32` 实现了 `Copy` trait,`let first = &v[0]` 在后续使用中,由于 `first` 是 `&i32`,`println!` 打印时需要解引用为 `i32` 值。但关键是将 `let first = &v[0]` 改成 `let first = v[0]` 后,`first` 是 `i32` 值(Copy),与 `v` 无关,更安全。使用 `&v[0]` 仍然会有借用冲突。
|
||
|
||
**5-2**:
|
||
|
||
1. 方式 A 的时间复杂度是 O(n²),因为每次 `remove(i)` 都需要将后续元素向前移动一位。方式 B 的 `retain` 是 O(n),内部使用一次遍历 + 一次批量移动实现。
|
||
|
||
2. 如果每次都 `i += 1`,删除偶数元素后索引不会重新检查当前位置(因为后续元素前移了),会**跳过**紧随其后的元素。例如 `[2, 2, 1]`,删除索引 0 的元素 2 后,数组变为 `[2, 1]`,i 变成 1,第二个 2 被跳过。
|
||
|
||
3. `retain` 的底层原理类似于"双指针":慢指针 `j` 指向下一个要保留的位置,快指针 `i` 遍历所有元素。当元素满足保留条件时,移动到 `j` 位置并使 `j++`;否则跳过。遍历完成后截断到 `j`。避免了每次删除都移动后续元素的 O(n²) 开销。
|
||
|
||
**5-3**:
|
||
|
||
1. 迭代器重写:
|
||
```rust
|
||
fn first_n_even(n: usize) -> Vec<i32> {
|
||
(1..).filter(|x| x % 2 == 0).take(n).collect()
|
||
}
|
||
```
|
||
|
||
2. 优化:
|
||
```rust
|
||
fn first_n_even_optimized(n: usize) -> Vec<i32> {
|
||
let mut result = Vec::with_capacity(n); // 预分配
|
||
(1..).filter(|x| x % 2 == 0).take(n).for_each(|x| result.push(x));
|
||
result
|
||
}
|
||
// 或者更简单的:从 2 开始每次加 2
|
||
fn first_n_even_optimized(n: usize) -> Vec<i32> {
|
||
(2..).step_by(2).take(n).collect()
|
||
}
|
||
```
|
||
使用 `Vec::with_capacity(n)` 预分配可以避免多次重分配的开销。
|
||
|
||
**5-4**:
|
||
|
||
1. `v2` 变成空 Vec `[]`。因为 `append` 方法将所有元素**移动**到目标 Vec,并清空源 Vec。这是 O(1) 时间,只交换了指针。
|
||
|
||
2. 区别:
|
||
- `append`:移动所有元素,源 Vec 被清空。适用于需要转移所有权的场景。
|
||
- `extend`:从迭代器克隆/复制元素到目标 Vec,源数据保持不变。适用于需要保留源的场景。
|
||
- 性能上 `append` 只需交换内部指针,是 O(1);`extend` 需要逐个复制,是 O(n)。
|
||
|
||
3. 使用 `extend` 或 `clone`:
|
||
```rust
|
||
v1.extend_from_slice(&v2); // v2 保持不变
|
||
// 或
|
||
v1.extend(v2.iter().cloned()); // 迭代器方式
|
||
```
|
||
|
||
**5-5**:
|
||
|
||
```rust
|
||
fn add_item(&mut self, item: Item) -> Result<(), String> {
|
||
// 先检查是否有同名物品
|
||
if let Some(existing) = self.items.iter_mut().find(|i| i.name == item.name) {
|
||
existing.count += item.count;
|
||
return Ok(());
|
||
}
|
||
|
||
// 新增物品,检查容量
|
||
if self.items.len() >= self.capacity {
|
||
return Err(format!("背包已满({}/{})", self.items.len(), self.capacity));
|
||
}
|
||
|
||
self.items.push(item);
|
||
Ok(())
|
||
}
|
||
|
||
fn remove_item(&mut self, name: &str, count: u32) -> Result<(), String> {
|
||
let pos = self
|
||
.items
|
||
.iter()
|
||
.position(|i| i.name == name)
|
||
.ok_or(format!("物品「{}」不存在", name))?;
|
||
|
||
if self.items[pos].count < count {
|
||
return Err(format!(
|
||
"物品「{}」数量不足(需要 {},当前 {})",
|
||
name, count, self.items[pos].count
|
||
));
|
||
}
|
||
|
||
self.items[pos].count -= count;
|
||
if self.items[pos].count == 0 {
|
||
self.items.remove(pos);
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
```
|
||
|
||
关键点是先检查是否可以叠加已有物品(不需要新槽位),再检查容量限制。这样当物品已存在时,即使背包"已满"也能正确叠加数量。
|
||
|
||
</details>
|