learning-rust/part4/struct练习题.md

41 KiB
Raw Blame History

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 是不可变的"与所有权转移无关——即使 user1mut..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-6self / &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 结构体,包含 nameStringidu32gradesVec<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 结构体,为它们分别实现 areaperimeter 方法。

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]
}

[批注] 原 appendcollect 实现有严重错误,已在上方代码中修正:

  • 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

以下代码为什么能通过编译?user1active 字段是 bool 类型(实现 CopyusernameemailString 类型(未实现 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

分析以下代码,回答:

  1. self&self&mut self 三者在调用时的区别是什么?

self方法接收一个结构体的所有权方法调用结束后结构体将不再可用&self方法接收结构体的引用方法调用结束后结构体仍然可用&mut self方法接收结构体的可变引用方法调用结束后结构体仍然可用并且允许修改结构体的字段。

  1. 当调用完 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::arear.area()r 的类型是 Rectangle,编译器调用 Rectangle::areaimpl Circleimpl 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) 有什么区别?在什么场景下应该使用元组结构体而不是普通元组?

[批注] 原答有多处错误。

  1. "元组结构体的字段顺序不要求与定义一致,使用命名即可" — 错误,元组结构体没有命名字段,只能用索引 .0 .1 访问。这是与普通结构体的核心区别。
  2. "元组结构体的字段是可变的,普通元组字段是只读的" — 错误,可变性与类型无关,取决于 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 的字段(usernameemail)。user1.username 已被移动到 user2user1 不再可用。

1-6 通过编译。..user1 展开时,显式赋值的 email 不会从 user1 移动,只有未显式赋值的非 Copy 字段(username)会被移动。user1.email 仍然有效,user1.activeCopy也有效。但 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 都是 u32Copy所以 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-2println!("{}", p) 需要 Display trait但只 derive 了 Debug。修复:

println!("{:?}", p);  // 使用 Debug 格式化
// 或 println!("{}", p) 行不通 → 需改为 println!("{:?}", p)

3-3:方法调用语法错误。rect::area() 应改为 rect.area():: 用于关联函数,. 用于方法调用。

3-4greet(self) 获取了所有权,第一次调用后 user 被消费。修复:

fn greet(&self) {  // 改为不可变引用
    println!("你好,我是 {},今年 {} 岁", self.name, self.age);
}

3-5get_value 的不可变借用和 set_value 的可变借用存在生命周期重叠。修复:让 vc.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.usernameString— 已被 ..user1 移动到 user2不可用
  • user1.emailString— 在 user2 中被显式赋予新值,未被移动,可用
  • user1.activeboolCopy 类型)— 被复制,可用

[批注] 此答案修正了原隐藏答案关于 user1.email 的错误。 显式覆盖的字段不会从原结构体移动。

5-2

  • 设计 A:适合需要拥有数据的场景,如从函数返回新结构体、需要独立生命周期、长期持有的数据
  • 设计 B:适合临时借用、只读引用外部数据、需要避免堆分配的场景;缺点是结构体受限于引用的生命周期
  • 设计 C:适合需要高性能 + 灵活性的场景,但增加了维护缓存的复杂性

5-3

  • &self:不可变借用,只读,可多次调用,调用后实例仍可用
  • &mut self:可变借用,可修改,同时只能有一个,调用后实例仍可用
  • self:获取所有权,可修改或消费,调用后实例被 drop不可再用
  • into_value() 获取了 counter 的所有权,调用后 counter 不再可用

5-4Rust 通过方法调用的接收者类型来区分。c.area() 的接收者是 Circle,调用 Circle::arear.area() 的接收者是 Rectangle,调用 Rectangle::area

5-5

  • 元组结构体 Point(i32, i32) 是一个命名类型,与 Vec2(i32, i32) 是不同的类型,不能互相赋值,提供了类型安全
  • 普通元组 (i32, i32) 是匿名类型,任何 (i32, i32) 都可以互相赋值
  • 使用元组结构体的场景:需要类型安全地区分语义不同的同构数据(如 Celsius(f64) vs Fahrenheit(f64)),或为特定类型实现方法和 trait