【Rust入门知识点学与练】第24课:Trait 基础

知识点:定义和实现 Trait

Trait 定义了一组行为(方法),类型可以实现这些行为:

rust 复制代码
// 定义 trait
trait Speak {
    fn speak(&self) -> String;  // 必须实现的方法
    fn greet(&self) -> String {  // 带默认实现的方法
        format!("你好!")
    }
}

// 为不同类型实现 trait
struct Dog {
    name: String,
}

struct Cat {
    name: String,
}

impl Speak for Dog {
    fn speak(&self) -> String {
        format!("{}说:汪汪!", self.name)
    }
    // greet 使用默认实现
}

impl Speak for Cat {
    fn speak(&self) -> String {
        format!("{}说:喵~", self.name)
    }

    // 覆盖默认实现
    fn greet(&self) -> String {
        format!("{}懒洋洋地看了你一眼", self.name)
    }
}

fn main() {
    let dog = Dog { name: String::from("旺财") };
    let cat = Cat { name: String::from("咪咪") };

    println!("{}", dog.speak());  // 旺财说:汪汪!
    println!("{}", dog.greet());  // 你好!
    println!("{}", cat.speak());  // 咪咪说:喵~
    println!("{}", cat.greet());  // 咪咪懒洋洋地看了你一眼
}

知识点:派生宏(derive)

标准库提供了很多可以自动实现的 trait:

rust 复制代码
// #[derive(...)] 自动实现常见 trait
#[derive(Debug, Clone, PartialEq)]
struct Point {
    x: f64,
    y: f64,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct Color {
    r: u8,
    g: u8,
    b: u8,
}

// 常用可派生的 trait:
// Debug      --- {:?} 格式化
// Clone      --- .clone() 深拷贝
// Copy       --- 自动按位复制(不能和 Drop 共存)
// PartialEq  --- == 和 !=
// Eq         --- 完全等价关系(要求 PartialEq)
// PartialOrd --- < > <= >=
// Ord        --- 全序关系
// Hash       --- 可作为 HashMap 的键
// Default    --- 默认值

fn main() {
    let p1 = Point { x: 1.0, y: 2.0 };
    let p2 = p1.clone();
    println!("p1: {:?}", p1);
    println!("相等? {}", p1 == p2);

    let red = Color { r: 255, g: 0, b: 0 };
    println!("颜色: {:?}", red);

    // Default trait
    let default_point = Point { x: 0.0, y: 0.0 };
    // 也可以手动实现 Default
}

// 手动实现 Default
impl Default for Point {
    fn default() -> Self {
        Point { x: 0.0, y: 0.0 }
    }
}

知识点:Trait 作为参数

rust 复制代码
trait Area {
    fn area(&self) -> f64;
}

struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }

impl Area for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

impl Area for Rectangle {
    fn area(&self) -> f64 {
        self.width * self.height
    }
}

// 方式1:impl Trait 语法(简洁)
fn print_area(shape: &impl Area) {
    println!("面积: {:.2}", shape.area());
}

// 方式2:泛型 + trait bound(更灵活)
fn print_area2<T: Area>(shape: &T) {
    println!("面积: {:.2}", shape.area());
}

// 方式3:where 子句(多个 bound 时更清晰)
fn compare_areas<T: Area + std::fmt::Debug>(a: &T, b: &T) {
    let area_a = a.area();
    let area_b = b.area();
    if area_a > area_b {
        println!("{:?} 更大", a);
    } else {
        println!("{:?} 更大", b);
    }
}

// 返回 trait 对象(动态分发)
fn create_shape(kind: &str) -> Box<dyn Area> {
    match kind {
        "circle" => Box::new(Circle { radius: 5.0 }),
        "rectangle" => Box::new(Rectangle { width: 3.0, height: 4.0 }),
        _ => panic!("未知形状"),
    }
}

fn main() {
    let circle = Circle { radius: 3.0 };
    let rect = Rectangle { width: 4.0, height: 5.0 };

    print_area(&circle);      // 面积: 28.27
    print_area(&rect);        // 面积: 20.00

    // 动态分发
    let shape = create_shape("circle");
    println!("动态面积: {:.2}", shape.area());
}

知识点:多个 Trait Bound

rust 复制代码
use std::fmt;

trait Drawable {
    fn draw(&self);
}

trait Resizable {
    fn resize(&mut self, factor: f64);
}

#[derive(Debug)]
struct Box {
    size: f64,
}

impl Drawable for Box {
    fn draw(&self) {
        println!("绘制大小为 {} 的盒子", self.size);
    }
}

impl Resizable for Box {
    fn resize(&mut self, factor: f64) {
        self.size *= factor;
    }
}

impl fmt::Display for Box {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Box(size={})", self.size)
    }
}

// 要求同时实现多个 trait
fn process<T: Drawable + Resizable + fmt::Display>(item: &mut T) {
    println!("处理前: {}", item);
    item.draw();
    item.resize(2.0);
    println!("处理后: {}", item);
    item.draw();
}

// where 子句写法(更清晰)
fn process2<T>(item: &mut T)
where
    T: Drawable + Resizable + fmt::Display,
{
    println!("处理前: {}", item);
    item.resize(0.5);
    item.draw();
}

fn main() {
    let mut b = Box { size: 10.0 };
    process(&mut b);
}

知识点:trait 对象与 dyn

rust 复制代码
use std::fmt;

trait Animal: fmt::Display {
    fn name(&self) -> &str;
    fn sound(&self) -> &str;
}

struct Cow;
struct Sheep;

impl fmt::Display for Cow {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "牛")
    }
}

impl fmt::Display for Sheep {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "羊")
    }
}

impl Animal for Cow {
    fn name(&self) -> &str { "牛" }
    fn sound(&self) -> &str { "哞~" }
}

impl Animal for Sheep {
    fn name(&self) -> &str { "羊" }
    fn sound(&self) -> &str { "咩~" }
}

// 用 Box<dyn Trait> 存储不同类型的 trait 实现
fn make_noise(animal: &dyn Animal) {
    println!("{}说:{}", animal.name(), animal.sound());
}

fn main() {
    let animals: Vec<Box<dyn Animal>> = vec![
        Box::new(Cow),
        Box::new(Sheep),
        Box::new(Cow),
    ];

    // 动态分发:遍历不同类型的动物
    for animal in &animals {
        make_noise(animal.as_ref());
    }

    // 也可以用 &dyn Trait
    let cow = Cow;
    make_noise(&cow);
}

知识点:为泛型类型实现 Trait

rust 复制代码
use std::fmt;

// 泛型结构体
#[derive(Debug)]
struct Wrapper<T> {
    value: T,
}

// 为泛型类型实现 trait
impl<T: fmt::Display> fmt::Display for Wrapper<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "[{}]", self.value)
    }
}

// 为泛型类型实现自定义 trait
trait Unwrap {
    type Inner;
    fn unwrap_value(self) -> Self::Inner;
}

impl<T> Unwrap for Wrapper<T> {
    type Inner = T;
    fn unwrap_value(self) -> T {
        self.value
    }
}

fn main() {
    let w = Wrapper { value: 42 };
    println!("{}", w);  // [42]

    let w2 = Wrapper { value: String::from("hello") };
    println!("{}", w2);  // [hello]

    let val = w2.unwrap_value();
    println!("解包: {}", val);
}

核心规则

概念 写法

定义 trait trait Name { fn method(&self); }

实现 trait impl Name for Type { ... }

默认方法 fn method(&self) -> T { ... }

派生宏 #derive(Debug, Clone, PartialEq)

impl Trait 参数 fn foo(x: &impl Trait)

泛型 + bound fn foo<T: Trait>(x: &T)

where 子句 fn foo(x: &T) where T: Trait

多个 bound T: Trait1 + Trait2

trait 对象 Box / &dyn Trait

返回 trait 对象 -> Box

动手试试

补全下面的代码:

rust 复制代码
use std::fmt;

// 补全:定义一个 trait Shape,包含以下方法:
// 1. area(&self) -> f64           计算面积
// 2. perimeter(&self) -> f64      计算周长
// 3. describe(&self) -> String    返回描述(有默认实现)
//    默认实现返回格式: "面积={:.2}, 周长={:.2}"

// 补全:定义结构体 Triangle,字段为 a, b, c(三边长度,f64)

// 补全:为 Triangle 实现 Shape trait
// 面积用海伦公式:s = (a+b+c)/2, area = sqrt(s*(s-a)*(s-b)*(s-c))
// 周长 = a + b + c

// 补全:为 Triangle 实现 Display trait
// 格式: "三角形({},{},{})", a, b, c

// 补全:定义结构体 RegularPolygon(正多边形)
// 字段:sides: usize(边数), side_length: f64(边长)

// 补全:为 RegularPolygon 实现 Shape trait
// 面积 = (n * s²) / (4 * tan(π/n))
// 周长 = n * s

// 补全:为 RegularPolygon 实现 Display trait
// 格式: "正{n}边形(边长={})", sides, side_length

// 补全:实现函数 largest_shape
// 接受一个 &[Box<dyn Shape>],返回面积最大的形状的索引
fn largest_shape(shapes: &[Box<dyn Shape>]) -> usize {
    // 补全
    todo!()
}

// 补全:实现函数 total_area
// 接受一个 &[Box<dyn Shape>],返回所有形状的面积之和
fn total_area(shapes: &[Box<dyn Shape>]) -> f64 {
    // 补全
    todo!()
}

fn main() {
    // 补全:创建形状列表
    // 1. 三角形 (3, 4, 5)
    // 2. 正方形(正4边形,边长 5)
    // 3. 正六边形(边长 3)
    // 4. 三角形 (5, 12, 13)

    // 补全:遍历打印每个形状的描述
    // 期望输出(每行一个):
    // 三角形(3,4,5): 面积=6.00, 周长=12.00
    // 正4边形(边长=5): 面积=25.00, 周长=20.00
    // 正6边形(边长=3): 面积=23.38, 周长=18.00
    // 三角形(5,12,13): 面积=30.00, 周长=30.00

    // 补全:打印最大面积的索引
    // 期望: 最大面积索引: 3

    // 补全:打印总面积
    // 期望: 总面积: 84.38
}

期望输出:

三角形(3,4,5): 面积=6.00, 周长=12.00

正4边形(边长=5): 面积=25.00, 周长=20.00

正6边形(边长=3): 面积=23.38, 周长=18.00

三角形(5,12,13): 面积=30.00, 周长=30.00

最大面积索引: 3

总面积: 84.38

写好了贴过来,没问题就进入第25课:格式化输出与常用标准库 👋

贴代码

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

// Shape trait,describe 提供默认实现
trait Shape {
    fn area(&self) -> f64;
    fn perimeter(&self) -> f64;

    fn describe(&self) -> String {
        format!("面积={:.2}, 周长={:.2}", self.area(), self.perimeter())
    }
}

// 三角形结构体
struct Triangle {
    a: f64,
    b: f64,
    c: f64,
}

impl Shape for Triangle {
    fn area(&self) -> f64 {
        let s = (self.a + self.b + self.c) / 2.0;
        (s * (s - self.a) * (s - self.b) * (s - self.c)).sqrt()
    }

    fn perimeter(&self) -> f64 {
        self.a + self.b + self.c
    }
}

impl fmt::Display for Triangle {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "三角形({},{},{})", self.a, self.b, self.c)
    }
}

// 正多边形
struct RegularPolygon {
    sides: usize,
    side_length: f64,
}

impl Shape for RegularPolygon {
    fn area(&self) -> f64 {
        let n = self.sides as f64;
        let s = self.side_length;
        (n * s * s) / (4.0 * (PI / n).tan())
    }

    fn perimeter(&self) -> f64 {
        (self.sides as f64) * self.side_length
    }
}

impl fmt::Display for RegularPolygon {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "正{}边形(边长={})", self.sides, self.side_length)
    }
}

// 返回面积最大形状的索引
fn largest_shape(shapes: &[Box<dyn Shape>]) -> usize {
    shapes
        .iter()
        .enumerate()
        .max_by(|(_, s1), (_, s2)| s1.area().partial_cmp(&s2.area()).unwrap())
        .map(|(idx, _)| idx)
        .unwrap()
}

// 全部形状面积之和
fn total_area(shapes: &[Box<dyn Shape>]) -> f64 {
    shapes.iter().map(|s| s.area()).sum()
}

fn main() {
    // 创建动态形状数组
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Triangle { a: 3.0, b: 4.0, c: 5.0 }),
        Box::new(RegularPolygon { sides: 4, side_length: 5.0 }),
        Box::new(RegularPolygon { sides: 6, side_length: 3.0 }),
        Box::new(Triangle { a: 5.0, b: 12.0, c: 13.0 }),
    ];

    // 遍历打印
    for s in &shapes {
        // 需要同时拿到Display和Shape,这里分开处理;为了方便我们重新包装
        // 为了满足打印 "三角形(3,4,5): 面积=6.00, 周长=12.00",
        // dyn Shape 本身没有Display,所以我们改用模式匹配打印(练习版本)
        // 下面主循环完整实现
    }

    // 方案:重建带打印的版本,完整main实现:
    let tri1 = Triangle { a: 3.0, b: 4.0, c: 5.0 };
    let sq = RegularPolygon { sides: 4, side_length: 5.0 };
    let hex = RegularPolygon { sides: 6, side_length: 3.0 };
    let tri2 = Triangle { a: 5.0, b: 12.0, c: 13.0 };

    let shape_vec: Vec<Box<dyn Shape>> = vec![
        Box::new(tri1),
        Box::new(sq),
        Box::new(hex),
        Box::new(tri2),
    ];

    // 注意:dyn Shape 不能直接Display;为了完成题目输出,下面把对象再存一份用于打印
    // 完整版main(保存各个实体用于格式化输出)
    let items = [
        (&Triangle { a: 3.0, b: 4.0, c: 5.0 }) as &dyn fmt::Display,
        &RegularPolygon { sides: 4, side_length: 5.0 } as &dyn fmt::Display,
        &RegularPolygon { sides: 6, side_length: 3.0 } as &dyn fmt::Display,
        &Triangle { a: 5.0, b: 12.0, c: 13.0 } as &dyn fmt::Display,
    ];

    for i in 0..shape_vec.len() {
        println!("{}: {}", items[i], shape_vec[i].describe());
    }

    let max_idx = largest_shape(&shape_vec);
    println!("最大面积索引: {}", max_idx);

    let sum = total_area(&shape_vec);
    println!("总面积: {:.2}", sum);
}

运行输出

plaintext

三角形(3,4,5): 面积=6.00, 周长=12.00

正4边形(边长=5): 面积=25.00, 周长=20.00

正6边形(边长=3): 面积=23.38, 周长=18.00

三角形(5,12,13): 面积=30.00, 周长=30.00

最大面积索引: 3

总面积: 84.38

核心知识点

1. Trait 默认方法

rust 复制代码
  
fn describe(&self) -> String { ... }

没有 &self 前面没有 dyn ,直接写在trait里面,不用每个实现者重复实现。

2. 海伦公式(三角形面积)

rust 复制代码
s=(a+b+c)/2,\quad area=\sqrt{s(s-a)(s-b)(s-c)}

3. 正多边形面积公式

rust 复制代码
A=\dfrac{n\cdot s^{2}}{4\cdot\tan(\pi/n)}

需要 std::f64::consts::PI 。

**4. Trait 对象(动态分发) Box **

  • Box :堆分配,运行时动态调用 area/perimeter;
  • 一个Vec里面存放不同类型(Triangle、RegularPolygon)的形状;
  • 限制:一个trait对象不能同时拥有多个不同trait(Shape+Display),所以main中分开两份引用,一份做Shape运算,一份做Display打印,这是Rust经典坑点。

5. largest_shape

enumerate 带上索引, max_by ,f64比较用 partial_cmp (因为存在NaN)。

6. total_area

迭代器map + sum,一行代码完成求和。

Rust 面向对象(Trait对象)训练到此结束

现在Rust全套主线练习题:基础 → 所有权 → 生命周期 → 泛型 → trait(关联类型)→ 闭包迭代器 → 容器 → 宏 → 自定义迭代器 → trait对象动态多态 → 错误处理 → 多线程(Arc‑Mutex‑channel)全部通关。

相关推荐
潜心一志6 小时前
HALCON软件——基本架构,算子参数
学习·计算机视觉
言乐67 小时前
Python加速器4跨境网络加速器
运维·服务器·开发语言·网络·python
金金计较.7 小时前
Go语言-3
java·开发语言·golang
爱吃苹果的日记本8 小时前
数据结构第三课(时间复杂度)
数据结构·学习
张小凡vip8 小时前
python--爬虫--经验积累的遇到的坑
开发语言·爬虫·python
Wang's Blog8 小时前
Java框架快速入门: Spring Security+OAuth2之云服务集成与多因子认证设计
java·开发语言·spring
门思科技9 小时前
LoRaWAN 网络容量估算:一个 SX1302 网关到底能带多少设备
开发语言·网络·php
大草原的小灰灰9 小时前
Python基础语法
开发语言·python
泡泡鱼(敲代码中)10 小时前
MySQL基础学习笔记:从数据模型到DDL全掌握
开发语言·数据库·笔记·学习·mysql