【Rust入门练中学】第6课:结构体与方法

目录目标:掌握 struct 定义与实例化、impl 块、方法、self 的三种形式、关联函数,理解结构体内存布局、方法解析机制、所有权与结构体的配合,以及实际项目中的常见模式。

目录

  • 一、从元组的痛点说起
  • 二、定义与实例化
    • [1. 基本定义](#1. 基本定义)
    • [2. 实例化](#2. 实例化)
    • [3. 修改字段](#3. 修改字段)
    • [4. 内存布局](#4. 内存布局)
    • [5. 字段初始化简写](#5. 字段初始化简写)
    • [6. 结构体更新语法](#6. 结构体更新语法)
    • [7. 元组结构体](#7. 元组结构体)
    • [8. 单元结构体](#8. 单元结构体)
  • [三、方法与 impl 块](#三、方法与 impl 块)
    • [1. 定义方法](#1. 定义方法)
    • [2. self 的三种形式](#2. self 的三种形式)
    • [3. 方法解析与自动引用](#3. 方法解析与自动引用)
    • [4. 方法 vs 关联函数 vs 自由函数](#4. 方法 vs 关联函数 vs 自由函数)
    • [5. 关联函数与构造器](#5. 关联函数与构造器)
    • [6. Self vs self](#6. Self vs self)
    • [7. 多个 impl 块](#7. 多个 impl 块)
    • [8. 方法链与构建器模式](#8. 方法链与构建器模式)
  • 四、结构体与所有权
    • [1. 结构体拥有字段的所有权](#1. 结构体拥有字段的所有权)
    • [2. 移动结构体](#2. 移动结构体)
    • [3. 部分移动](#3. 部分移动)
    • [4. 结构体持有引用:生命周期](#4. 结构体持有引用:生命周期)
    • [5. #[derive(...)] 常用 trait](#[derive(...)] 常用 trait)
  • 五、实际项目中的结构体模式
    • [1. newtype 模式](#1. newtype 模式)
    • [2. 构建器模式](#2. 构建器模式)
    • [3. 状态机](#3. 状态机)
    • [4. 数据 + 行为的封装](#4. 数据 + 行为的封装)
  • 六、多元化练习题(含答案与解读)
    • [🟢 练习 1(基础):定义并实例化](#🟢 练习 1(基础):定义并实例化)
    • [🟢 练习 2(字段简写):改写代码](#🟢 练习 2(字段简写):改写代码)
    • [🟡 练习 3(方法):给圆加面积方法](#🟡 练习 3(方法):给圆加面积方法)
    • [🟡 练习 4(可变方法):修复代码](#🟡 练习 4(可变方法):修复代码)
    • [🟡 练习 5(关联函数):判断对错](#🟡 练习 5(关联函数):判断对错)
    • [🟡 练习 6(方法链):实现构建器](#🟡 练习 6(方法链):实现构建器)
    • [🔴 练习 7(综合):矩形管理器](#🔴 练习 7(综合):矩形管理器)
    • [🔴 练习 8(所有权与结构体):预测能否编译](#🔴 练习 8(所有权与结构体):预测能否编译)
    • [🔴 练习 9(newtype):类型安全](#🔴 练习 9(newtype):类型安全)
    • [🔴 练习 10(综合):图书管理系统](#🔴 练习 10(综合):图书管理系统)
    • [🔵 思考题 1:方法为什么用 &self 而不是 self?](#🔵 思考题 1:方法为什么用 &self 而不是 self?)
    • [🔵 思考题 2:关联函数和方法有什么区别?](#🔵 思考题 2:关联函数和方法有什么区别?)
    • [🔵 思考题 3:Self 和 self 有什么区别?](#🔵 思考题 3:Self 和 self 有什么区别?)
    • [🔵 思考题 4:为什么 Rust 方法调用能自动加引用?](#🔵 思考题 4:为什么 Rust 方法调用能自动加引用?)
    • [🔵 思考题 5:结构体什么时候该用 String,什么时候用 &str?](#🔵 思考题 5:结构体什么时候该用 String,什么时候用 &str?)
    • 🔵 思考题 6:构建器模式为什么用 `mut self` 而不是 `&mut self`?(#🔵-思考题-6构建器模式为什么用-mut-self-而、方法解析机制、所有权与结构体的配合,以及实际项目中的常见模式。

一、从元组的痛点说起

第3课我们学过元组,它能把多个值打包:

rust 复制代码
let point = (3, 4);

但元组有个致命问题:没有字段名。

rust 复制代码
let user = ("Alice", 30, true);

println!("{}", user.0);   // "Alice"?还是 30?
println!("{}", user.1);   // 30?还是 true?

读代码的人不知道 .0、.1 是什么含义,写代码的人过一周也忘了。更麻烦的是,如果参数顺序写反了:

rust 复制代码
fn create_user(name: &str, age: u32, active: bool) { }

create_user("Alice", 30, true);    // 正确
create_user("Alice", true, 30);    // ❌ 编译错误,但换成同类型参数就抓不住

结构体就是来解决这个问题的。 它把相关数据组织成一个有名字的类型,每个字段都有明确的含义。

rust 复制代码
struct User {
    name: String,
    age: u32,
    active: bool,
}

let user = User {
    name: String::from("Alice"),
    age: 30,
    active: true,
};

从"一堆散落的变量"到"一个有名有姓的类型",这是抽象的第一步。


下面是本课的整体知识结构图:
#mermaid-svg-2HyTDeeb6c6RkGQ4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2HyTDeeb6c6RkGQ4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .error-icon{fill:#552222;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .marker.cross{stroke:#333333;}#mermaid-svg-2HyTDeeb6c6RkGQ4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2HyTDeeb6c6RkGQ4 p{margin:0;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .cluster-label text{fill:#333;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .cluster-label span{color:#333;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .cluster-label span p{background-color:transparent;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .label text,#mermaid-svg-2HyTDeeb6c6RkGQ4 span{fill:#333;color:#333;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .node rect,#mermaid-svg-2HyTDeeb6c6RkGQ4 .node circle,#mermaid-svg-2HyTDeeb6c6RkGQ4 .node ellipse,#mermaid-svg-2HyTDeeb6c6RkGQ4 .node polygon,#mermaid-svg-2HyTDeeb6c6RkGQ4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .rough-node .label text,#mermaid-svg-2HyTDeeb6c6RkGQ4 .node .label text,#mermaid-svg-2HyTDeeb6c6RkGQ4 .image-shape .label,#mermaid-svg-2HyTDeeb6c6RkGQ4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .rough-node .label,#mermaid-svg-2HyTDeeb6c6RkGQ4 .node .label,#mermaid-svg-2HyTDeeb6c6RkGQ4 .image-shape .label,#mermaid-svg-2HyTDeeb6c6RkGQ4 .icon-shape .label{text-align:center;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .node.clickable{cursor:pointer;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .arrowheadPath{fill:#333333;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2HyTDeeb6c6RkGQ4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2HyTDeeb6c6RkGQ4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2HyTDeeb6c6RkGQ4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .cluster text{fill:#333;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .cluster span{color:#333;}#mermaid-svg-2HyTDeeb6c6RkGQ4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2HyTDeeb6c6RkGQ4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .icon-shape,#mermaid-svg-2HyTDeeb6c6RkGQ4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .icon-shape p,#mermaid-svg-2HyTDeeb6c6RkGQ4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .icon-shape .label rect,#mermaid-svg-2HyTDeeb6c6RkGQ4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2HyTDeeb6c6RkGQ4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2HyTDeeb6c6RkGQ4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2HyTDeeb6c6RkGQ4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 元组的痛点:没有字段名
结构体 struct
定义与实例化
方法与 impl 块
所有权与结构体
实际项目模式
基本定义
实例化
修改字段
内存布局
字段简写
更新语法 ..
元组结构体
单元结构体
定义方法
self 三种形式
自动引用
方法 vs 关联函数
关联函数与构造器
Self vs self
多个 impl
方法链与构建器
拥有字段所有权
移动结构体
部分移动
生命周期
derive 常用 trait
newtype 模式
构建器模式
状态机
数据 + 行为封装

二、定义与实例化

1. 基本定义

rust 复制代码
struct User {
    name: String,
    age: u32,
    active: bool,
}

命名惯例:结构体名用大驼峰(PascalCase),字段名用蛇形(snake_case)。

2. 实例化

rust 复制代码
fn main() {
    let user = User {
        name: String::from("Alice"),
        age: 30,
        active: true,
    };

    println!("{}", user.name);   // Alice
    println!("{}", user.age);    // 30
}

字段顺序不必和定义一致,但必须全部赋值,不能缺字段。这是 Rust 的强制要求------不存在"部分初始化"的结构体。

3. 修改字段

要修改字段,实例必须是 mut:

rust 复制代码
let mut user = User {
    name: String::from("Alice"),
    age: 30,
    active: true,
};
user.age = 31;   // ✅

注意:mut 作用于整个实例 ,不能只让某个字段可变。Rust 没有"部分可变"的语法。想细粒度控制,用 Cell、RefCell(后面讲)。

4. 内存布局

rust 复制代码
struct Point {
    x: i32,
    y: i32,
}

Point 在栈上占 8 字节(两个 i32)。字段按声明顺序排列,编译器可能重排优化(减少 padding),但语义上按声明顺序。

如果字段是 String:

rust 复制代码
struct User {
    name: String,    // 栈上 24 字节(指针 8 + 长度 8 + 容量 8)
    age: u32,        // 栈上 4 字节
    active: bool,    // 栈上 1 字节
}

User 本身在栈上,name 的堆数据 在堆上,name 的三个字段(指针、长度、容量)在栈上。

复制代码
栈                                    堆
┌────────────────────────┐
│ User                   │
│  name:                 │
│    ptr ────────────────┼────► ┌─────────┐
│    len: 5              │      │ "Alice" │
│    cap: 5              │      └─────────┘
│  age: 30               │
│  active: true          │
└────────────────────────┘

关键理解 :结构体拥有它所有字段的所有权。User 被 drop 时,name 的堆内存也会被释放。这是 Rust 自动内存管理的核心机制。

下面是 User 结构体的内存布局示意图:
#mermaid-svg-qkndxjuuoNBnme2Q{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qkndxjuuoNBnme2Q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qkndxjuuoNBnme2Q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qkndxjuuoNBnme2Q .error-icon{fill:#552222;}#mermaid-svg-qkndxjuuoNBnme2Q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qkndxjuuoNBnme2Q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qkndxjuuoNBnme2Q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qkndxjuuoNBnme2Q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qkndxjuuoNBnme2Q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qkndxjuuoNBnme2Q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qkndxjuuoNBnme2Q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qkndxjuuoNBnme2Q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qkndxjuuoNBnme2Q .marker.cross{stroke:#333333;}#mermaid-svg-qkndxjuuoNBnme2Q svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qkndxjuuoNBnme2Q p{margin:0;}#mermaid-svg-qkndxjuuoNBnme2Q .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qkndxjuuoNBnme2Q .cluster-label text{fill:#333;}#mermaid-svg-qkndxjuuoNBnme2Q .cluster-label span{color:#333;}#mermaid-svg-qkndxjuuoNBnme2Q .cluster-label span p{background-color:transparent;}#mermaid-svg-qkndxjuuoNBnme2Q .label text,#mermaid-svg-qkndxjuuoNBnme2Q span{fill:#333;color:#333;}#mermaid-svg-qkndxjuuoNBnme2Q .node rect,#mermaid-svg-qkndxjuuoNBnme2Q .node circle,#mermaid-svg-qkndxjuuoNBnme2Q .node ellipse,#mermaid-svg-qkndxjuuoNBnme2Q .node polygon,#mermaid-svg-qkndxjuuoNBnme2Q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qkndxjuuoNBnme2Q .rough-node .label text,#mermaid-svg-qkndxjuuoNBnme2Q .node .label text,#mermaid-svg-qkndxjuuoNBnme2Q .image-shape .label,#mermaid-svg-qkndxjuuoNBnme2Q .icon-shape .label{text-anchor:middle;}#mermaid-svg-qkndxjuuoNBnme2Q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qkndxjuuoNBnme2Q .rough-node .label,#mermaid-svg-qkndxjuuoNBnme2Q .node .label,#mermaid-svg-qkndxjuuoNBnme2Q .image-shape .label,#mermaid-svg-qkndxjuuoNBnme2Q .icon-shape .label{text-align:center;}#mermaid-svg-qkndxjuuoNBnme2Q .node.clickable{cursor:pointer;}#mermaid-svg-qkndxjuuoNBnme2Q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qkndxjuuoNBnme2Q .arrowheadPath{fill:#333333;}#mermaid-svg-qkndxjuuoNBnme2Q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qkndxjuuoNBnme2Q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qkndxjuuoNBnme2Q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qkndxjuuoNBnme2Q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qkndxjuuoNBnme2Q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qkndxjuuoNBnme2Q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qkndxjuuoNBnme2Q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qkndxjuuoNBnme2Q .cluster text{fill:#333;}#mermaid-svg-qkndxjuuoNBnme2Q .cluster span{color:#333;}#mermaid-svg-qkndxjuuoNBnme2Q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-qkndxjuuoNBnme2Q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qkndxjuuoNBnme2Q rect.text{fill:none;stroke-width:0;}#mermaid-svg-qkndxjuuoNBnme2Q .icon-shape,#mermaid-svg-qkndxjuuoNBnme2Q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qkndxjuuoNBnme2Q .icon-shape p,#mermaid-svg-qkndxjuuoNBnme2Q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qkndxjuuoNBnme2Q .icon-shape .label rect,#mermaid-svg-qkndxjuuoNBnme2Q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qkndxjuuoNBnme2Q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qkndxjuuoNBnme2Q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qkndxjuuoNBnme2Q :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 堆
栈
User 实例
name: ptr
name: len
name: cap
age: u32
active: bool
String 数据: 'Alice'

5. 字段初始化简写

当变量名和字段名相同时,可以简写:

rust 复制代码
fn build_user(name: String, age: u32) -> User {
    User {
        name,        // 等价于 name: name
        age,         // 等价于 age: age
        active: true,
    }
}

这是 Rust 的惯用写法,减少重复。构造器里尤其常见。

6. 结构体更新语法

基于已有实例创建新实例,用 .. 展开:

rust 复制代码
let user2 = User {
    name: String::from("Bob"),
    ..user1
};

..user1 表示"剩余字段从 user1 取"。但这里有个关键陷阱------所有权如何转移:

rust 复制代码
let user1 = User {
    name: String::from("Alice"),
    age: 30,
    active: true,
};

let user2 = User {
    age: 25,
    ..user1
};

这里发生的是:

  • name 从 user1 移动 到 user2(String 没有 Copy)
  • age 被显式赋值 25,没动
  • active 从 user1 复制 (bool 是 Copy)

之后 user1.name 不能用了,但 user1.age、user1.active 还能用。这叫部分移动(partial move)。

rust 复制代码
println!("{}", user1.age);      // ✅ 30
// println!("{}", user1.name);  // ❌ 已被移动

想保留 user1 完整 :用 .clone() 或显式赋值。

rust 复制代码
let user2 = User {
    name: user1.name.clone(),   // 显式克隆
    age: 25,
    ..user1
};

判断规则 :..other 会把"未被显式赋值的字段"从 other 里移动或复制。Copy 类型复制,非 Copy 类型移动。一旦某字段被移动,other 就不能整体使用了。

7. 元组结构体

没有字段名,只有类型:

rust 复制代码
struct Point(i32, i32, i32);

fn main() {
    let p = Point(1, 2, 3);
    println!("{}", p.0);   // 1
}

适合字段含义明确、不需要命名的场景,比如坐标、颜色、包装类型。

newtype 模式是元组结构体最有价值的用法:

rust 复制代码
struct Meters(f64);
struct Seconds(f64);

fn speed(d: Meters, t: Seconds) -> f64 {
    d.0 / t.0
}

fn main() {
    let d = Meters(100.0);
    let t = Seconds(9.58);
    println!("{:.2} m/s", speed(d, t));   // 10.44

    // speed(t, d);   // ❌ 类型不匹配,编译报错
}

Meters 和 Seconds 底层都是 f64,但类型不同。用不同类型包装,编译器帮你区分"米"和"秒",零运行时开销地防止单位混用。这是类型安全的经典技巧,比注释和命名更可靠。

8. 单元结构体

没有任何字段:

rust 复制代码
struct AlwaysEqual;

用于实现 trait 但不需要存数据的场景。比如标记类型、错误类型:

rust 复制代码
struct ParseError;

impl std::fmt::Display for ParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "解析失败")
    }
}

单元结构体在运行时不占空间,编译器会优化掉。


三、方法与 impl 块

1. 定义方法

用 impl 给结构体定义方法:

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 };
    println!("{}", rect.area());   // 1500
}

&self 是 self: &Self 的简写,表示借用实例。方法用 rect.area() 调用。

2. self 的三种形式

这是本课最重要的概念。self 的形式决定了方法对实例的所有权:

&self:不可变借用,只读。最常用。

rust 复制代码
fn area(&self) -> u32 {
    self.width * self.height
}

&mut self:可变借用,能修改字段。

rust 复制代码
fn scale(&mut self, factor: u32) {
    self.width *= factor;
    self.height *= factor;
}

调用时需要实例是 mut:

rust 复制代码
let mut rect = Rectangle { width: 30, height: 50 };
rect.scale(2);

self:获取所有权,方法调用后实例失效。用于消费实例、返回新类型。

rust 复制代码
fn into_square(self) -> Rectangle {
    Rectangle { width: self.width, height: self.width }
}

调用后原实例不能再用:

rust 复制代码
let rect = Rectangle { width: 30, height: 50 };
let square = rect.into_square();
// println!("{}", rect.width);   // ❌ rect 已被移动

方法名惯例(社区约定,不是语法强制):

  • into_xxx:消耗 self,返回新类型
  • as_xxx:返回借用,如 as_str()
  • to_xxx:返回新值,如 to_string()
  • is_xxx:返回 bool,如 is_empty()

下面是 self 三种形式的对比图:
#mermaid-svg-7qabIahSVOE8iBY4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7qabIahSVOE8iBY4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7qabIahSVOE8iBY4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7qabIahSVOE8iBY4 .error-icon{fill:#552222;}#mermaid-svg-7qabIahSVOE8iBY4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7qabIahSVOE8iBY4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7qabIahSVOE8iBY4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7qabIahSVOE8iBY4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7qabIahSVOE8iBY4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7qabIahSVOE8iBY4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7qabIahSVOE8iBY4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7qabIahSVOE8iBY4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7qabIahSVOE8iBY4 .marker.cross{stroke:#333333;}#mermaid-svg-7qabIahSVOE8iBY4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7qabIahSVOE8iBY4 p{margin:0;}#mermaid-svg-7qabIahSVOE8iBY4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7qabIahSVOE8iBY4 .cluster-label text{fill:#333;}#mermaid-svg-7qabIahSVOE8iBY4 .cluster-label span{color:#333;}#mermaid-svg-7qabIahSVOE8iBY4 .cluster-label span p{background-color:transparent;}#mermaid-svg-7qabIahSVOE8iBY4 .label text,#mermaid-svg-7qabIahSVOE8iBY4 span{fill:#333;color:#333;}#mermaid-svg-7qabIahSVOE8iBY4 .node rect,#mermaid-svg-7qabIahSVOE8iBY4 .node circle,#mermaid-svg-7qabIahSVOE8iBY4 .node ellipse,#mermaid-svg-7qabIahSVOE8iBY4 .node polygon,#mermaid-svg-7qabIahSVOE8iBY4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7qabIahSVOE8iBY4 .rough-node .label text,#mermaid-svg-7qabIahSVOE8iBY4 .node .label text,#mermaid-svg-7qabIahSVOE8iBY4 .image-shape .label,#mermaid-svg-7qabIahSVOE8iBY4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-7qabIahSVOE8iBY4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7qabIahSVOE8iBY4 .rough-node .label,#mermaid-svg-7qabIahSVOE8iBY4 .node .label,#mermaid-svg-7qabIahSVOE8iBY4 .image-shape .label,#mermaid-svg-7qabIahSVOE8iBY4 .icon-shape .label{text-align:center;}#mermaid-svg-7qabIahSVOE8iBY4 .node.clickable{cursor:pointer;}#mermaid-svg-7qabIahSVOE8iBY4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7qabIahSVOE8iBY4 .arrowheadPath{fill:#333333;}#mermaid-svg-7qabIahSVOE8iBY4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7qabIahSVOE8iBY4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7qabIahSVOE8iBY4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7qabIahSVOE8iBY4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7qabIahSVOE8iBY4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7qabIahSVOE8iBY4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7qabIahSVOE8iBY4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7qabIahSVOE8iBY4 .cluster text{fill:#333;}#mermaid-svg-7qabIahSVOE8iBY4 .cluster span{color:#333;}#mermaid-svg-7qabIahSVOE8iBY4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7qabIahSVOE8iBY4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7qabIahSVOE8iBY4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-7qabIahSVOE8iBY4 .icon-shape,#mermaid-svg-7qabIahSVOE8iBY4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7qabIahSVOE8iBY4 .icon-shape p,#mermaid-svg-7qabIahSVOE8iBY4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7qabIahSVOE8iBY4 .icon-shape .label rect,#mermaid-svg-7qabIahSVOE8iBY4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7qabIahSVOE8iBY4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7qabIahSVOE8iBY4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7qabIahSVOE8iBY4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 方法接收者
&self
&mut self
self
不可变借用
只读访问
调用后实例仍可用
可变借用
可修改字段
调用后实例仍可用
获取所有权
可修改字段
调用后实例失效
最常用
需要修改时用
消费式 API

3. 方法解析与自动引用

Rust 调用方法时会自动加引用或解引用,匹配方法签名:

rust 复制代码
struct Point {
    x: i32,
    y: i32,
}

impl Point {
    fn get_x(&self) -> i32 {
        self.x
    }
}

fn main() {
    let p = Point { x: 1, y: 2 };
    let r = &p;

    p.get_x();    // 等价于 Point::get_x(&p)
    r.get_x();    // 等价于 Point::get_x(r)
    (&p).get_x(); // 同上
}

编译器在调用 x.method() 时,会依次尝试:

  1. x.method() ------ x 本身
  2. (&x).method() ------ 加不可变引用
  3. (&mut x).method() ------ 加可变引用
  4. 如果 x 是指针,解引用后重复

找到匹配的方法签名就停止。

为什么这样设计? 因为 Rust 的所有权和借用是显式的。如果方法调用也要手动加 &,代码会很啰嗦:

rust 复制代码
// 没有自动引用的话:
(&rect).area();
(&mut rect).scale(2);

自动引用让 . 调用更自然,同时不改变语义------编译器只是帮你补上引用,借用规则依然严格。

对比 C++ :C++ 里 p->method() 和 p.method() 有明确区别,需要程序员自己选。Rust 统一用 .,编译器处理细节。这让代码更简洁,但也意味着看代码时要留意方法签名是 &self 还是 self。

4. 方法 vs 关联函数 vs 自由函数

rust 复制代码
// 方法:第一个参数是 self
impl Rectangle {
    fn area(&self) -> u32 { ... }
}
rect.area();

// 关联函数:没有 self,用 :: 调用
impl Rectangle {
    fn new(w: u32, h: u32) -> Rectangle { ... }
}
Rectangle::new(3, 4);

// 自由函数:不在 impl 里
fn area(rect: &Rectangle) -> u32 { ... }
area(&rect);

三者功能上可以互相转换。方法让调用更自然(rect.area() 比 area(&rect) 更像"矩形算面积")。选择哪种取决于 API 语义:

  • 和实例强相关 → 方法
  • 构造实例 → 关联函数
  • 通用工具 → 自由函数

5. 关联函数与构造器

关联函数常用于构造器:

rust 复制代码
impl Rectangle {
    fn new(width: u32, height: u32) -> Rectangle {
        Rectangle { width, height }
    }

    fn square(size: u32) -> Rectangle {
        Rectangle { width: size, height: size }
    }
}

fn main() {
    let r = Rectangle::new(30, 50);
    let s = Rectangle::square(10);
}

Rust 没有专门的构造函数语法(不像 C++ 的 constructor 或 Java 的 new),new 只是社区约定。标准库也遵循这个约定,比如 String::new()、Vec::new()。

返回 Self 而非 Rectangle:更灵活,类型改名时不用改。

rust 复制代码
fn new(width: u32, height: u32) -> Self {
    Self { width, height }
}

6. Self vs self

Self 是类型 的别名,指当前 impl 的目标类型。self 是实例的别名,指方法接收者。

rust 复制代码
impl Rectangle {
    fn new(w: u32, h: u32) -> Self {   // Self 就是 Rectangle
        Self { width: w, height: h }    // 用 Self 构造
    }

    fn area(&self) -> u32 {             // self 是当前实例
        self.width * self.height
    }
}

记忆方法 :大写 Self 是类型,小写 self 是值。类比 Python 的 cls 和 self,或 Java 的 ClassName 和 this。

7. 多个 impl 块

同一个结构体可以有多个 impl 块,功能上等价,只是组织代码用:

rust 复制代码
impl Rectangle {
    fn area(&self) -> u32 { self.width * self.height }
}

impl Rectangle {
    fn new(w: u32, h: u32) -> Rectangle { Rectangle { width: w, height: h } }
}

常见用途:把构造器、查询方法、修改方法分开组织,或为 trait 实现单独开 impl 块。编译器会把它们合并。

8. 方法链与构建器模式

每个返回 Self 或 &mut Self 的方法可以链式调用:

rust 复制代码
struct Builder {
    name: String,
    age: u32,
}

impl Builder {
    fn new() -> Self {
        Builder { name: String::new(), age: 0 }
    }

    fn name(mut self, name: &str) -> Self {
        self.name = name.to_string();
        self
    }

    fn age(mut self, age: u32) -> Self {
        self.age = age;
        self
    }

    fn build(self) -> (String, u32) {
        (self.name, self.age)
    }
}

fn main() {
    let (name, age) = Builder::new()
        .name("Alice")
        .age(30)
        .build();
    println!("{} {}", name, age);
}

这是构建器模式 (Builder Pattern)。每个设置方法消耗 self,修改后返回 self,形成链。mut self 表示参数是可变的所有权。实际项目里很常见,比如 std::process::Command:

rust 复制代码
Command::new("ls")
    .arg("-l")
    .arg("-a")
    .spawn();

为什么用 mut self 而不是 &mut self? 因为构建器通常是一次性的,消耗 self 可以避免生命周期纠缠,也支持"移动语义"的链式调用。&mut self 也能链式,但需要 let mut 绑定,更啰嗦。


下面是构建器模式的调用流程:
Builder 调用方 Builder 调用方 #mermaid-svg-sf8PgMblLvNNcqQu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-sf8PgMblLvNNcqQu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-sf8PgMblLvNNcqQu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-sf8PgMblLvNNcqQu .error-icon{fill:#552222;}#mermaid-svg-sf8PgMblLvNNcqQu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sf8PgMblLvNNcqQu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-sf8PgMblLvNNcqQu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sf8PgMblLvNNcqQu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sf8PgMblLvNNcqQu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-sf8PgMblLvNNcqQu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sf8PgMblLvNNcqQu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sf8PgMblLvNNcqQu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sf8PgMblLvNNcqQu .marker.cross{stroke:#333333;}#mermaid-svg-sf8PgMblLvNNcqQu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sf8PgMblLvNNcqQu p{margin:0;}#mermaid-svg-sf8PgMblLvNNcqQu .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-sf8PgMblLvNNcqQu text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-sf8PgMblLvNNcqQu .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-sf8PgMblLvNNcqQu .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-sf8PgMblLvNNcqQu .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-sf8PgMblLvNNcqQu .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-sf8PgMblLvNNcqQu #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-sf8PgMblLvNNcqQu .sequenceNumber{fill:white;}#mermaid-svg-sf8PgMblLvNNcqQu #sequencenumber{fill:#333;}#mermaid-svg-sf8PgMblLvNNcqQu #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-sf8PgMblLvNNcqQu .messageText{fill:#333;stroke:none;}#mermaid-svg-sf8PgMblLvNNcqQu .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-sf8PgMblLvNNcqQu .labelText,#mermaid-svg-sf8PgMblLvNNcqQu .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-sf8PgMblLvNNcqQu .loopText,#mermaid-svg-sf8PgMblLvNNcqQu .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-sf8PgMblLvNNcqQu .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-sf8PgMblLvNNcqQu .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-sf8PgMblLvNNcqQu .noteText,#mermaid-svg-sf8PgMblLvNNcqQu .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-sf8PgMblLvNNcqQu .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-sf8PgMblLvNNcqQu .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-sf8PgMblLvNNcqQu .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-sf8PgMblLvNNcqQu .actorPopupMenu{position:absolute;}#mermaid-svg-sf8PgMblLvNNcqQu .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-sf8PgMblLvNNcqQu .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-sf8PgMblLvNNcqQu .actor-man circle,#mermaid-svg-sf8PgMblLvNNcqQu line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-sf8PgMblLvNNcqQu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} new() Self name("Alice") Self age(30) Self build() (String, u32)

四、结构体与所有权

1. 结构体拥有字段的所有权

rust 复制代码
struct User {
    name: String,
}

fn main() {
    let user = User { name: String::from("Alice") };
    // user 拥有 name 的所有权
}   // user 被 drop,name 的堆内存也被释放

2. 移动结构体

rust 复制代码
fn main() {
    let user1 = User { name: String::from("Alice") };
    let user2 = user1;              // 移动
    // println!("{}", user1.name);  // ❌
    println!("{}", user2.name);     // ✅
}

结构体整体移动,之后旧变量失效。这是第4课所有权规则在结构体上的应用。

3. 部分移动

可以只移动结构体的某些字段:

rust 复制代码
let user = User {
    name: String::from("Alice"),
    age: 30,
};

let name = user.name;    // name 被移动
println!("{}", user.age); // ✅ age 还能用
// println!("{}", user.name); // ❌

部分移动后,整个结构体不能再用,但未被移动的字段可以单独访问。这个规则在 .. 更新语法里经常遇到。

4. 结构体持有引用:生命周期

结构体字段可以是引用,但需要生命周期标注:

rust 复制代码
struct Excerpt<'a> {
    part: &'a str,
}

fn main() {
    let s = String::from("hello world");
    let ex = Excerpt { part: &s[..5] };
    println!("{}", ex.part);
}

'a 表示 Excerpt 不能活得比它借用的 s 更长。这是第10课生命周期在结构体上的应用。

初学者建议 :优先用所有权字段(String 而非 &str,Vec<T> 而非 &[T]),遇到性能问题再考虑引用。生命周期标注是 Rust 学习曲线的陡坡,不必一开始就纠结。

5. #[derive(...)] 常用 trait

rust 复制代码
#[derive(Debug, Clone, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = p1.clone();               // Clone
    println!("{:?}", p1);              // Debug
    println!("{}", p1 == p2);          // PartialEq
}

常用:

  • Debug:{:?} 打印,调试必备
  • Clone:.clone()
  • Copy:赋值复制(要求所有字段都是 Copy)
  • PartialEq、Eq:== 比较
  • PartialOrd、Ord:<、> 比较
  • Default:Default::default()
  • Hash:用作 HashMap 键

注意 :Copy 和 Clone 不能随便加。String 字段不能 Copy,只能 Clone。加 Copy 要求所有字段都实现 Copy。如果结构体有堆数据,加 Copy 会编译报错。


五、实际项目中的结构体模式

1. newtype 模式

用元组结构体包装一个类型,增加类型安全:

rust 复制代码
struct UserId(u64);
struct OrderId(u64);

fn get_user(id: UserId) { }
fn get_order(id: OrderId) { }

fn main() {
    let uid = UserId(1);
    let oid = OrderId(2);
    get_user(uid);
    get_order(oid);
    // get_user(oid);   // ❌ 类型不匹配
}

这是零成本的类型安全------运行时没有额外开销,编译器帮你防止混用。实际项目里常用于 ID、单位、敏感字符串(如密码)的包装。

2. 构建器模式

上面已演示。适合字段多、有可选字段的场景,比一长串参数更清晰:

rust 复制代码
// 不用构建器:
Server::new("localhost", 8080, true, false, 30, None)

// 用构建器:
Server::builder()
    .host("localhost")
    .port(8080)
    .tls(true)
    .timeout(30)
    .build()

3. 状态机

用枚举加结构体表达状态机:

rust 复制代码
enum State {
    Idle,
    Running { pid: u32 },
    Stopped,
}

struct Process {
    name: String,
    state: State,
}

impl Process {
    fn start(&mut self, pid: u32) {
        self.state = State::Running { pid };
    }
}

这是第7课枚举和第6课结构体的组合,实际项目里很常见(网络连接、订单、任务调度)。

下面是状态机的状态转换图:
#mermaid-svg-hjEhFvxdU6kA7mih{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hjEhFvxdU6kA7mih .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hjEhFvxdU6kA7mih .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hjEhFvxdU6kA7mih .error-icon{fill:#552222;}#mermaid-svg-hjEhFvxdU6kA7mih .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hjEhFvxdU6kA7mih .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hjEhFvxdU6kA7mih .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hjEhFvxdU6kA7mih .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hjEhFvxdU6kA7mih .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hjEhFvxdU6kA7mih .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hjEhFvxdU6kA7mih .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hjEhFvxdU6kA7mih .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hjEhFvxdU6kA7mih .marker.cross{stroke:#333333;}#mermaid-svg-hjEhFvxdU6kA7mih svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hjEhFvxdU6kA7mih p{margin:0;}#mermaid-svg-hjEhFvxdU6kA7mih defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-hjEhFvxdU6kA7mih g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-hjEhFvxdU6kA7mih g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-hjEhFvxdU6kA7mih g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-hjEhFvxdU6kA7mih g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-hjEhFvxdU6kA7mih g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-hjEhFvxdU6kA7mih .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-hjEhFvxdU6kA7mih .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-hjEhFvxdU6kA7mih .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-hjEhFvxdU6kA7mih .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-hjEhFvxdU6kA7mih .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-hjEhFvxdU6kA7mih .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-hjEhFvxdU6kA7mih .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-hjEhFvxdU6kA7mih .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hjEhFvxdU6kA7mih .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hjEhFvxdU6kA7mih .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hjEhFvxdU6kA7mih .edgeLabel .label text{fill:#333;}#mermaid-svg-hjEhFvxdU6kA7mih .label div .edgeLabel{color:#333;}#mermaid-svg-hjEhFvxdU6kA7mih .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-hjEhFvxdU6kA7mih .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-hjEhFvxdU6kA7mih .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-hjEhFvxdU6kA7mih .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-hjEhFvxdU6kA7mih .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-hjEhFvxdU6kA7mih .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hjEhFvxdU6kA7mih .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hjEhFvxdU6kA7mih #statediagram-barbEnd{fill:#333333;}#mermaid-svg-hjEhFvxdU6kA7mih .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hjEhFvxdU6kA7mih .cluster-label,#mermaid-svg-hjEhFvxdU6kA7mih .nodeLabel{color:#131300;}#mermaid-svg-hjEhFvxdU6kA7mih .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-hjEhFvxdU6kA7mih .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-hjEhFvxdU6kA7mih .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-hjEhFvxdU6kA7mih .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-hjEhFvxdU6kA7mih .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-hjEhFvxdU6kA7mih .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-hjEhFvxdU6kA7mih .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-hjEhFvxdU6kA7mih .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-hjEhFvxdU6kA7mih .note-edge{stroke-dasharray:5;}#mermaid-svg-hjEhFvxdU6kA7mih .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-hjEhFvxdU6kA7mih .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-hjEhFvxdU6kA7mih .statediagram-note text{fill:black;}#mermaid-svg-hjEhFvxdU6kA7mih .statediagram-note .nodeLabel{color:black;}#mermaid-svg-hjEhFvxdU6kA7mih .statediagram .edgeLabel{color:red;}#mermaid-svg-hjEhFvxdU6kA7mih #dependencyStart,#mermaid-svg-hjEhFvxdU6kA7mih #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-hjEhFvxdU6kA7mih .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hjEhFvxdU6kA7mih :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} start(pid)
stop()
start(pid)
reset()
Idle
Running
Stopped

4. 数据 + 行为的封装

结构体把数据和操作数据的方法绑在一起:

rust 复制代码
struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Self {
        Counter { count: 0 }
    }

    fn increment(&mut self) {
        self.count += 1;
    }

    fn value(&self) -> u32 {
        self.count
    }
}

调用方不需要知道 count 的内部表示,只用 increment 和 value。这就是封装。Rust 没有 private 关键字,但字段默认是模块私有的(第12课模块系统会讲)。


六、多元化练习题(含答案与解读)

🟢 练习 1(基础):定义并实例化

要求 :定义 Book 结构体,字段 title: String、pages: u32,创建实例并打印两个字段。
参考答案

rust 复制代码
struct Book {
    title: String,
    pages: u32,
}

fn main() {
    let book = Book {
        title: String::from("Rust 入门"),
        pages: 300,
    };
    println!("{} 有 {} 页", book.title, book.pages);
}

解读 :String 字段用 String::from 创建,u32 直接写数字。访问用 book.title、book.pages。结构体本身在栈上,title 的堆数据在堆上。


🟢 练习 2(字段简写):改写代码

用字段初始化简写重写:

rust 复制代码
fn make_point(x: i32, y: i32) -> Point {
    Point { x: x, y: y }
}

参考答案

rust 复制代码
fn make_point(x: i32, y: i32) -> Point {
    Point { x, y }
}

解读 :变量名与字段名相同时可省略 : x。这是 Rust 的常见写法,减少重复。构造器里尤其常见。


🟡 练习 3(方法):给圆加面积方法

要求 :定义 Circle 结构体(字段 radius: f64),实现 area(&self) -> f64 方法,返回 πr²。用 std::f64::consts::PI。
参考答案

rust 复制代码
use std::f64::consts::PI;

struct Circle {
    radius: f64,
}

impl Circle {
    fn area(&self) -> f64 {
        PI * self.radius * self.radius
    }
}

fn main() {
    let c = Circle { radius: 2.0 };
    println!("{:.2}", c.area());   // 12.57
}

解读 :&self 是只读借用,不需要 mut。PI 从标准库引入,是 f64 常量。{:.2} 保留两位小数。方法名 area 和字段名不冲突,因为一个是方法一个是字段,调用方式不同(c.area() vs c.radius)。


🟡 练习 4(可变方法):修复代码

rust 复制代码
struct Counter {
    count: u32,
}

impl Counter {
    fn increment(&self) {
        self.count += 1;
    }
}

参考答案

rust 复制代码
struct Counter {
    count: u32,
}

impl Counter {
    fn increment(&mut self) {
        self.count += 1;
    }
}

fn main() {
    let mut c = Counter { count: 0 };
    c.increment();
    c.increment();
    println!("{}", c.count);   // 2
}

解读 :要修改字段必须用 &mut self,同时实例声明为 mut。三处呼应:字段可变、方法可变、实例可变。缺一不可。


🟡 练习 5(关联函数):判断对错

以下代码能编译吗?

rust 复制代码
struct Point {
    x: i32,
    y: i32,
}

impl Point {
    fn origin() -> Point {
        Point { x: 0, y: 0 }
    }

    fn print(&self) {
        println!("({}, {})", self.x, self.y);
    }
}

fn main() {
    let p = Point::origin();
    p.print();
    Point::print(&p);
}

参考答案

不能编译。

解读 :Point::origin() 和 p.print() 都合法。但 Point::print(&p) 不合法------带 self 的方法必须用点号调用,不能像关联函数那样用 :: 显式传 self。编译器报错 this function takes 1 argument but 0 arguments were supplied 或提示用 . 调用。:: 只能用于关联函数(无 self)。

例外 :Point::print(&p) 在 trait 方法上可以用 UFCS(Universal Function Call Syntax),但这是高级用法,这里不展开。


🟡 练习 6(方法链):实现构建器

要求 :为 Person 实现构建器,支持 Person::new().name("Alice").age(30).build(),返回 Person。
参考答案

rust 复制代码
struct Person {
    name: String,
    age: u32,
}

impl Person {
    fn new() -> Self {
        Person { name: String::new(), age: 0 }
    }

    fn name(mut self, name: &str) -> Self {
        self.name = name.to_string();
        self
    }

    fn age(mut self, age: u32) -> Self {
        self.age = age;
        self
    }

    fn build(self) -> Person {
        self
    }
}

fn main() {
    let p = Person::new()
        .name("Alice")
        .age(30)
        .build();
    println!("{} {}", p.name, p.age);
}

解读 :每个设置方法消耗 self,修改后返回 self,形成链。mut self 表示参数是可变的所有权。build 只是返回 self,实际项目里可以在这里做校验。这是构建器模式的标准写法。


🔴 练习 7(综合):矩形管理器

要求:

  • 定义 Rectangle,字段 width: u32、height: u32
  • 关联函数 new(w, h) 构造
  • 方法 area(&self) 返回面积
  • 方法 scale(&mut self, factor) 按倍数放大
  • 方法 is_square(&self) 返回是否正方形
  • 方法 into_square(self) 消耗自身返回正方形
  • 在 main 中演示全部

参考答案

rust 复制代码
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn new(width: u32, height: u32) -> Self {
        Rectangle { width, height }
    }

    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn scale(&mut self, factor: u32) {
        self.width *= factor;
        self.height *= factor;
    }

    fn is_square(&self) -> bool {
        self.width == self.height
    }

    fn into_square(self) -> Rectangle {
        let side = self.width.max(self.height);
        Rectangle { width: side, height: side }
    }
}

fn main() {
    let mut r = Rectangle::new(3, 4);
    println!("面积: {}", r.area());          // 12
    println!("正方形? {}", r.is_square());   // false

    r.scale(2);
    println!("放大后: {:?}", r);             // 6x8

    let s = r.into_square();
    println!("变成正方形: {:?}", s);          // 8x8
    // println!("{:?}", r);                   // ❌ r 已被移动
}

解读 :new 用 Self 返回类型。scale 用 &mut self 修改,area、is_square 用 &self 只读。into_square 用 self 消耗实例,返回新 Rectangle。#[derive(Debug)] 让 {:?} 能打印。max 是 u32 的方法,取两个值中较大者。这个例子覆盖了 self 的三种形式。


🔴 练习 8(所有权与结构体):预测能否编译

rust 复制代码
struct User {
    name: String,
    age: u32,
}

fn main() {
    let u1 = User { name: String::from("Alice"), age: 30 };
    let u2 = User { name: String::from("Bob"), ..u1 };
    println!("{}", u1.name);
}

参考答案

能编译。

解读 :..u1 会移动 u1 中未被显式赋值的字段。这里 name 被显式赋为 "Bob",所以 name 没被移动;age 是 u32(Copy),复制而非移动。所以 u1.name 仍然可用。

如果写成 User { age: 25, ..u1 },则 name 从 u1 移动,u1.name 就不能再用了。关键看哪个字段被 .. 拿走。Copy 字段不受影响。这是部分移动的典型场景。


🔴 练习 9(newtype):类型安全

要求 :定义 Meters(f64) 和 Seconds(f64),写函数 speed(d: Meters, t: Seconds) -> f64,返回速度。演示类型不匹配会编译失败。
参考答案

rust 复制代码
struct Meters(f64);
struct Seconds(f64);

fn speed(d: Meters, t: Seconds) -> f64 {
    d.0 / t.0
}

fn main() {
    let d = Meters(100.0);
    let t = Seconds(9.58);
    println!("{:.2} m/s", speed(d, t));   // 10.44

    // let wrong = speed(t, d);   // ❌ 类型不匹配
}

解读 :Meters 和 Seconds 底层都是 f64,但类型不同。speed(t, d) 会编译失败,因为参数类型不匹配。这是 newtype 模式的核心价值:零运行时开销的类型安全。编译器帮你防止单位混用,比注释和命名更可靠。


🔴 练习 10(综合):图书管理系统

要求 :定义 Book(title、author、year)和 Library(books: Vec<Book>)。为 Library 实现 add_book、find_by_author、oldest 方法。
参考答案

rust 复制代码
#[derive(Debug, Clone)]
struct Book {
    title: String,
    author: String,
    year: u32,
}

struct Library {
    books: Vec<Book>,
}

impl Library {
    fn new() -> Self {
        Library { books: Vec::new() }
    }

    fn add_book(&mut self, book: Book) {
        self.books.push(book);
    }

    fn find_by_author(&self, author: &str) -> Vec<&Book> {
        self.books.iter().filter(|b| b.author == author).collect()
    }

    fn oldest(&self) -> Option<&Book> {
        self.books.iter().min_by_key(|b| b.year)
    }
}

fn main() {
    let mut lib = Library::new();
    lib.add_book(Book {
        title: String::from("Rust 入门"),
        author: String::from("Alice"),
        year: 2020,
    });
    lib.add_book(Book {
        title: String::from("深入 Rust"),
        author: String::from("Bob"),
        year: 2018,
    });
    lib.add_book(Book {
        title: String::from("Rust 实战"),
        author: String::from("Alice"),
        year: 2022,
    });

    for b in lib.find_by_author("Alice") {
        println!("{} ({})", b.title, b.year);
    }

    if let Some(b) = lib.oldest() {
        println!("最老: {} ({})", b.title, b.year);
    }
}

解读 :add_book 用 &mut self 修改,find_by_author 和 oldest 用 &self 只读。find_by_author 返回 Vec<&Book> 借用内部数据,不复制。oldest 返回 Option<&Book>,空库时返回 None。min_by_key 是迭代器方法,按 year 找最小值。这个例子综合了结构体、方法、所有权、迭代器、Option。


🔵 思考题 1:方法为什么用 &self 而不是 self?

参考答案

用 &self 借用,调用后实例仍然可用;用 self 获取所有权,调用后实例失效。

大多数方法只是读取或修改数据,不需要消费实例,所以用 &self 或 &mut self。只有确实要"消耗"实例时才用 self,比如转换成另一种类型:

rust 复制代码
impl Rectangle {
    fn into_square(self) -> Rectangle {
        Rectangle { width: self.width, height: self.width }
    }
}

用 self 的方法会移动实例,调用后原变量不能再用。这是一种"消费式" API,表示这个操作会把当前值转变成别的。选择哪种 self,取决于方法的语义。

实践建议 :默认用 &self,需要修改用 &mut self,确实要消费才用 self。self 用得越少,API 越灵活。


🔵 思考题 2:关联函数和方法有什么区别?

参考答案

关联函数在 impl 块里但没有 self 参数 ,用 Type::function() 调用。

方法是关联函数的一种,第一个参数是 self、&self 或 &mut self ,用 instance.method() 调用。

rust 复制代码
impl Rectangle {
    fn new(w: u32, h: u32) -> Rectangle { ... }  // 关联函数
    fn area(&self) -> u32 { ... }                 // 方法
}

let r = Rectangle::new(3, 4);   // 关联函数用 ::
r.area();                        // 方法用 .

解读 :new、from、default 这类构造器通常是关联函数。方法则是"实例能做的动作"。:: 是命名空间调用,. 是实例调用。关联函数不需要实例就能调用,常用于创建实例。


🔵 思考题 3:Self 和 self 有什么区别?

参考答案

Self 是类型 的别名,指当前 impl 的目标类型。self 是实例的别名,指方法接收者。

rust 复制代码
impl Rectangle {
    fn new(w: u32, h: u32) -> Self {   // Self 就是 Rectangle
        Self { width: w, height: h }    // 用 Self 构造
    }

    fn area(&self) -> u32 {             // self 是当前实例
        self.width * self.height
    }
}

Self 让代码在类型改名时不用逐个替换。关联函数里可以没有 self,但 Self 一直可用。

记忆方法 :大写 Self 是类型,小写 self 是值。类比 Python 的 cls 和 self,或 Java 的 ClassName 和 this。


🔵 思考题 4:为什么 Rust 方法调用能自动加引用?

参考答案

这是 Rust 的自动引用/解引用机制(auto-ref/auto-deref)。

编译器在调用 x.method() 时,会依次尝试:

  1. x.method() ------ x 本身
  2. (&x).method() ------ 加不可变引用
  3. (&mut x).method() ------ 加可变引用
  4. 如果 x 是指针,解引用后重复

找到匹配的方法签名就停止。

rust 复制代码
let p = Point { x: 1, y: 2 };
let r = &p;

p.get_x();    // get_x(&p),自动加 &
r.get_x();    // get_x(r),r 已经是 &

为什么这样设计? 因为 Rust 的所有权和借用是显式的。如果方法调用也要手动加 &,代码会很啰嗦。自动引用让 . 调用更自然,同时不影响所有权规则------编译器只是在"找匹配的签名",不改变语义。

对比 C++ :C++ 里 p->method() 和 p.method() 有明确区别,需要程序员自己选。Rust 统一用 .,编译器处理细节。这让代码更简洁,但也意味着看代码时要留意方法签名是 &self 还是 self。


🔵 思考题 5:结构体什么时候该用 String,什么时候用 &str?

参考答案

用 String:结构体拥有数据,生命周期独立。这是默认选择。

rust 复制代码
struct User {
    name: String,   // User 拥有 name
}

用 &str:结构体只是借用数据,需要生命周期标注,且结构体不能活得比数据长。

rust 复制代码
struct Excerpt<'a> {
    part: &'a str,   // 借用外部数据
}

判断标准:

  • 结构体要独立存在,能自由传递 → String
  • 结构体只是临时视图,借用外部数据,且明确知道生命周期 → &str
  • 性能敏感,不想复制 → 考虑 &str 加生命周期

实践建议 :初学者优先用 String,简单、不容易出生命周期错误。遇到性能瓶颈或明确知道"这个结构体只是借用"时,再改成 &str。实际项目中,拥有所有权的结构体(String、Vec<T>)占大多数,借用型结构体(&str、&[T])多用于解析、视图、零拷贝场景。


🔵 思考题 6:构建器模式为什么用 mut self 而不是 &mut self?

参考答案

mut self 消耗实例,返回新 Self,支持链式调用:

rust 复制代码
Builder::new().name("A").age(30).build()

&mut self 也能链式,但需要先绑定变量,更啰嗦:

rust 复制代码
let mut b = Builder::new();
b.name("A");
b.age(30);
b.build()

mut self 的优势:

  • 链式调用更自然,一行搞定
  • 语义上"构建器用完就丢弃",消耗 self 符合直觉
  • 避免生命周期纠缠(&mut self 返回 &mut Self 需要生命周期标注)

&mut self 的场景:需要复用同一个构建器多次,或者构建器持有大资源不想移动。

解读 :构建器模式的本质是"逐步配置,最后产出"。每一步都是"配置后返回自己",mut self 让这个语义最干净。实际项目里两种都有,mut self 更常见。


七、知识点总结(第6课)

结构体 :struct Name { field: Type, ... },把相关数据打包成有名字的类型。

实例化 :Name { field: value, ... },字段访问用 .,必须全部赋值。

字段简写 :变量名与字段名相同时可省略 field:。

更新语法 :..other 复用其他实例的字段,可能移动字段(部分移动)。

元组结构体 :struct Point(i32, i32),用 .0 访问,常用于 newtype 模式。

单元结构体 :struct Unit;,无字段,用于 trait 场景,运行时零大小。

方法 :在 impl 块中,第一个参数是 self、&self 或 &mut self,用 . 调用。

&self:只读借用,最常用。

&mut self:可变借用,能改字段。

self:获取所有权,调用后实例失效,用于消费式 API。

自动引用 :方法调用时编译器自动加 & 或 &mut,匹配签名。

关联函数 :无 self 参数,用 :: 调用,常用于构造器。

Self vs self :Self 是类型,self 是实例。

多个 impl :同一类型可有多个 impl 块,功能等价。

构建器模式 :链式调用,每个方法返回 Self,用 mut self。

newtype 模式:包装类型增加类型安全,零运行时开销。

derive :Debug、Clone、PartialEq 等自动实现。

部分移动:可以只移动结构体的某些字段,之后整个结构体不能整体使用。

结构体与所有权:结构体拥有其字段的所有权;持有引用需要生命周期标注。

内存布局:结构体在栈上,堆数据通过指针字段引用,drop 时自动释放。


八、下一课预告

第7课:枚举与模式匹配

  • 定义枚举 enum
  • Option<T>:Rust 的可空类型
  • match 表达式
  • if let 与 let else
  • 枚举与结构体的组合

📌 本课心法:

结构体回答"数据长什么样",impl 回答"数据能做什么"。数据和行为绑在一起,代码就从"一堆变量"变成了"有语义的对象"。self 的三种形式是所有权在方法上的投影------借、改、拿,一一对应。方法调用的自动引用让你写得少,但读代码时要记得多看一眼签名:是 &self、&mut self 还是 self,决定了这个调用之后,你还能不能用它。而 newtype 和构建器这两个模式,是结构体在实际项目里最有价值的两把武器。

相关推荐
本地化文档1 小时前
cutile-rs-book-l10n
rust·gpu·nvidia·cuda·sphinx·crowdin
code tsunami1 小时前
【无标题】
开发语言·python
CodeGL2 小时前
10、Python基础之进程和线程
开发语言·python
TechWayfarer3 小时前
PHP漏洞被利用,怎么揪出攻击IP?
开发语言·python·tcp/ip·网络安全·php
卡卡~~4 小时前
“101计划”核心课程实践教材《数据实验结构指导-C++语言版》题目集
开发语言·c++·学习·算法
qq_406981034 小时前
大白话rust系列09 函数调用两次有什么错?
开发语言·python·rust
学逆向的5 小时前
C 多文件开发
c语言·开发语言·网络安全
布吉岛的石头5 小时前
Java 程序员第 50 阶段3:数据工程:预训练语料清洗与去重
java·开发语言·人工智能·深度学习·transformer
benchmark_cc5 小时前
QuantDash 安装成功后,如何验证 Python 环境可以正常导入 SDK?
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash