556 lines
12 KiB
Markdown
556 lines
12 KiB
Markdown
# Rust 所有权练习题
|
||
|
||
> 建议先手动写出每道题的答案,再运行代码验证。
|
||
|
||
## 一、基础题:判断正误
|
||
|
||
判断以下代码能否通过编译。如果可以,说明原因;如果不能,指出错误原因。
|
||
|
||
### 题目 1-1
|
||
|
||
```rust
|
||
fn main() {
|
||
let s = String::from("hello");
|
||
let t = s;
|
||
println!("{}", s);
|
||
}
|
||
```
|
||
|
||
### 题目 1-2
|
||
|
||
```rust
|
||
fn main() {
|
||
let x = 42;
|
||
let y = x;
|
||
println!("x = {}, y = {}", x, y);
|
||
}
|
||
```
|
||
|
||
### 题目 1-3
|
||
|
||
```rust
|
||
fn main() {
|
||
let s = String::from("rust");
|
||
let t = s.clone();
|
||
println!("s = {}, t = {}", s, t);
|
||
}
|
||
```
|
||
|
||
### 题目 1-4
|
||
|
||
```rust
|
||
fn main() {
|
||
let s1 = String::from("hello");
|
||
let s2 = &s1;
|
||
let s3 = &s1;
|
||
println!("{} {} {}", s1, s2, s3);
|
||
}
|
||
```
|
||
|
||
### 题目 1-5
|
||
|
||
```rust
|
||
fn main() {
|
||
let mut s = String::from("hello");
|
||
let r1 = &mut s;
|
||
let r2 = &mut s;
|
||
println!("{}, {}", r1, r2);
|
||
}
|
||
```
|
||
|
||
### 题目 1-6
|
||
|
||
```rust
|
||
fn main() {
|
||
let mut s = String::from("hello");
|
||
let r1 = &s;
|
||
let r2 = &s;
|
||
let r3 = &mut s;
|
||
println!("{}, {}, {}", r1, r2, r3);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 二、填空题:补充代码
|
||
|
||
补全下列代码使其能通过编译。
|
||
|
||
### 题目 2-1:转移所有权
|
||
|
||
```rust
|
||
fn main() {
|
||
let s = String::from("rustacean");
|
||
takes_ownership(________); // 填空,使 s 的所有权进入函数
|
||
// println!("{}", s); // 此行若取消注释会报错
|
||
}
|
||
|
||
fn takes_ownership(s: String) {
|
||
println!("{}", s);
|
||
}
|
||
```
|
||
|
||
### 题目 2-2:借用的生命周期
|
||
|
||
```rust
|
||
fn main() {
|
||
let s = String::from("hello");
|
||
let len = get_length(________); // 填空:传递引用取得长度
|
||
println!("'{}' 的长度是 {}", s, len);
|
||
}
|
||
|
||
fn get_length(s: &String) -> usize {
|
||
s.len()
|
||
}
|
||
```
|
||
|
||
### 题目 2-3:可变引用
|
||
|
||
```rust
|
||
fn main() {
|
||
let ________ s = String::from("hello"); // 填空:声明可变变量
|
||
append_world(________); // 填空:传递可变引用
|
||
println!("{}", s); // 期望输出:hello, world!
|
||
}
|
||
|
||
fn append_world(s: &mut String) {
|
||
s.push_str(", world!");
|
||
}
|
||
```
|
||
|
||
### 题目 2-4:作用域技巧
|
||
|
||
```rust
|
||
fn main() {
|
||
let mut s = String::from("hello");
|
||
|
||
{ // 进入新作用域
|
||
let r1 = ________; // 填空:创建不可变引用
|
||
println!("{}", r1);
|
||
} // r1 离开作用域
|
||
|
||
let r2 = ________; // 填空:现在可以创建可变引用了
|
||
r2.push_str(" world");
|
||
println!("{}", r2);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 三、找出并修复错误
|
||
|
||
以下每段代码都有编译错误,请指出错误并写出修正后的代码。
|
||
|
||
### 题目 3-1
|
||
|
||
```rust
|
||
fn main() {
|
||
let s1 = String::from("hello");
|
||
let s2 = s1;
|
||
print_both(s1, s2);
|
||
}
|
||
|
||
fn print_both(a: String, b: String) {
|
||
println!("{} and {}", a, b);
|
||
}
|
||
```
|
||
|
||
### 题目 3-2
|
||
|
||
```rust
|
||
fn main() {
|
||
let s = dangle();
|
||
println!("{}", s);
|
||
}
|
||
|
||
fn dangle() -> &String {
|
||
let s = String::from("hello");
|
||
&s
|
||
}
|
||
```
|
||
|
||
### 题目 3-3
|
||
|
||
```rust
|
||
fn main() {
|
||
let mut s = String::from("hello");
|
||
let r1 = &mut s;
|
||
r1.push_str(", world");
|
||
let r2 = &mut s;
|
||
r2.push_str("!");
|
||
println!("{}", r1);
|
||
println!("{}", r2);
|
||
}
|
||
```
|
||
|
||
### 题目 3-4
|
||
|
||
```rust
|
||
fn main() {
|
||
let s = String::from("hello world");
|
||
let first = &s[0..5];
|
||
s.clear(); // 清空字符串
|
||
println!("first = {}", first);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 四、编程题
|
||
|
||
### 题目 4-1:计算单词数量
|
||
|
||
编写一个函数 `count_words(s: &str) -> usize`,计算字符串中的单词数量(以空格分隔)。
|
||
|
||
```rust
|
||
fn count_words(s: &str) -> usize {
|
||
// 你的代码
|
||
}
|
||
|
||
fn main() {
|
||
let s = String::from("hello world rust");
|
||
let count = count_words(&s);
|
||
println!("'{}' 有 {} 个单词", s, count); // 期望输出:3
|
||
|
||
// 验证 s 的所有权未被取走
|
||
println!("原始字符串仍然可用: {}", s);
|
||
}
|
||
```
|
||
|
||
### 题目 4-2:获取首尾单词
|
||
|
||
编写函数 `first_and_last(s: &str) -> (&str, &str)`,返回字符串的首单词和尾单词。
|
||
|
||
```rust
|
||
fn first_and_last(s: &str) -> (&str, &str) {
|
||
// 你的代码
|
||
}
|
||
|
||
fn main() {
|
||
let s = String::from("Rust is a systems programming language");
|
||
let (first, last) = first_and_last(&s);
|
||
println!("首单词: '{}', 尾单词: '{}'", first, last);
|
||
// 期望输出:首单词: 'Rust', 尾单词: 'language'
|
||
println!("原字符串: {}", s);
|
||
}
|
||
```
|
||
|
||
### 题目 4-3:字符串处理
|
||
|
||
编写一个函数,接收一个字符串,返回一个新的字符串,将其中的每个单词首字母大写,其余字母小写。
|
||
|
||
```rust
|
||
fn title_case(s: &str) -> String {
|
||
// 你的代码
|
||
// 提示:可以使用 split_whitespace、to_uppercase、to_lowercase、collect 等方法
|
||
}
|
||
|
||
fn main() {
|
||
let s = String::from("hello WORLD rust");
|
||
let result = title_case(&s);
|
||
println!("输入: {}", s);
|
||
println!("输出: {}", result);
|
||
// 期望输出:Hello World Rust
|
||
|
||
// 验证 s 的所有权未被取走
|
||
println!("原字符串: {}", s);
|
||
}
|
||
```
|
||
|
||
### 题目 4-4:参数所有权设计
|
||
|
||
请合理设计以下函数的参数类型(传值 / 传引用 / 传可变引用):
|
||
|
||
```rust
|
||
// fn capitalize(s: ???) // 1. 只读取,不修改,调用后原变量还要用
|
||
// fn consume_and_print(s: ???) // 2. 拿走所有权,调用后原变量不再使用
|
||
// fn append_exclamation(s: ???)// 3. 修改原字符串
|
||
|
||
fn capitalize(s: ________) -> String {
|
||
let mut result = s.clone();
|
||
if let Some(c) = result.get_mut(0..1) {
|
||
c.make_ascii_uppercase();
|
||
}
|
||
result
|
||
}
|
||
|
||
fn consume_and_print(s: ________) {
|
||
println!("消费了: {}", s);
|
||
}
|
||
|
||
fn append_exclamation(s: ________) {
|
||
s.push_str("!");
|
||
}
|
||
|
||
fn main() {
|
||
let s = String::from("hello");
|
||
|
||
let capitalized = capitalize(________); // 填空:调用 capitalize
|
||
println!("{}", s); // s 仍可用
|
||
|
||
consume_and_print(________); // 填空:调用 consume_and_print,传入 s
|
||
// println!("{}", s); // 若取消注释会报错
|
||
|
||
let mut t = String::from("hello");
|
||
append_exclamation(________); // 填空:调用 append_exclamation
|
||
println!("{}", t); // 期望输出:hello!
|
||
}
|
||
```
|
||
|
||
### 题目 4-5:数组切片操作
|
||
|
||
编写函数实现以下功能:
|
||
|
||
```rust
|
||
// 返回数组前 n 个元素的和
|
||
fn sum_of_first_n(arr: &[i32], n: usize) -> i32 {
|
||
// 你的代码
|
||
// 提示:使用切片 &arr[..n]
|
||
}
|
||
|
||
// 判断一个切片是否包含目标值
|
||
fn contains(arr: &[i32], target: i32) -> bool {
|
||
// 你的代码
|
||
}
|
||
|
||
fn main() {
|
||
let numbers = [10, 20, 30, 40, 50];
|
||
|
||
println!("前 3 个元素的和: {}", sum_of_first_n(&numbers, 3)); // 期望:60
|
||
|
||
println!("包含 30 吗? {}", contains(&numbers, 30)); // 期望:true
|
||
println!("包含 99 吗? {}", contains(&numbers, 99)); // 期望:false
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 五、综合思考题
|
||
|
||
### 题目 5-1
|
||
|
||
以下代码为什么不能通过编译?提出两种修复方法,并说明各自的优缺点。
|
||
|
||
```rust
|
||
fn main() {
|
||
let mut data = vec![1, 2, 3];
|
||
let first = &data[0];
|
||
data.push(4);
|
||
println!("first = {}", first);
|
||
}
|
||
```
|
||
|
||
### 题目 5-2
|
||
|
||
分析以下代码,回答:
|
||
1. 写出每一步所有权的状态变化
|
||
2. 最终输出是什么?
|
||
|
||
```rust
|
||
fn main() {
|
||
let mut s = String::from("rust");
|
||
let r1 = &s;
|
||
let r2 = &s;
|
||
println!("{} and {}", r1, r2);
|
||
// r1, r2 此后不再使用
|
||
|
||
let r3 = &mut s;
|
||
r3.push_str(" is great");
|
||
println!("{}", r3);
|
||
// r3 此后不再使用
|
||
|
||
let s2 = s; // 移动 s
|
||
// println!("{}", s); // 若取消注释会怎样?
|
||
println!("{}", s2);
|
||
}
|
||
```
|
||
|
||
### 题目 5-3
|
||
|
||
以下代码在 Rust 中是否合法?为什么?
|
||
|
||
```rust
|
||
fn main() {
|
||
let x = 5;
|
||
let y = &x;
|
||
let z = &x;
|
||
println!("{} {} {}", x, y, z);
|
||
}
|
||
```
|
||
|
||
如果将 `x` 的类型从 `i32` 改为 `String`,同样的结构是否仍然合法?
|
||
|
||
```rust
|
||
fn main() {
|
||
let x = String::from("hello");
|
||
let y = &x;
|
||
let z = &x;
|
||
println!("{} {} {}", x, y, z);
|
||
}
|
||
```
|
||
|
||
### 题目 5-4
|
||
|
||
Rust 的所有权系统与 C++ 的 RAII、Java 的垃圾回收各自有什么优缺点?请简要对比分析。
|
||
|
||
---
|
||
|
||
## 参考答案
|
||
|
||
> 请独立完成再查看答案。
|
||
|
||
<details>
|
||
<summary>点击展开答案</summary>
|
||
|
||
### 一、基础题
|
||
|
||
**1-1**:❌ 编译错误。`s` 的所有权已移动到 `t`,`s` 已失效。
|
||
|
||
**1-2**:✅ 通过编译。`i32` 实现了 `Copy`,赋值时自动复制。
|
||
|
||
**1-3**:✅ 通过编译。`clone` 创建了深拷贝,两个变量独立。
|
||
|
||
**1-4**:✅ 通过编译。不可变引用可以同时存在多个。
|
||
|
||
**1-5**:❌ 编译错误。同一作用域不能同时存在两个可变引用。
|
||
|
||
**1-6**:❌ 编译错误。已有不可变引用时不能创建可变引用。
|
||
|
||
### 二、填空题
|
||
|
||
**2-1**:`takes_ownership(s);`
|
||
|
||
**2-2**:`let len = get_length(&s);`
|
||
|
||
**2-3**:
|
||
```rust
|
||
let mut s = String::from("hello");
|
||
append_world(&mut s);
|
||
```
|
||
|
||
**2-4**:
|
||
```rust
|
||
let r1 = &s;
|
||
let r2 = &mut s;
|
||
```
|
||
|
||
### 三、修复错误
|
||
|
||
**3-1**:`s1` 已被移动给 `s2`,不能再传给函数。修复:
|
||
```rust
|
||
fn main() {
|
||
let s1 = String::from("hello");
|
||
let s2 = s1.clone(); // 克隆一份
|
||
print_both(s1, s2);
|
||
}
|
||
```
|
||
|
||
**3-2**:返回了局部变量的引用,生命周期不够。修复:
|
||
```rust
|
||
fn dangle() -> String { // 返回 String 而非 &String
|
||
let s = String::from("hello");
|
||
s // 所有权移出
|
||
}
|
||
```
|
||
|
||
**3-3**:`r1` 和 `r2` 的可变借用时间重叠。修复:将两次借用限制在各自的作用域内,或使用 `Non-Lexical Lifetimes` 特性(Rust 2018+ 已默认启用,但此处 `r1` 在 `r2` 创建后仍被使用)。修复方法:
|
||
```rust
|
||
fn main() {
|
||
let mut s = String::from("hello");
|
||
let r1 = &mut s;
|
||
r1.push_str(", world");
|
||
println!("{}", r1);
|
||
// r1 在 println! 后不再使用
|
||
let r2 = &mut s;
|
||
r2.push_str("!");
|
||
println!("{}", r2);
|
||
}
|
||
```
|
||
|
||
**3-4**:`clear()` 需要一个可变引用,但 `first` 是一个不可变引用,违反了借用规则。修复:先使用 `first`,再 `clear`:
|
||
```rust
|
||
fn main() {
|
||
let s = String::from("hello world");
|
||
let first = &s[0..5];
|
||
println!("first = {}", first); // 先使用
|
||
let mut s = s; // 重新绑定
|
||
s.clear();
|
||
}
|
||
```
|
||
|
||
### 四、编程题
|
||
|
||
**4-1**:
|
||
```rust
|
||
fn count_words(s: &str) -> usize {
|
||
s.split_whitespace().count()
|
||
}
|
||
```
|
||
|
||
**4-2**:
|
||
```rust
|
||
fn first_and_last(s: &str) -> (&str, &str) {
|
||
let words: Vec<&str> = s.split_whitespace().collect();
|
||
(words[0], words[words.len() - 1])
|
||
}
|
||
```
|
||
|
||
**4-3**:
|
||
```rust
|
||
fn title_case(s: &str) -> String {
|
||
s.split_whitespace()
|
||
.map(|word| {
|
||
let mut chars = word.chars();
|
||
match chars.next() {
|
||
None => String::new(),
|
||
Some(c) => c.to_uppercase().collect::<String>() + &chars.as_str().to_lowercase(),
|
||
}
|
||
})
|
||
.collect::<Vec<String>>()
|
||
.join(" ")
|
||
}
|
||
```
|
||
|
||
**4-4**:
|
||
```rust
|
||
fn capitalize(s: &str) -> String { /* ... */ }
|
||
fn consume_and_print(s: String) { /* ... */ }
|
||
fn append_exclamation(s: &mut String) { /* ... */ }
|
||
|
||
// 调用方式:
|
||
let capitalized = capitalize(&s); // 传引用
|
||
consume_and_print(s); // 传值,所有权移入
|
||
append_exclamation(&mut t); // 传可变引用
|
||
```
|
||
|
||
**4-5**:
|
||
```rust
|
||
fn sum_of_first_n(arr: &[i32], n: usize) -> i32 {
|
||
arr[..n].iter().sum()
|
||
}
|
||
|
||
fn contains(arr: &[i32], target: i32) -> bool {
|
||
arr.contains(&target)
|
||
}
|
||
```
|
||
|
||
### 五、综合思考题
|
||
|
||
**5-1**:`first` 是对 `data` 元素的不可变引用,`push` 需要一个可变引用,违反借用规则。
|
||
- 方法一:调整顺序,先 push 再取引用 —— 简单但有时逻辑不允许
|
||
- 方法二:使用索引 `let first = data[0]`(需要 Copy 类型)—— 避免了借用但只适用于 Copy 类型
|
||
|
||
**5-2**:
|
||
- 步骤:创建 `s` → 两次不可变借用(r1, r2)→ 打印后 r1, r2 释放 → 一次可变借用(r3)→ 打印后 r3 释放 → 移动 `s` 到 `s2`
|
||
- 输出:`rust and rust` / `rust is great` / `rust is great`
|
||
- 取消注释会报错:`s` 已移动
|
||
|
||
**5-3**:两者都合法。创建引用不会转移 `x` 的所有权,无论 `x` 是什么类型。只要引用和原值不冲突即可(此处都是不可变引用)。
|
||
|
||
**5-4**(简要):
|
||
- **C++ RAII**:灵活、性能极高,但依赖程序员自觉,容易出现野指针、重复释放
|
||
- **Java GC**:使用方便,不会出现悬挂指针,但存在运行时开销、不可预测的停顿
|
||
- **Rust 所有权**:编译期零成本抽象,保证内存安全/线程安全,但学习曲线陡峭
|
||
|
||
</details>
|