feat(part3): 添加所有权练习题答案和详细解析
- 在main.rs中添加了引用相关的代码示例,演示x、y、z三个变量的使用 - 在练习题文档中补充了题目1-1到1-6的正确答案,解释了所有权和借用规则 - 详细分析了题目2-1到2-4关于借用生命周期的问题,提供了正确的代码修复方案 - 解决了悬垂引用(dangling reference)问题,修正了函数返回引用的错误做法 - 完善了字符串操作相关练习的答案,包括count_words、first_and_last等函数实现 - 补充了切片操作练习的完整代码和逻辑说明 - 修正了所有权转移和引用借用的概念性错误,提供了准确的解析 - 更新了Rust所有权系统与其他语言内存管理机制的对比分析
This commit is contained in:
parent
739ebd5047
commit
a7aa274be3
|
|
@ -1,3 +1,6 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
let x = String::from("hello");
|
||||
let y = &x;
|
||||
let z = &x;
|
||||
println!("{} {} {}", x, y, z);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,262 @@
|
|||
# Rust 所有权关键概念梳理
|
||||
|
||||
> 基于练习题作答中的误区整理,配合纠正说明。
|
||||
|
||||
---
|
||||
|
||||
## 1. 三条核心规则
|
||||
|
||||
| 规则 | 说明 |
|
||||
|------|------|
|
||||
| 每个值有且只有一个**所有者** | 变量绑定 = 所有权 |
|
||||
| 同一时刻只能有**一个可变引用**,或**任意多个不可变引用** | 读写互斥 |
|
||||
| 所有者离开作用域,值被 `drop` | RAII 式自动释放 |
|
||||
|
||||
---
|
||||
|
||||
## 2. Move(所有权转移)
|
||||
|
||||
```rust
|
||||
let s1 = String::from("hello");
|
||||
let s2 = s1; // s1 的所有权移动给 s2
|
||||
// println!("{}", s1); // ❌ s1 已失效
|
||||
```
|
||||
|
||||
**常见误区:** 认为 move 之后还能继续用原变量。堆数据(`String`、`Vec`、`Box` 等)的赋值/传参默认是 move,源变量立即失效。
|
||||
|
||||
---
|
||||
|
||||
## 3. Copy 类型(自动复制)
|
||||
|
||||
```rust
|
||||
let x = 42;
|
||||
let y = x; // i32 实现了 Copy,自动复制
|
||||
println!("{}", x); // ✅ x 仍然有效
|
||||
```
|
||||
|
||||
实现了 `Copy` 的类型(`i32`、`bool`、`f64`、`char`、`&T` 等)赋值时会自动复制,不发生 move。
|
||||
**注意:** 包含堆数据的类型(`String`、`Vec` 等)**不实现** `Copy`。
|
||||
|
||||
---
|
||||
|
||||
## 4. Clone(显式深拷贝)
|
||||
|
||||
```rust
|
||||
let s1 = String::from("hello");
|
||||
let s2 = s1.clone(); // 显式深拷贝,两个变量各自独立
|
||||
println!("{} {}", s1, s2); // ✅
|
||||
```
|
||||
|
||||
与 Copy 的区别:clone 是显式的、可能有开销;Copy 是隐式的、按位复制、无开销。
|
||||
|
||||
---
|
||||
|
||||
## 5. 引用(Borrow)—— 不转移所有权 ⚠️ 关键
|
||||
|
||||
> **`&` 就是借用,从!不!转!移!所!有!权!**
|
||||
|
||||
```rust
|
||||
let x = String::from("hello");
|
||||
let y = &x; // y 只是借用了 x,x 仍然是所有者
|
||||
println!("{}", x); // ✅ x 仍然可用
|
||||
println!("{}", y); // ✅ y 可以通过引用读取
|
||||
```
|
||||
|
||||
**原答误以为** `let y = &x` 会转移所有权 → **完全错误**。与下面对比:
|
||||
|
||||
```rust
|
||||
let x = String::from("hello");
|
||||
let y = x; // ← 没有 &,这是 move!x 失效
|
||||
let z = &x; // ← 有 &,引用,x 仍在
|
||||
```
|
||||
|
||||
| 写法 | 行为 |
|
||||
|------|------|
|
||||
| `let y = x;` | move,所有权转移 |
|
||||
| `let y = &x;` | borrow,借用,x 不动 |
|
||||
| `let y = x.clone();` | 深拷贝,各自独立 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 不可变引用(`&T`)
|
||||
|
||||
```rust
|
||||
let s = String::from("hello");
|
||||
let r1 = &s;
|
||||
let r2 = &s; // ✅ 允许多个不可变引用共存
|
||||
println!("{} {} {}", s, r1, r2); // ✅ 全部可用
|
||||
```
|
||||
|
||||
规则:**多个不可变引用可以同时存在**,原值也可以被读取。
|
||||
|
||||
---
|
||||
|
||||
## 7. 可变引用(`&mut T`)
|
||||
|
||||
```rust
|
||||
let mut s = String::from("hello");
|
||||
let r1 = &mut s; // 可变引用
|
||||
r1.push_str(" world");
|
||||
// let r2 = &mut s; // ❌ 同一作用域不能有两个可变引用
|
||||
```
|
||||
|
||||
规则:**同一时刻只能有一个可变引用**。
|
||||
|
||||
---
|
||||
|
||||
## 8. 不可变引用与可变引用互斥 ⚠️ 1-6 错误点
|
||||
|
||||
```rust
|
||||
let mut s = String::from("hello");
|
||||
let r1 = &s; // 不可变借用
|
||||
let r2 = &s; // 不可变借用
|
||||
let r3 = &mut s; // ❌ 已有不可变借用时,不能创建可变借用
|
||||
println!("{} {} {}", r1, r2, r3);
|
||||
```
|
||||
|
||||
**原答认为能编译 → 不能。** 不可变和可变引用不能共存于重叠的作用域中。
|
||||
|
||||
---
|
||||
|
||||
## 9. NLL(Non-Lexical Lifetimes)—— 3-3 的修复原理
|
||||
|
||||
Rust 2018+ 引入 NLL:**引用的生命周期结束于它最后一次被使用的语句,而非作用域末尾。**
|
||||
|
||||
```rust
|
||||
let mut s = String::from("hello");
|
||||
|
||||
let r1 = &mut s;
|
||||
r1.push_str(", world");
|
||||
println!("{}", r1); // ← r1 最后一次使用,NLL 在此结束其借用
|
||||
|
||||
let r2 = &mut s; // ✅ r1 已失效,可以创建新的可变引用
|
||||
r2.push_str("!");
|
||||
println!("{}", r2);
|
||||
```
|
||||
|
||||
同一段代码在 `let r1 = &mut s;` 和 `let r2 = &mut s;` 之间如果没有 NLL,按词法作用域会冲突。NLL 使借用更短、更精确。
|
||||
|
||||
**但 NLL 不破坏规则**:如果 r1 在 r2 创建后仍然被使用(比如 println 在最后),编译仍然失败。
|
||||
|
||||
---
|
||||
|
||||
## 10. 悬垂引用(Dangling Reference)—— 3-2 真正原因
|
||||
|
||||
```rust
|
||||
fn dangle() -> &String { // ❌ 返回引用
|
||||
let s = String::from("hello");
|
||||
&s // s 在函数结束时被 drop,引用悬垂
|
||||
}
|
||||
```
|
||||
|
||||
**原答写"返回值被移动了" → 不准确。** 真正原因:返回的引用指向一个即将被释放的局部变量。Rust 编译期拒绝这种代码。
|
||||
|
||||
修复:
|
||||
|
||||
```rust
|
||||
fn dangle() -> String { // ✅ 返回所有权
|
||||
let s = String::from("hello");
|
||||
s
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 切片与借用冲突 —— 3-4
|
||||
|
||||
```rust
|
||||
let s = String::from("hello world");
|
||||
let first = &s[0..5]; // first 是切片引用(不可变借用)
|
||||
s.clear(); // ❌ clear() 需要 &mut self,与 first 冲突
|
||||
println!("{}", first);
|
||||
```
|
||||
|
||||
**核心冲突:不可变引用(切片)和可变引用(clear)不能共存。** 修复方法:
|
||||
|
||||
```rust
|
||||
let s = String::from("hello world");
|
||||
let first = &s[0..5];
|
||||
println!("first = {}", first); // 先使用 first
|
||||
let mut s = s; // 重新绑定,first 的借用已结束
|
||||
s.clear();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. `str` vs `String` —— 4-4 错误点
|
||||
|
||||
| 类型 | 说明 |
|
||||
|------|------|
|
||||
| `String` | 堆上分配、可增长的字符串。有所有权。`Sized`。 |
|
||||
| `&str` | 字符串切片引用。借用。`Sized`(引用本身是指针+长度)。 |
|
||||
| `str` | **动态大小类型(DST)**,编译期大小未知。**不能直接作为参数/变量类型。** |
|
||||
|
||||
```rust
|
||||
fn consume_and_print(s: str) { } // ❌ str 是 DST
|
||||
fn consume_and_print(s: String) { } // ✅ 拿走所有权
|
||||
fn consume_and_print(s: &str) { } // ✅ 借用(推荐用于只读场景)
|
||||
|
||||
fn append_exclamation(s: &mut str) { } // ❌ str 是 DST
|
||||
fn append_exclamation(s: &mut String) {} // ✅ 可变引用
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. `&str` 是借用,不会转移所有权 —— 5-3 核心纠正
|
||||
|
||||
```rust
|
||||
let x = String::from("hello");
|
||||
let y = &x; // 不可变引用,x 仍然拥有 String
|
||||
let z = &x; // 另一个不可变引用
|
||||
println!("{} {} {}", x, y, z); // ✅ 全部合法
|
||||
```
|
||||
|
||||
**原答说"所有权被转移给 y 和 z" → 彻底错误。**
|
||||
|
||||
所有权转移只有两种方式:
|
||||
1. **赋值没有 `&`**:`let y = x;`
|
||||
2. **传参没有 `&`**:`fn foo(s: String)` + `foo(x);`
|
||||
|
||||
有 `&` 就是借用,不是转移。
|
||||
|
||||
---
|
||||
|
||||
## 14. 借用后修改的影响 —— 5-2 输出纠正
|
||||
|
||||
```rust
|
||||
let mut s = String::from("rust");
|
||||
let r3 = &mut s;
|
||||
r3.push_str(" is great"); // ⚠️ 通过可变引用修改了 s 的内容
|
||||
println!("{}", r3); // "rust is great"
|
||||
// r3 不再使用
|
||||
|
||||
let s2 = s; // s 现在是 "rust is great",移动给 s2
|
||||
println!("{}", s2); // "rust is great"
|
||||
```
|
||||
|
||||
**原答认为最后输出是 "rust" → 忽视了 r3 对 s 的修改。**
|
||||
|
||||
---
|
||||
|
||||
## 15. 常见错误速查
|
||||
|
||||
| 错误类型 | 示例 | 编译器信息 |
|
||||
|----------|------|-----------|
|
||||
| use after move | `let y = x; println!("{}", x);` | `E0382: use of moved value` |
|
||||
| 两个可变引用 | `let r1 = &mut s; let r2 = &mut s;` | `E0499: cannot borrow as mutable more than once` |
|
||||
| 不可变+可变冲突 | `let r = &s; let rm = &mut s;` | `E0502: cannot borrow as mutable, immutable borrow exists` |
|
||||
| 悬垂引用 | `fn f() -> &String { &s }` | `E0106: missing lifetime specifier` / `E0515` |
|
||||
| 返回局部引用 | `fn f() -> &String { &s }` where s is local | 生命周期错误 |
|
||||
| DST 作参数 | `fn f(s: str) { }` | `E0277: the size is not known at compile time` |
|
||||
|
||||
---
|
||||
|
||||
## 16. 决策总结
|
||||
|
||||
| 需求 | 签名 |
|
||||
|------|------|
|
||||
| 只读取,原值还要用 | `fn f(s: &str)` 或 `fn f(s: &String)` |
|
||||
| 只读取,可能存下来 | `fn f(s: &str) -> String` 返回新值 |
|
||||
| 修改原值 | `fn f(s: &mut String)` |
|
||||
| 拿走所有权 | `fn f(s: String)` |
|
||||
| 函数只读 | 优先用 `&str`(比 `&String` 更通用) |
|
||||
121
part3/所有权练习题.md
121
part3/所有权练习题.md
|
|
@ -15,6 +15,7 @@ fn main() {
|
|||
println!("{}", s);
|
||||
}
|
||||
```
|
||||
> 答: 不能
|
||||
|
||||
### 题目 1-2
|
||||
|
||||
|
|
@ -25,6 +26,7 @@ fn main() {
|
|||
println!("x = {}, y = {}", x, y);
|
||||
}
|
||||
```
|
||||
> 答: 能
|
||||
|
||||
### 题目 1-3
|
||||
|
||||
|
|
@ -35,6 +37,7 @@ fn main() {
|
|||
println!("s = {}, t = {}", s, t);
|
||||
}
|
||||
```
|
||||
> 答: 能
|
||||
|
||||
### 题目 1-4
|
||||
|
||||
|
|
@ -46,6 +49,7 @@ fn main() {
|
|||
println!("{} {} {}", s1, s2, s3);
|
||||
}
|
||||
```
|
||||
> 答: 能
|
||||
|
||||
### 题目 1-5
|
||||
|
||||
|
|
@ -57,6 +61,7 @@ fn main() {
|
|||
println!("{}, {}", r1, r2);
|
||||
}
|
||||
```
|
||||
> 答: 不能
|
||||
|
||||
### 题目 1-6
|
||||
|
||||
|
|
@ -69,6 +74,9 @@ fn main() {
|
|||
println!("{}, {}, {}", r1, r2, r3);
|
||||
}
|
||||
```
|
||||
> **[批注] 原答"能"错误 —— 该代码无法通过编译。**
|
||||
> r1、r2 是不可变引用,r3 是可变引用。Rust 不允许在已有不可变借用的作用域内再创建可变借用。`println!` 同时使用了三者,借用范围重叠,编译器报错:
|
||||
> `error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable`
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -89,6 +97,7 @@ fn takes_ownership(s: String) {
|
|||
println!("{}", s);
|
||||
}
|
||||
```
|
||||
> 答: s
|
||||
|
||||
### 题目 2-2:借用的生命周期
|
||||
|
||||
|
|
@ -103,6 +112,7 @@ fn get_length(s: &String) -> usize {
|
|||
s.len()
|
||||
}
|
||||
```
|
||||
> 答: &s
|
||||
|
||||
### 题目 2-3:可变引用
|
||||
|
||||
|
|
@ -117,6 +127,7 @@ fn append_world(s: &mut String) {
|
|||
s.push_str(", world!");
|
||||
}
|
||||
```
|
||||
> 答: mut, &mut s
|
||||
|
||||
### 题目 2-4:作用域技巧
|
||||
|
||||
|
|
@ -134,6 +145,7 @@ fn main() {
|
|||
println!("{}", r2);
|
||||
}
|
||||
```
|
||||
> 答: &s, &mut s
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -150,6 +162,18 @@ fn main() {
|
|||
print_both(s1, s2);
|
||||
}
|
||||
|
||||
fn print_both(a: String, b: String) {
|
||||
println!("{} and {}", a, b);
|
||||
}
|
||||
```
|
||||
> **[批注] 原错误分析有误:** 错误原因是 `let s2 = s1;` 将 s1 的所有权移动给了 s2,此后 s1 已失效,不能继续用于 `print_both(s1, s2)`。原答写的修复代码语法也有问题(`fn print_both(a: String, &b: String)` 不是合法签> 名)。正确修复:clone 一份。
|
||||
```rust
|
||||
fn main() {
|
||||
let s1 = String::from("hello");
|
||||
let s2 = s1.clone();
|
||||
print_both(s1, s2);
|
||||
}
|
||||
|
||||
fn print_both(a: String, b: String) {
|
||||
println!("{} and {}", a, b);
|
||||
}
|
||||
|
|
@ -168,6 +192,18 @@ fn dangle() -> &String {
|
|||
&s
|
||||
}
|
||||
```
|
||||
> **[批注] 原错误分析不精确。** 真正原因:`dangle()` 返回 `&String`,但引用指向的是函数内的局部变量 `s`。函数结束时 `s` 被释放,返回的引用变成悬垂引用(dangling reference)。Rust 编译器在编译期阻止了这种行为。修复方法:直接返回 `String` 让所有权移出。
|
||||
```rust
|
||||
fn main() {
|
||||
let s = dangle();
|
||||
println!("{}", s);
|
||||
}
|
||||
|
||||
fn dangle() -> String {
|
||||
let s = String::from("hello");
|
||||
s
|
||||
}
|
||||
```
|
||||
|
||||
### 题目 3-3
|
||||
|
||||
|
|
@ -182,6 +218,18 @@ fn main() {
|
|||
println!("{}", r2);
|
||||
}
|
||||
```
|
||||
> **[批注] 原分析"同时只能存在一个可变引用"基本正确,但未给出修复代码。** 由于 r1 在 `println!("{}", r1)` 之后不再使用,Rust 的 NLL(Non-Lexical Lifetimes)可在该点结束 r1 的借用。修复:将 println! 移到 r2 创建之前。
|
||||
```rust
|
||||
fn main() {
|
||||
let mut s = String::from("hello");
|
||||
let r1 = &mut s;
|
||||
r1.push_str(", world");
|
||||
println!("{}", r1); // r1 在此之后不再使用,NLL 结束其借用
|
||||
let r2 = &mut s; // 此时 r1 已失效,可以创建新的可变引用
|
||||
r2.push_str("!");
|
||||
println!("{}", r2);
|
||||
}
|
||||
```
|
||||
|
||||
### 题目 3-4
|
||||
|
||||
|
|
@ -193,6 +241,16 @@ fn main() {
|
|||
println!("first = {}", first);
|
||||
}
|
||||
```
|
||||
> **[批注] 原分析不精确,原修复代码 `let first = s[0..5];` 仍是切片引用,与 `s.clear()` 的 `&mut self` 冲突,同样无法编译。** 真正原因:`first` 是对 `s` 内容的不可变引用,而 `clear()` 需要 `&mut self`(可变引用),两者不能同时存在。正确修复:先使用 first,再 clear。
|
||||
```rust
|
||||
fn main() {
|
||||
let s = String::from("hello world");
|
||||
let first = &s[0..5];
|
||||
println!("first = {}", first); // 先使用 first
|
||||
let mut s = s; // 重新绑定为可变的
|
||||
s.clear(); // 此时 first 的借用已结束
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -205,6 +263,7 @@ fn main() {
|
|||
```rust
|
||||
fn count_words(s: &str) -> usize {
|
||||
// 你的代码
|
||||
return s.split_whitespace().count();
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
@ -224,6 +283,8 @@ fn main() {
|
|||
```rust
|
||||
fn first_and_last(s: &str) -> (&str, &str) {
|
||||
// 你的代码
|
||||
let words: Vec<&str> = s.split_whitespace().collect();
|
||||
return (words[0], words[words.len() - 1]);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
@ -243,6 +304,19 @@ fn main() {
|
|||
fn title_case(s: &str) -> String {
|
||||
// 你的代码
|
||||
// 提示:可以使用 split_whitespace、to_uppercase、to_lowercase、collect 等方法
|
||||
> **[批注] 原代码未完成。** 只 collect 了 words 但没有做大小写转换。补全如下:
|
||||
```rust
|
||||
fn title_case(s: &str) -> String {
|
||||
s.split_whitespace()
|
||||
.map(|word| {
|
||||
let mut chars = word.chars();
|
||||
match chars.next() {
|
||||
Some(c) => c.to_uppercase().collect::<String>() + &chars.as_str().to_lowercase(),
|
||||
None => String::new(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
@ -266,33 +340,33 @@ fn main() {
|
|||
// fn consume_and_print(s: ???) // 2. 拿走所有权,调用后原变量不再使用
|
||||
// fn append_exclamation(s: ???)// 3. 修改原字符串
|
||||
|
||||
fn capitalize(s: ________) -> String {
|
||||
let mut result = s.clone();
|
||||
fn capitalize(s: &str) -> String {
|
||||
let mut result = s.to_string(); // &str -> String
|
||||
if let Some(c) = result.get_mut(0..1) {
|
||||
c.make_ascii_uppercase();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn consume_and_print(s: ________) {
|
||||
fn consume_and_print(s: String) { // [批注] 原写了 str,str 是 DST 不能直接作参数
|
||||
println!("消费了: {}", s);
|
||||
}
|
||||
|
||||
fn append_exclamation(s: ________) {
|
||||
fn append_exclamation(s: &mut String) { // [批注] 原写了 &mut str,应为 &mut String
|
||||
s.push_str("!");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let s = String::from("hello");
|
||||
|
||||
let capitalized = capitalize(________); // 填空:调用 capitalize
|
||||
let capitalized = capitalize(&s); // 填空:调用 capitalize
|
||||
println!("{}", s); // s 仍可用
|
||||
|
||||
consume_and_print(________); // 填空:调用 consume_and_print,传入 s
|
||||
consume_and_print(s); // 填空:调用 consume_and_print,传入 s
|
||||
// println!("{}", s); // 若取消注释会报错
|
||||
|
||||
let mut t = String::from("hello");
|
||||
append_exclamation(________); // 填空:调用 append_exclamation
|
||||
append_exclamation(&mut t); // 填空:调用 append_exclamation
|
||||
println!("{}", t); // 期望输出:hello!
|
||||
}
|
||||
```
|
||||
|
|
@ -306,11 +380,13 @@ fn main() {
|
|||
fn sum_of_first_n(arr: &[i32], n: usize) -> i32 {
|
||||
// 你的代码
|
||||
// 提示:使用切片 &arr[..n]
|
||||
return arr[..n].iter().sum();
|
||||
}
|
||||
|
||||
// 判断一个切片是否包含目标值
|
||||
fn contains(arr: &[i32], target: i32) -> bool {
|
||||
// 你的代码
|
||||
return arr.contains(&target);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
@ -339,6 +415,7 @@ fn main() {
|
|||
println!("first = {}", first);
|
||||
}
|
||||
```
|
||||
> 答:
|
||||
|
||||
### 题目 5-2
|
||||
|
||||
|
|
@ -364,6 +441,26 @@ fn main() {
|
|||
println!("{}", s2);
|
||||
}
|
||||
```
|
||||
> **[批注] 原答最终输出写错了。** r3 通过 `push_str(" is great")` 修改了 s 的内容,s 此时已经是 `"rust is great"`。之后 s 移动给 s2,s2 也是 `"rust is great"`。
|
||||
>
|
||||
> 正确的逐步分析:
|
||||
> 1. `let mut s = String::from("rust")` — s 拥有 "rust"
|
||||
> 2. `let r1 = &s` — 不可变借用,s 仍持有所有权
|
||||
> 3. `let r2 = &s` — 第二个不可变借用(允许多个)
|
||||
> 4. `println!("{} and {}", r1, r2)` — 输出 `rust and rust`,此后 r1、r2 不再使用
|
||||
> 5. `let r3 = &mut s` — 可变借用(r1、r2 已失效,允许)
|
||||
> 6. `r3.push_str(" is great")` — 通过可变引用修改 s
|
||||
> 7. `println!("{}", r3)` — 输出 `rust is great`
|
||||
> 8. `let s2 = s` — s 的所有权移动到 s2
|
||||
> 9. `println!("{}", s2)` — 输出 `rust is great`
|
||||
>
|
||||
> 最终输出:
|
||||
> ```
|
||||
> rust and rust
|
||||
> rust is great
|
||||
> rust is great
|
||||
> ```
|
||||
|
||||
|
||||
### 题目 5-3
|
||||
|
||||
|
|
@ -377,6 +474,11 @@ fn main() {
|
|||
println!("{} {} {}", x, y, z);
|
||||
}
|
||||
```
|
||||
> **[批注] 原分析对引用的理解有严重错误:引用(`&`)不会转移所有权!**
|
||||
>
|
||||
> **对于 `i32`:** `y = &x` 创建的是指向 x 的引用,并没有发生 Copy 或所有权转移。`x` 始终拥有那个 `5`,`y` 和 `z` 只是借用它。代码合法,因为多个不可变引用可以共存,且原值本身也可以在引用存在时使用(前提是 `x` 实现了 `Copy` 或本身没有被移动)。这里 `println!` 读取 `x` 也是合法的,因为 i32 是 Copy,会自动复制。
|
||||
>
|
||||
> **对于 `String`:** 同样,`y = &x` 和 `z = &x` 创建的是不可变引用,没有转移所有权。`x` 仍然持有该 String。代码合法,因为多个不可变引用可以同时存在。原答说"所有权被转移给y和z,x不再有变量所有权"是完全错误的——引用和所有权转移(move)是两回事。
|
||||
|
||||
如果将 `x` 的类型从 `i32` 改为 `String`,同样的结构是否仍然合法?
|
||||
|
||||
|
|
@ -388,10 +490,15 @@ fn main() {
|
|||
println!("{} {} {}", x, y, z);
|
||||
}
|
||||
```
|
||||
> **[批注] 同上一问,引用不会转移所有权。** String 版本同样合法。x 持有 String 的所有权,y 和 z 只是不可变借用。但注意这里 `println!` 中直接使用 `x` 与 String 的情况,由于 `x` 不可变,`println!` 只需要 `&T`,所以编译器会自动将 `x` 视为 `&x`,不会发生移动。如果此处是 `let y = x;`(不带 & 的移动),那 x 的所有权才真正被转移,编译才会失败。
|
||||
|
||||
### 题目 5-4
|
||||
|
||||
Rust 的所有权系统与 C++ 的 RAII、Java 的垃圾回收各自有什么优缺点?请简要对比分析。
|
||||
> **[批注] 原答过于简略且有概念偏差:"C++ RAII和Java的垃圾回收在运行时检查"不够准确。**
|
||||
> - **C++ RAII**:所有权由程序员显式管理(构造/析构、拷贝/移动语义),C++11 引入 `std::unique_ptr` 等智能指针提供了类似 Rust 的部分保护,但编译器不强制检查,野指针、重复释放、use-after-move 等错误仍可能静默通过编译。优点:灵活性极高,零运行时开销。
|
||||
> - **Java GC**:完全无所有权概念,所有对象在堆上分配,由 GC 自动追踪引用并回收。程序员完全不用操心释放。缺点:运行时开销(GC 暂停),无法在编译期保证资源及时释放,内存占用不可预测。
|
||||
> - **Rust 所有权**:编译期静态检查所有权、移动、借用,零运行时开销。内存安全和数据竞争在编译期得到保证。缺点:学习曲线陡峭,某些合法但需要复杂生命周期的模式难以表达。
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "part4"
|
||||
version = "0.1.0"
|
||||
Loading…
Reference in New Issue