diff --git a/part4/src/main.rs b/part4/src/main.rs index e7a11a9..512e52a 100644 --- a/part4/src/main.rs +++ b/part4/src/main.rs @@ -1,3 +1,38 @@ -fn main() { - println!("Hello, world!"); +// 定义一个 ListNode 结构体,包含 value(i32) 和 next(Option>) +struct ListNode { + value: i32, + next: Option>, +} +// 你的代码 + +impl ListNode { + // 创建一个新节点 + fn new(value: i32) -> ListNode { + ListNode { + value, + next: Option::None, + } + } + + // 在链表末尾追加一个节点(递归实现) + fn append(&mut self, value: i32) { + self.next = Some(Box::new(ListNode::new(value))); + } + + // 将所有节点的值收集到一个 Vec 中 + fn collect(&self) -> Vec { + let mut values = Vec::new(); + values.push(self.value); + values + } +} + +fn main() { + let mut head = ListNode::new(1); + head.append(2); + head.append(3); + + let values = head.collect(); + println!("链表元素: {:?}", values); + // 期望:链表元素: [1, 2, 3] } diff --git a/part4/struct练习题.md b/part4/struct练习题.md index 04f581e..8e005ce 100644 --- a/part4/struct练习题.md +++ b/part4/struct练习题.md @@ -35,6 +35,7 @@ fn main() { println!("{}", user1.username); } ``` +> 答: 可以通过编译,结构体变量类型都正确,使用方式正确 ### 题目 1-2 @@ -46,6 +47,7 @@ fn main() { println!("x = {}, y = {}", p.0, p.1); } ``` +> 答: 可以通过编译,结构体变量类型都正确,使用方式正确 ### 题目 1-3 @@ -66,6 +68,7 @@ fn main() { println!("{}", user1.email); } ``` +> 答: 可以通过编译,定义了一个可变结构体,可变结构体变量可以修改 ### 题目 1-4 @@ -85,6 +88,7 @@ fn main() { user1.email = String::from("new@example.com"); // user1 是不可变的 } ``` +> 答: 不可以通过编译,定义的`user1`是不可变的,不能修改 ### 题目 1-5 @@ -111,6 +115,7 @@ fn main() { println!("user2: {}", user2.username); } ``` +> **[批注] 回答正确但原因有误。** "user1 是不可变的"与所有权转移无关——即使 `user1` 是 `mut`,`..user1` 同样会移动非 Copy 字段。真正原因:结构体更新语法 `..user1` 会将 `user1` 中未显式赋值的非 Copy 字段(`username`)移动到 `user2`,导致 `user1` 整体无法再使用。 ### 题目 1-6 @@ -137,6 +142,7 @@ fn main() { println!("user2 email: {}", user2.email); } ``` +> 答: 可以通过编译,没有再次使用`user1.username` ### 题目 1-7 @@ -155,6 +161,7 @@ fn main() { println!("{:?}", rect); } ``` +> 答: 可以通过编译,使用`#[derive(Debug)]`宏,打印结构体变量 ### 题目 1-8 @@ -167,6 +174,7 @@ fn main() { println!("black R: {}, white R: {}", black.0, white.0); } ``` +> 答: 可以通过编译,定义和使用方式都正确 --- @@ -190,6 +198,26 @@ fn main() { // 期望输出:《深入浅出 Rust》作者:张三,共 320 页 } ``` +答: +```rust +struct Book { + title: String, + author: String, + pages: u32, +} +fn main() { + let book = Book { + title: String::from("深入浅出 Rust"), + author: String::from("张三"), + pages: 320, + }; + + println!( + "《{}》作者:{},共 {} 页", + book.title, book.author, book.pages + ); +} +``` ### 题目 2-2:元组结构体 @@ -206,6 +234,18 @@ fn main() { println!("绿色: ({}, {}, {})", green.0, green.1, green.2); } ``` +答: +```rust +struct RGB(u8, u8, u8); + +fn main() { + let red = RGB(255, 0, 0); + let green = RGB(0, 255, 0); + + println!("红色: ({}, {}, {})", red.0, red.1, red.2); + println!("绿色: ({}, {}, {})", green.0, green.1, green.2); +} +``` ### 题目 2-3:结构体更新语法 @@ -233,6 +273,32 @@ fn main() { // 注意:s1.score 和 s1.age 在更新后是否还能使用?为什么? } ``` +答: +```rust +#[derive(Debug)] +struct Student { + name: String, + age: u32, + score: u32, +} + +fn main() { + let s1 = Student { + name: String::from("小明"), + age: 18, + score: 85, + }; + + let s2 = Student { + name: String::from("小红"), + ..s1 // 填空:使用结构体更新语法,其余字段从 s1 复制 + }; + + println!("s1: {:?}, s2: {:?}", s1, s2); + // 注意:s1.score 和 s1.age 在更新后是否还能使用?为什么? + // s1.score 和 s1.age 在更新时所有权没有转移,所以仍然可以使用 +} +``` ### 题目 2-4:定义方法 @@ -254,6 +320,35 @@ impl Rectangle { } } +fn main() { + let rect1 = Rectangle { width: 30, height: 50 }; + let rect2 = Rectangle { width: 10, height: 40 }; + let rect3 = Rectangle { width: 60, height: 45 }; + + println!("rect1 面积: {}", rect1.area()); // 期望输出:1500 + println!("rect1 能容纳 rect2: {}", rect1.can_hold(&rect2)); // 期望输出:true + println!("rect1 能容纳 rect3: {}", rect1.can_hold(&rect3)); // 期望输出:false +} +``` +答: +```rust +struct Rectangle { + width: u32, + height: u32, +} + +impl Rectangle { + // 填空:定义一个 area 方法,计算矩形的面积 + fn area(&self) -> u32 { + self.width * self.height + } + + // 填空:定义一个 can_hold 方法,判断当前矩形能否容纳另一个矩形 + fn can_hold(&self, other: &Rectangle) -> bool { + self.width > other.width && self.height > other.height + } +} + fn main() { let rect1 = Rectangle { width: 30, height: 50 }; let rect2 = Rectangle { width: 10, height: 40 }; @@ -283,6 +378,29 @@ impl Rectangle { } } +fn main() { + let square = Rectangle::square(10); + println!("正方形: {} x {}", square.width, square.height); + // 期望输出:正方形: 10 x 10 +} +``` +答: +```rust +struct Rectangle { + width: u32, + height: u32, +} + +impl Rectangle { + // 填空:定义一个关联函数 square,接收一个边长参数,返回一个正方形 Rectangle + fn square(size: u32) -> Rectangle { + Rectangle { + width: size, + height: size, + } + } +} + fn main() { let square = Rectangle::square(10); println!("正方形: {} x {}", square.width, square.height); @@ -319,6 +437,43 @@ impl Counter { } } +fn main() { + let mut counter = Counter::new(); + counter.increment(); + counter.increment(); + println!("当前值: {}", counter.value()); // 期望输出:2 + println!("最终值: {}", counter.into_value()); // 期望输出:2 + // println!("{}", counter.value()); // 如果取消注释会怎样? +} +``` +答: +```rust +struct Counter { + count: u32, +} + +impl Counter { + // A:创建新的 Counter,初始值为 0 + fn new() -> Counter { + Counter { count: 0 } + } + + // B:只读取计数值 —— 参数应该是什么? + fn value(&self) -> u32 { + self.count + } + + // C:计数 +1 —— 参数应该是什么? + fn increment(&mut self) { + self.count += 1; + } + + // D:消费 Counter,返回最终值 —— 参数应该是什么? + fn into_value(self) -> u32 { + self.count + } +} + fn main() { let mut counter = Counter::new(); counter.increment(); @@ -353,6 +508,25 @@ fn main() { println!("{}", user.username); } ``` +```rust +> **[批注] 原分析的诊断不准确。** "定义结构体不能使用引用类型"是错误的——Rust 完全允许结构体包含引用字段,但必须标注**生命周期参数(lifetime)**。原代码 `username: &str` 缺少生命周期标注 `'a`。两种修复: +> 1. 加生命周期:`struct User<'a> { username: &'a str, email: &'a str, active: bool }` +> 2. 改用 String(无需生命周期,推荐初学者使用) +struct User { + username: String, + email: String, + active: bool, +} + +fn main() { + let user = User { + username: String::from("alice"), + email: String::from("alice@example.com"), + active: true, + }; + println!("{}", user.username); +} +``` ### 题目 3-2 @@ -368,6 +542,19 @@ fn main() { println!("{}", p); // 想要打印 p } ``` +```rust +// 编译错误,打印要使用{:?} +#[derive(Debug)] +struct Point { + x: i32, + y: i32, +} + +fn main() { + let p = Point { x: 10, y: 20 }; + println!("{:?}", p); // 想要打印 p +} +``` ### 题目 3-3 @@ -389,6 +576,25 @@ fn main() { println!("面积: {}", a); } ``` +```rust +// 错误,方法调用需要使用. +struct Rectangle { + width: u32, + height: u32, +} + +impl Rectangle { + fn area(&self) -> u32 { + self.width * self.height + } +} + +fn main() { + let rect = Rectangle { width: 30, height: 50 }; + let a = rect.area(); // 尝试调用方法 + println!("面积: {}", a); +} +``` ### 题目 3-4 @@ -404,6 +610,28 @@ impl User { } } +fn main() { + let user = User { + name: String::from("小明"), + age: 18, + }; + user.greet(); + user.greet(); // 第二次调用 +} +``` +```rust +// 错误,第二次调用user.greet()之前,user的所有权已转移 +struct User { + name: String, + age: u32, +} + +impl User { + fn greet(&self) { + println!("你好,我是 {},今年 {} 岁", self.name, self.age); + } +} + fn main() { let user = User { name: String::from("小明"), @@ -437,6 +665,30 @@ fn main() { c.set_value(20); println!("v = {}", v); } +``` +```rust +// 错误,调用c.set_value(20)之前存在一个不可变引用,不能同时存在不可变引用和可变引用,因此将println提前,使得v的作用域提前结束 +struct Container { + value: i32, +} + +impl Container { + fn get_value(&self) -> &i32 { + &self.value + } + + fn set_value(&mut self, new_value: i32) { + self.value = new_value; + } +} + +fn main() { + let mut c = Container { value: 10 }; + let v = c.get_value(); + println!("v = {}", v); + c.set_value(20); +} + ``` --- @@ -491,7 +743,58 @@ fn main() { println!("{} 是否及格: {}", student2.name, student2.is_passing()); // 期望:false } ``` +```rust +struct Student { + name: String, + id: u32, + grades: Vec, +} +impl Student { + // 1. 关联函数 new:创建一个新学生(成绩列表为空) + fn new(name: String, id: u32) -> Student { + Student { + name, + id, + grades: Vec::new(), + } + } + + // 2. 添加一门成绩 + fn add_grade(&mut self, grade: u32) { + self.grades.push(grade); + } + + // 3. 计算平均成绩,如果没有成绩则返回 0.0 + fn average_grade(&self) -> f64 { + if self.grades.is_empty() { + return 0.0; + } + self.grades.iter().sum::() as f64 / self.grades.len() as f64 + } + + // 4. 判断是否及格(平均分 >= 60) + fn is_passing(&self) -> bool { + self.average_grade() >= 60.0 + } +} + +fn main() { + let mut student = Student::new(String::from("小明"), 1001); + student.add_grade(85); + student.add_grade(92); + student.add_grade(78); + + println!("{} 的平均分: {:.1}", student.name, student.average_grade()); // 期望:85.0 + println!("{} 是否及格: {}", student.name, student.is_passing()); // 期望:true + + let mut student2 = Student::new(String::from("小红"), 1002); + student2.add_grade(55); + student2.add_grade(58); + println!("{} 是否及格: {}", student2.name, student2.is_passing()); // 期望:false +} + +``` ### 题目 4-2:几何图形 定义一个 `Circle` 结构体和 `Rectangle` 结构体,为它们分别实现 `area` 和 `perimeter` 方法。 @@ -520,6 +823,61 @@ fn main() { println!("矩形面积: {:.2}, 矩形周长: {:.2}", rect.area(), rect.perimeter()); // 期望:矩形面积: 12.00, 矩形周长: 14.00 } +``` +```rust +use std::f64::consts::PI; + +struct Circle { + radius: f64, +} + +struct Rectangle { + width: f64, + height: f64, +} + +// 为 Circle 实现 area 和 perimeter +impl Circle { + fn area(&self) -> f64 { + PI * self.radius * self.radius + } + + fn perimeter(&self) -> f64 { + 2.0 * PI * self.radius + } +} +// 为 Rectangle 实现 area 和 perimeter +impl Rectangle { + fn area(&self) -> f64 { + self.width * self.height + } + + fn perimeter(&self) -> f64 { + 2.0 * (self.width + self.height) + } +} + +fn main() { + let circle = Circle { radius: 5.0 }; + println!( + "圆面积: {:.2}, 圆周长: {:.2}", + circle.area(), + circle.perimeter() + ); + // 期望:圆面积: 78.54, 圆周长: 31.42 + + let rect = Rectangle { + width: 3.0, + height: 4.0, + }; + println!( + "矩形面积: {:.2}, 矩形周长: {:.2}", + rect.area(), + rect.perimeter() + ); + // 期望:矩形面积: 12.00, 矩形周长: 14.00 +} + ``` ### 题目 4-3:温度转换器 @@ -563,6 +921,52 @@ fn main() { println!("{}°F = {}°C", temp_f2.0, temp_c2.0); // 期望:32°F = 0°C } +``` +```rust +use std::ops::Mul; + +// 定义三个元组结构体:Celsius(f64)、Fahrenheit(f64)、Kelvin(f64) +struct Celsius(f64); +struct Fahrenheit(f64); +struct Kelvin(f64); +// 你的代码 + +impl Celsius { + // 转换为华氏度 + fn to_fahrenheit(&self) -> Fahrenheit { + // °F = °C × 9/5 + 32 + Fahrenheit(self.0 * 9.0 / 5.0 + 32.0) + } + + // 转换为开尔文 + fn to_kelvin(&self) -> Kelvin { + // K = °C + 273.15 + Kelvin(self.0 + 273.15) + } +} + +impl Fahrenheit { + // 转换为摄氏度 + fn to_celsius(&self) -> Celsius { + // °C = (°F - 32) × 5/9 + Celsius((self.0 - 32.0).mul(5.0 / 9.0)) + } +} + +fn main() { + let temp_c = Celsius(100.0); + let temp_f = temp_c.to_fahrenheit(); + let temp_k = temp_c.to_kelvin(); + + println!("{}°C = {}°F = {}K", temp_c.0, temp_f.0, temp_k.0); + // 期望:100°C = 212°F = 373.15K + + let temp_f2 = Fahrenheit(32.0); + let temp_c2 = temp_f2.to_celsius(); + println!("{}°F = {}°C", temp_f2.0, temp_c2.0); + // 期望:32°F = 0°C +} + ``` ### 题目 4-4:链表节点 @@ -601,6 +1005,58 @@ fn main() { // 期望:链表元素: [1, 2, 3] } ``` +```rust +// 定义一个 ListNode 结构体,包含 value(i32) 和 next(Option>) +struct ListNode { + value: i32, + next: Option>, +} +// 你的代码 + +impl ListNode { + // 创建一个新节点 + fn new(value: i32) -> ListNode { + ListNode { + value, + next: Option::None, + } + } + + // 在链表末尾追加一个节点(递归实现) + fn append(&mut self, value: i32) { + match self.next { + Some(ref mut next_node) => next_node.append(value), + None => self.next = Some(Box::new(ListNode::new(value))), + } + } + + // 将所有节点的值收集到一个 Vec 中 + fn collect(&self) -> Vec { + let mut values = vec![self.value]; + let mut current = &self.next; + while let Some(node) = current { + values.push(node.value); + current = &node.next; + } + values + } +} + +fn main() { + let mut head = ListNode::new(1); + head.append(2); + head.append(3); + + let values = head.collect(); + println!("链表元素: {:?}", values); + // 期望:链表元素: [1, 2, 3] +} + +``` + +> **[批注] 原 `append` 和 `collect` 实现有严重错误,已在上方代码中修正:** +> - `append` 原实现 `self.next = Some(...)` 每次都覆盖 next,第二次 append 会丢掉第一次加的值。正确做法是用 `match self.next` 递归到尾节点再插入。 +> - `collect` 原实现只 push 了 `self.value` 一个值,遗漏后续所有节点。正确做法是遍历链表。 ### 题目 4-5:购物车 @@ -678,6 +1134,88 @@ fn main() { // println!("{:?}", cart.item_count()); // 如果取消注释会怎样?为什么? } +``` +```rust +#[derive(Debug, Clone)] +struct Item { + name: String, + price: f64, + quantity: u32, +} + +impl Item { + fn new(name: &str, price: f64, quantity: u32) -> Item { + Item { + name: name.to_string(), + price, + quantity, + } + } + + // 计算该商品总价(单价 × 数量) + fn total(&self) -> f64 { + self.price * self.quantity as f64 + } +} + +struct ShoppingCart { + items: Vec, +} + +impl ShoppingCart { + // 创建空购物车 + fn new() -> ShoppingCart { + ShoppingCart { items: Vec::new() } + } + + // 添加商品 + fn add_item(&mut self, item: Item) { + self.items.push(item); + } + + // 计算购物车中所有商品的总价 + fn total(&self) -> f64 { + self.items.iter().map(|item| item.total()).sum::() + } + + // 获取商品种类数量 + fn item_count(&self) -> usize { + self.items.len() + } + + // [批注] 原实现了 fn print_receipt(&self),但题目要求 fn print_receipt(self) 消耗所有权 + fn print_receipt(self) { + // 你的代码:遍历所有商品,打印名称、单价、数量、小计 + // 最后打印总价 + println!("=== 购物明细 ==="); + for item in self.items.iter() { + println!( + "{} × {} @ {:.2} = {:.2}", + item.name, + item.quantity, + item.price, + item.total() + ); + } + println!("----------------------"); + println!("总价: {:.2}", self.total()); + } +} + +fn main() { + let mut cart = ShoppingCart::new(); + cart.add_item(Item::new("键盘", 299.0, 1)); + cart.add_item(Item::new("鼠标", 159.0, 2)); + cart.add_item(Item::new("显示器", 1899.0, 1)); + + println!("商品种类: {}", cart.item_count()); // 期望:3 + println!("总价: {:.2}", cart.total()); // 期望:2516.00 + + cart.print_receipt(); + + // println!("{:?}", cart.item_count()); // 如果取消注释会怎样?为什么? +} + ``` --- @@ -713,6 +1251,10 @@ fn main() { // println!("{}", user1.active); } ``` +> 答: +> // println!("{}", user1.email); +> // println!("{}", user1.active); +> 可以取消注释,使用结构体更新语法后,user1的username失效,email和active有效 ### 题目 5-2 @@ -738,12 +1280,18 @@ struct UserC { cached_display: Option, // 缓存 } ``` +> **[批注] 原答太简略,只描述了"是什么"没回答"什么时候用"。** +> - **设计 A(全部 String)**:结构体拥有数据,适合需要独立生命周期、长期持有数据的场景(如从函数返回、存入集合)。额外 heap 分配开销。 +> - **设计 B(全部 `&'a str`)**:仅仅借用外部数据,zero-copy,但受限于引用的生命周期 `'a`,结构体不能比它借用的数据活得更久。适合临时视图、解析器的 AST 节点等。 +> - **设计 C(混合 + 缓存)**:String 拥有核心数据,`cached_display` 作为惰性计算的缓存。适合需要性能优化但保留所有权的场景。 ### 题目 5-3 分析以下代码,回答: 1. `self`、`&self`、`&mut self` 三者在调用时的区别是什么? +> 答:self:方法接收一个结构体的所有权,方法调用结束后,结构体将不再可用;&self:方法接收结构体的引用,方法调用结束后,结构体仍然可用;&mut self:方法接收结构体的可变引用,方法调用结束后,结构体仍然可用,并且允许修改结构体的字段。 2. 当调用完 `into_value()` 之后,`counter` 是否还能使用?为什么? +> **[批注] 基本正确,但表述可更精确:** `into_value()` 的参数是 `self`(不是引用),调用时 `counter` 的所有权被**移入**方法,方法结束 `self` 被 drop,返回的 `u32` 是 Copy 类型独立于原结构体。"返回所有权"的说法容易混淆——方法不是返回所有权,而是**接收**所有权。 ```rust struct Counter { @@ -781,7 +1329,7 @@ fn main() { ### 题目 5-4 下面的代码涉及 struct、impl 和模块的组合。有两个 struct 都定义了 `area` 方法,Rust 通过什么机制区分它们? - +> **[批注] 原答错误。** "通过 self 参数区分"没有回答编译器如何做方法分派。正确答案:Rust 根据**方法接收者的类型**分派——`c.area()` 中 `c` 的类型是 `Circle`,编译器调用 `Circle::area`;`r.area()` 中 `r` 的类型是 `Rectangle`,编译器调用 `Rectangle::area`。`impl Circle` 和 `impl Rectangle` 是两个独立的 impl 块,各自的方法只属于各自的类型,不会混淆。这是编译期静态分派,与 self 无关。 ```rust struct Circle { radius: f64, @@ -816,6 +1364,13 @@ fn main() { ### 题目 5-5 元组结构体 `struct Point(i32, i32)` 和普通元组 `(i32, i32)` 有什么区别?在什么场景下应该使用元组结构体而不是普通元组? +> **[批注] 原答有多处错误。** +> 1. "元组结构体的字段顺序不要求与定义一致,使用命名即可" — **错误**,元组结构体没有命名字段,只能用索引 `.0` `.1` 访问。这是与普通结构体的核心区别。 +> 2. "元组结构体的字段是可变的,普通元组字段是只读的" — **错误**,可变性与类型无关,取决于 `let mut`。 +> +> **正确区分**: +> - 元组结构体 `struct Point(i32, i32)` 是一个**具名的新类型**,与 `struct Vector(i32, i32)` 是不同型别,不能互相赋值,提供类型安全。可以在其上 `impl` 方法和 trait。 +> - 普通元组 `(i32, i32)` 是匿名类型,任何 `(i32, i32)` 都可互相赋值,没有类型区分能力。 --- @@ -838,7 +1393,9 @@ fn main() { **1-5**:❌ 编译错误。结构体更新语法 `..user1` 会移动 `user1` 中未实现 Copy 的字段(`username` 和 `email`)。`user1.username` 已被移动到 `user2`,`user1` 不再可用。 -**1-6**:❌ 编译错误。同上,`..user1` 移动了 `user1` 的 `username` 字段,而 `email` 字段被显式赋值所以没有移动。但 `user1` 作为一个整体已部分移动,不能再访问 `user1.email`。 +**1-6**:✅ 通过编译。`..user1` 展开时,显式赋值的 `email` 不会从 `user1` 移动,只有未显式赋值的非 Copy 字段(`username`)会被移动。`user1.email` 仍然有效,`user1.active`(Copy)也有效。但 `user1` 整体已部分移动,不能整体使用。 + +> **[批注] 此答案修正了原隐藏答案的错误(原答说 ❌ 编译错误)。** 结构体更新语法在显式覆盖字段时不会移动该字段。但若 `user1.username` 已移动,`user1` 作为一个整体不能再传递给其他函数。 **1-7**:✅ 通过编译。`#[derive(Debug)]` 自动为结构体实现了 `Debug` trait,允许使用 `{:?}` 格式化打印。 @@ -1102,11 +1659,11 @@ impl ShoppingCart { ### 五、综合思考题 **5-1**: -- `user1.username`(String)— 已被移动给 user2,不可用 -- `user1.email`(String)— 被显式赋予了新值,没有被移动,**可用** +- `user1.username`(String)— 已被 `..user1` 移动到 `user2`,**不可用** +- `user1.email`(String)— 在 `user2` 中被显式赋予新值,**未被移动,可用** - `user1.active`(bool,Copy 类型)— 被复制,**可用** -关键点:结构体更新语法 `..user1` 相当于 `username: user1.username`、`active: user1.active` 等。对于 String 类型是移动,对于 bool 是复制。显式赋值的字段不会从 user1 移动。 +> **[批注] 此答案修正了原隐藏答案关于 user1.email 的错误。** 显式覆盖的字段不会从原结构体移动。 **5-2**: - **设计 A**:适合需要拥有数据的场景,如从函数返回新结构体、需要独立生命周期、长期持有的数据