41 KiB
Rust 结构体(Struct)练习题
建议先手动写出每道题的答案,再运行代码验证。
目录
一、基础题:判断正误
判断以下代码能否通过编译。如果可以,说明原因;如果不能,指出错误原因。
题目 1-1
struct User {
username: String,
email: String,
active: bool,
}
fn main() {
let user1 = User {
email: String::from("alice@example.com"),
username: String::from("alice"),
active: true,
};
println!("{}", user1.username);
}
答: 可以通过编译,结构体变量类型都正确,使用方式正确
题目 1-2
struct Point(f64, f64);
fn main() {
let p = Point(3.0, 4.0);
println!("x = {}, y = {}", p.0, p.1);
}
答: 可以通过编译,结构体变量类型都正确,使用方式正确
题目 1-3
struct User {
username: String,
email: String,
active: bool,
}
fn main() {
let mut user1 = User {
email: String::from("alice@example.com"),
username: String::from("alice"),
active: true,
};
user1.email = String::from("new_email@example.com");
println!("{}", user1.email);
}
答: 可以通过编译,定义了一个可变结构体,可变结构体变量可以修改
题目 1-4
struct User {
username: String,
email: String,
active: bool,
}
fn main() {
let user1 = User {
email: String::from("alice@example.com"),
username: String::from("alice"),
active: true,
};
user1.email = String::from("new@example.com"); // user1 是不可变的
}
答: 不可以通过编译,定义的
user1是不可变的,不能修改
题目 1-5
struct User {
username: String,
email: String,
active: bool,
}
fn main() {
let user1 = User {
email: String::from("alice@example.com"),
username: String::from("alice"),
active: true,
};
let user2 = User {
email: String::from("bob@example.com"),
..user1
};
println!("user1: {}", user1.username);
println!("user2: {}", user2.username);
}
[批注] 回答正确但原因有误。 "user1 是不可变的"与所有权转移无关——即使
user1是mut,..user1同样会移动非 Copy 字段。真正原因:结构体更新语法..user1会将user1中未显式赋值的非 Copy 字段(username)移动到user2,导致user1整体无法再使用。
题目 1-6
struct User {
username: String,
email: String,
active: bool,
}
fn main() {
let user1 = User {
email: String::from("alice@example.com"),
username: String::from("alice"),
active: true,
};
let user2 = User {
email: String::from("bob@example.com"),
..user1
};
println!("user1 email: {}", user1.email);
println!("user2 email: {}", user2.email);
}
答: 可以通过编译,没有再次使用
user1.username
题目 1-7
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect = Rectangle {
width: 30,
height: 50,
};
println!("{:?}", rect);
}
答: 可以通过编译,使用
#[derive(Debug)]宏,打印结构体变量
题目 1-8
struct Color(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let white = Color(255, 255, 255);
println!("black R: {}, white R: {}", black.0, white.0);
}
答: 可以通过编译,定义和使用方式都正确
二、填空题:补充代码
补全下列代码使其能通过编译并达到期望输出。
题目 2-1:定义结构体并实例化
// 填空:定义一个名为 Book 的结构体,包含 title(String)、author(String)、pages(u32)
fn main() {
let book = ________ {
title: String::from("深入浅出 Rust"),
________: String::from("张三"),
pages: 320,
};
println!("《{}》作者:{},共 {} 页", book.title, book.author, book.pages);
// 期望输出:《深入浅出 Rust》作者:张三,共 320 页
}
答:
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:元组结构体
// 填空:定义一个元组结构体 RGB,包含三个 u8 值(红、绿、蓝)
struct RGB(________, ________, ________);
fn main() {
let red = RGB(255, 0, 0);
let green = RGB(0, 255, 0);
println!("红色: ({}, {}, {})", red.0, red.1, ________);
println!("绿色: ({}, {}, {})", green.0, green.1, green.2);
}
答:
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:结构体更新语法
#[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 复制
};
println!("s1: {:?}, s2: {:?}", s1, s2);
// 注意:s1.score 和 s1.age 在更新后是否还能使用?为什么?
}
答:
#[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:定义方法
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// 填空:定义一个 area 方法,计算矩形的面积
fn ________(&self) -> ________ {
self.width ________ self.height
}
// 填空:定义一个 can_hold 方法,判断当前矩形能否容纳另一个矩形
fn can_hold(&self, other: ________) -> 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 };
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
}
答:
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 };
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
}
题目 2-5:关联函数
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// 填空:定义一个关联函数 square,接收一个边长参数,返回一个正方形 Rectangle
fn ________(size: u32) -> ________ {
Rectangle {
width: size,
height: ________,
}
}
}
fn main() {
let square = Rectangle::square(10);
println!("正方形: {} x {}", square.width, square.height);
// 期望输出:正方形: 10 x 10
}
答:
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);
// 期望输出:正方形: 10 x 10
}
题目 2-6:self / &self / &mut self
struct Counter {
count: u32,
}
impl Counter {
// A:创建新的 Counter,初始值为 0
fn new() -> Counter {
Counter { count: 0 }
}
// B:只读取计数值 —— 参数应该是什么?
fn value(________) -> u32 {
self.count
}
// C:计数 +1 —— 参数应该是什么?
fn increment(________) {
self.count += 1;
}
// D:消费 Counter,返回最终值 —— 参数应该是什么?
fn into_value(________) -> u32 {
self.count
}
}
fn main() {
let mut counter = Counter::new();
counter.increment();
counter.increment();
println!("当前值: {}", counter.value()); // 期望输出:2
println!("最终值: {}", counter.into_value()); // 期望输出:2
// println!("{}", counter.value()); // 如果取消注释会怎样?
}
答:
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();
counter.increment();
println!("当前值: {}", counter.value()); // 期望输出:2
println!("最终值: {}", counter.into_value()); // 期望输出:2
// println!("{}", counter.value()); // 如果取消注释会怎样?
}
三、找出并修复错误
以下每段代码都有编译错误,请指出错误并写出修正后的代码。
题目 3-1
struct User {
username: &str,
email: &str,
active: bool,
}
fn main() {
let user = User {
username: "alice",
email: "alice@example.com",
active: true,
};
println!("{}", user.username);
}
> **[批注] 原分析的诊断不准确。** "定义结构体不能使用引用类型"是错误的——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
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 10, y: 20 };
println!("{}", p); // 想要打印 p
}
// 编译错误,打印要使用{:?}
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 10, y: 20 };
println!("{:?}", p); // 想要打印 p
}
题目 3-3
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);
}
// 错误,方法调用需要使用.
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
struct User {
name: String,
age: u32,
}
impl User {
fn greet(self) {
println!("你好,我是 {},今年 {} 岁", self.name, self.age);
}
}
fn main() {
let user = User {
name: String::from("小明"),
age: 18,
};
user.greet();
user.greet(); // 第二次调用
}
// 错误,第二次调用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("小明"),
age: 18,
};
user.greet();
user.greet(); // 第二次调用
}
题目 3-5
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();
c.set_value(20);
println!("v = {}", v);
}
// 错误,调用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);
}
四、编程题
题目 4-1:学生管理系统基础结构
定义一个 Student 结构体,包含 name(String)、id(u32)、grades(Vec<u32>),并实现以下方法:
struct Student {
name: String,
id: u32,
grades: Vec<u32>,
}
impl Student {
// 1. 关联函数 new:创建一个新学生(成绩列表为空)
fn new(name: String, id: u32) -> Student {
// 你的代码
}
// 2. 添加一门成绩
fn add_grade(&mut self, grade: u32) {
// 你的代码
}
// 3. 计算平均成绩,如果没有成绩则返回 0.0
fn average_grade(&self) -> f64 {
// 你的代码
}
// 4. 判断是否及格(平均分 >= 60)
fn is_passing(&self) -> bool {
// 你的代码
}
}
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 student2 = Student::new(String::from("小红"), 1002);
student2.add_grade(55);
student2.add_grade(58);
println!("{} 是否及格: {}", student2.name, student2.is_passing()); // 期望:false
}
struct Student {
name: String,
id: u32,
grades: Vec<u32>,
}
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::<u32>() 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 方法。
use std::f64::consts::PI;
struct Circle {
radius: f64,
}
struct Rectangle {
width: f64,
height: f64,
}
// 你的代码:为 Circle 实现 area 和 perimeter
// 你的代码:为 Rectangle 实现 area 和 perimeter
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
}
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:温度转换器
使用元组结构体实现温度类型,使不同类型的温度之间不会混淆。
// 定义三个元组结构体:Celsius(f64)、Fahrenheit(f64)、Kelvin(f64)
// 你的代码
impl Celsius {
// 转换为华氏度
fn to_fahrenheit(&self) -> Fahrenheit {
// °F = °C × 9/5 + 32
}
// 转换为开尔文
fn to_kelvin(&self) -> Kelvin {
// K = °C + 273.15
}
}
impl Fahrenheit {
// 转换为摄氏度
fn to_celsius(&self) -> Celsius {
// °C = (°F - 32) × 5/9
}
}
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
}
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:链表节点
使用结构体实现一个单向链表的节点,要求展示你对 Box 和所有权机制的理解。
// 定义一个 ListNode 结构体,包含 value(i32) 和 next(Option<Box<ListNode>>)
// 你的代码
impl ListNode {
// 创建一个新节点
fn new(value: i32) -> ListNode {
// 你的代码
}
// 在链表末尾追加一个节点(递归实现)
fn append(&mut self, value: i32) {
// 你的代码
}
// 将所有节点的值收集到一个 Vec 中
fn collect(&self) -> Vec<i32> {
// 你的代码
}
}
fn main() {
let mut head = ListNode::new(1);
head.append(2);
head.append(3);
let values = head.collect();
println!("链表元素: {:?}", values);
// 期望:链表元素: [1, 2, 3]
}
// 定义一个 ListNode 结构体,包含 value(i32) 和 next(Option<Box<ListNode>>)
struct ListNode {
value: i32,
next: Option<Box<ListNode>>,
}
// 你的代码
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<i32> {
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:购物车
定义一个 ShoppingCart 结构体,包含商品列表(Vec),设计合理的所有权和借用关系。
#[derive(Debug, Clone)]
struct Item {
name: String,
price: f64,
quantity: u32,
}
impl Item {
fn new(name: &str, price: f64, quantity: u32) -> Item {
// 你的代码
}
// 计算该商品总价(单价 × 数量)
fn total(&self) -> f64 {
// 你的代码
}
}
struct ShoppingCart {
items: Vec<Item>,
}
impl ShoppingCart {
// 创建空购物车
fn new() -> ShoppingCart {
// 你的代码
}
// 添加商品
fn add_item(&mut self, item: Item) {
// 你的代码
}
// 计算购物车中所有商品的总价
fn total(&self) -> f64 {
// 你的代码
}
// 获取商品种类数量
fn item_count(&self) -> usize {
// 你的代码
}
// 打印购物车明细(消耗购物车所有权)
fn print_receipt(self) {
// 你的代码:遍历所有商品,打印名称、单价、数量、小计
// 最后打印总价
}
}
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();
// 期望输出:
// === 购物明细 ===
// 键盘 × 1 @ 299.00 = 299.00
// 鼠标 × 2 @ 159.00 = 318.00
// 显示器 × 1 @ 1899.00 = 1899.00
// ----------------------
// 总计: 2516.00
// ======================
// println!("{:?}", cart.item_count()); // 如果取消注释会怎样?为什么?
}
#[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<Item>,
}
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::<f64>()
}
// 获取商品种类数量
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()); // 如果取消注释会怎样?为什么?
}
五、综合思考题
题目 5-1
以下代码为什么能通过编译?user1 的 active 字段是 bool 类型(实现 Copy),而 username 和 email 是 String 类型(未实现 Copy),使用结构体更新语法后,各自的可用性如何?
struct User {
username: String,
email: String,
active: bool,
}
fn main() {
let user1 = User {
email: String::from("alice@example.com"),
username: String::from("alice"),
active: true,
};
let user2 = User {
email: String::from("bob@example.com"),
..user1
};
// 下面哪些行可以取消注释?为什么?
// println!("{}", user1.username);
// println!("{}", user1.email);
// println!("{}", user1.active);
}
答: // println!("{}", user1.email); // println!("{}", user1.active); 可以取消注释,使用结构体更新语法后,user1的username失效,email和active有效
题目 5-2
对比以下三种设计。在什么场景下分别应该使用哪种?
// 设计 A:所有字段都是 String(自有类型)
struct UserA {
name: String,
email: String,
}
// 设计 B:所有字段都是 &str(借用)
struct UserB<'a> {
name: &'a str,
email: &'a str,
}
// 设计 C:混合
struct UserC {
name: String,
email: String,
cached_display: Option<String>, // 缓存
}
[批注] 原答太简略,只描述了"是什么"没回答"什么时候用"。
- 设计 A(全部 String):结构体拥有数据,适合需要独立生命周期、长期持有数据的场景(如从函数返回、存入集合)。额外 heap 分配开销。
- 设计 B(全部
&'a str):仅仅借用外部数据,zero-copy,但受限于引用的生命周期'a,结构体不能比它借用的数据活得更久。适合临时视图、解析器的 AST 节点等。- 设计 C(混合 + 缓存):String 拥有核心数据,
cached_display作为惰性计算的缓存。适合需要性能优化但保留所有权的场景。
题目 5-3
分析以下代码,回答:
self、&self、&mut self三者在调用时的区别是什么?
答:self:方法接收一个结构体的所有权,方法调用结束后,结构体将不再可用;&self:方法接收结构体的引用,方法调用结束后,结构体仍然可用;&mut self:方法接收结构体的可变引用,方法调用结束后,结构体仍然可用,并且允许修改结构体的字段。
- 当调用完
into_value()之后,counter是否还能使用?为什么?
[批注] 基本正确,但表述可更精确:
into_value()的参数是self(不是引用),调用时counter的所有权被移入方法,方法结束self被 drop,返回的u32是 Copy 类型独立于原结构体。"返回所有权"的说法容易混淆——方法不是返回所有权,而是接收所有权。
struct Counter {
count: u32,
}
impl Counter {
fn value(&self) -> u32 {
self.count
}
fn increment(&mut self) {
self.count += 1;
}
fn reset(&mut self) {
self.count = 0;
}
fn into_value(self) -> u32 {
self.count
}
}
fn main() {
let mut counter = Counter { count: 0 };
counter.increment();
counter.increment();
println!("value: {}", counter.value());
let n = counter.into_value();
println!("final: {}", n);
}
题目 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 无关。
struct Circle {
radius: f64,
}
struct Rectangle {
width: f64,
height: f64,
}
impl Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
impl Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
fn main() {
let c = Circle { radius: 3.0 };
let r = Rectangle { width: 4.0, height: 5.0 };
println!("圆面积: {:.2}", c.area()); // Circle::area(&c)
println!("矩形面积: {:.2}", r.area()); // Rectangle::area(&r)
}
题目 5-5
元组结构体 struct Point(i32, i32) 和普通元组 (i32, i32) 有什么区别?在什么场景下应该使用元组结构体而不是普通元组?
[批注] 原答有多处错误。
- "元组结构体的字段顺序不要求与定义一致,使用命名即可" — 错误,元组结构体没有命名字段,只能用索引
.0.1访问。这是与普通结构体的核心区别。- "元组结构体的字段是可变的,普通元组字段是只读的" — 错误,可变性与类型无关,取决于
let mut。正确区分:
- 元组结构体
struct Point(i32, i32)是一个具名的新类型,与struct Vector(i32, i32)是不同型别,不能互相赋值,提供类型安全。可以在其上impl方法和 trait。- 普通元组
(i32, i32)是匿名类型,任何(i32, i32)都可互相赋值,没有类型区分能力。
参考答案
请独立完成再查看答案。
点击展开答案
一、基础题
1-1:✅ 通过编译。结构体中的字段顺序不要求与定义一致,使用命名即可。
1-2:✅ 通过编译。元组结构体使用 .索引 方式访问字段。
1-3:✅ 通过编译。mut 修饰的结构体实例,其所有字段都是可变的。
1-4:❌ 编译错误。user1 不是 mut 的,不能修改其字段。Rust 中可变性是整个结构体级别的,不能只标记某个字段为 mut。
1-5:❌ 编译错误。结构体更新语法 ..user1 会移动 user1 中未实现 Copy 的字段(username 和 email)。user1.username 已被移动到 user2,user1 不再可用。
1-6:✅ 通过编译。..user1 展开时,显式赋值的 email 不会从 user1 移动,只有未显式赋值的非 Copy 字段(username)会被移动。user1.email 仍然有效,user1.active(Copy)也有效。但 user1 整体已部分移动,不能整体使用。
[批注] 此答案修正了原隐藏答案的错误(原答说 ❌ 编译错误)。 结构体更新语法在显式覆盖字段时不会移动该字段。但若
user1.username已移动,user1作为一个整体不能再传递给其他函数。
1-7:✅ 通过编译。#[derive(Debug)] 自动为结构体实现了 Debug trait,允许使用 {:?} 格式化打印。
1-8:✅ 通过编译。元组结构体的实例各自独立,互不影响。
二、填空题
2-1:
struct Book {
title: String,
author: String,
pages: u32,
}
let book = Book {
title: String::from("深入浅出 Rust"),
author: String::from("张三"),
pages: 320,
};
2-2:
struct RGB(u8, u8, u8);
println!("红色: ({}, {}, {})", red.0, red.1, red.2);
2-3:
let s2 = Student {
name: String::from("小红"),
..s1
};
// s1.score 和 s1.age 在更新后是否还能使用?
// — 如果字段类型是 Copy 的(u32),可以继续使用
// — 如果字段类型不是 Copy 的(String),已被移动,不能使用
// 这里 age 和 score 都是 u32(Copy),所以 s1.score 和 s1.age 仍然可用
2-4:
fn area(&self) -> u32 {
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
2-5:
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
2-6:
fn value(&self) -> u32 { self.count } // 不可变借用
fn increment(&mut self) { self.count += 1; } // 可变借用
fn into_value(self) -> u32 { self.count } // 获取所有权
// 最后一行取消注释会报错:into_value() 消费了 counter,之后不能再使用
三、修复错误
3-1:结构体使用了引用字段但没有标注生命周期。修复:
struct User<'a> {
username: &'a str,
email: &'a str,
active: bool,
}
或者改用 String 类型(推荐给初学者):
struct User {
username: String,
email: String,
active: bool,
}
3-2:println!("{}", p) 需要 Display trait,但只 derive 了 Debug。修复:
println!("{:?}", p); // 使用 Debug 格式化
// 或 println!("{}", p) 行不通 → 需改为 println!("{:?}", p)
3-3:方法调用语法错误。rect::area() 应改为 rect.area()。:: 用于关联函数,. 用于方法调用。
3-4:greet(self) 获取了所有权,第一次调用后 user 被消费。修复:
fn greet(&self) { // 改为不可变引用
println!("你好,我是 {},今年 {} 岁", self.name, self.age);
}
3-5:get_value 的不可变借用和 set_value 的可变借用存在生命周期重叠。修复:让 v 在 c.set_value 之前结束使用:
fn main() {
let mut c = Container { value: 10 };
let v = c.get_value();
println!("v = {}", v); // 先使用 v
c.set_value(20); // v 不再使用,可以创建可变借用
}
四、编程题
4-1:
fn new(name: String, id: u32) -> Student {
Student { name, id, grades: Vec::new() }
}
fn add_grade(&mut self, grade: u32) {
self.grades.push(grade);
}
fn average_grade(&self) -> f64 {
if self.grades.is_empty() {
0.0
} else {
self.grades.iter().sum::<u32>() as f64 / self.grades.len() as f64
}
}
fn is_passing(&self) -> bool {
self.average_grade() >= 60.0
}
4-2:
impl Circle {
fn area(&self) -> f64 {
PI * self.radius * self.radius
}
fn perimeter(&self) -> f64 {
2.0 * PI * self.radius
}
}
impl Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
fn perimeter(&self) -> f64 {
2.0 * (self.width + self.height)
}
}
4-3:
struct Celsius(f64);
struct Fahrenheit(f64);
struct Kelvin(f64);
impl Celsius {
fn to_fahrenheit(&self) -> Fahrenheit {
Fahrenheit(self.0 * 9.0 / 5.0 + 32.0)
}
fn to_kelvin(&self) -> Kelvin {
Kelvin(self.0 + 273.15)
}
}
impl Fahrenheit {
fn to_celsius(&self) -> Celsius {
Celsius((self.0 - 32.0) * 5.0 / 9.0)
}
}
4-4:
struct ListNode {
value: i32,
next: Option<Box<ListNode>>,
}
impl ListNode {
fn new(value: i32) -> ListNode {
ListNode { value, next: None }
}
fn append(&mut self, value: i32) {
match &mut self.next {
Some(next_node) => next_node.append(value),
None => self.next = Some(Box::new(ListNode::new(value))),
}
}
fn collect(&self) -> Vec<i32> {
let mut result = vec![self.value];
let mut current = &self.next;
while let Some(node) = current {
result.push(node.value);
current = &node.next;
}
result
}
}
4-5:
impl Item {
fn new(name: &str, price: f64, quantity: u32) -> Item {
Item { name: String::from(name), price, quantity }
}
fn total(&self) -> f64 {
self.price * self.quantity as f64
}
}
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) {
println!("=== 购物明细 ===");
for item in &self.items {
println!("{} × {} @ {:.2} = {:.2}", item.name, item.quantity, item.price, item.total());
}
println!("----------------------");
println!("总计: {:.2}", self.total());
println!("======================");
}
}
// print_receipt(self) 消耗了购物车所有权,之后再调用 item_count() 会报错
五、综合思考题
5-1:
user1.username(String)— 已被..user1移动到user2,不可用user1.email(String)— 在user2中被显式赋予新值,未被移动,可用user1.active(bool,Copy 类型)— 被复制,可用
[批注] 此答案修正了原隐藏答案关于 user1.email 的错误。 显式覆盖的字段不会从原结构体移动。
5-2:
- 设计 A:适合需要拥有数据的场景,如从函数返回新结构体、需要独立生命周期、长期持有的数据
- 设计 B:适合临时借用、只读引用外部数据、需要避免堆分配的场景;缺点是结构体受限于引用的生命周期
- 设计 C:适合需要高性能 + 灵活性的场景,但增加了维护缓存的复杂性
5-3:
&self:不可变借用,只读,可多次调用,调用后实例仍可用&mut self:可变借用,可修改,同时只能有一个,调用后实例仍可用self:获取所有权,可修改或消费,调用后实例被 drop,不可再用into_value()获取了counter的所有权,调用后counter不再可用
5-4:Rust 通过方法调用的接收者类型来区分。c.area() 的接收者是 Circle,调用 Circle::area;r.area() 的接收者是 Rectangle,调用 Rectangle::area。
5-5:
- 元组结构体
Point(i32, i32)是一个命名类型,与Vec2(i32, i32)是不同的类型,不能互相赋值,提供了类型安全 - 普通元组
(i32, i32)是匿名类型,任何(i32, i32)都可以互相赋值 - 使用元组结构体的场景:需要类型安全地区分语义不同的同构数据(如
Celsius(f64)vsFahrenheit(f64)),或为特定类型实现方法和 trait