【rust语言】rust多态实现方式

news/2024/7/3 12:29:05 标签: rust, 开发语言, 面向对象, 多态

文章目录

  • 前言
  • 一、多态
  • 二、rust实现多态
    • trait的静态方式
    • 还有一种方式可以通过动态分发,还以上面那段代码,比如
        • dyn关键字
    • 泛型方式
    • 枚举方式
      • 优点:
      • 缺点:
  • 总结


前言

学习rust当中遇到了这个问题,记录一下,不对地方望指正

一、多态

多态面向对象程序设计中的一个重要概念,指同一个行为或操作在不同实例上具有不同的行为或结果。简单来说,多态就是指同一种类型的对象,在不同的上下文中有不同的行为。多态性使得程序可以更加灵活、可扩展和易于维护。在实现多态性时,通常会使用继承、接口、抽象类等技术

rust_10">二、rust实现多态

trait的静态方式

rust">
trait Animal {
    fn make_sound(&self);
}
struct Cat {}

impl Animal for Cat {
    fn make_sound(&self) {
        println!("Meow");
    }
}
struct Dog {}

impl Animal for Dog {
    fn make_sound(&self) {
        println!("Woof");
    }
}


fn main() {
    let cat: Cat = Cat {};
    let dog: Dog = Dog {};
    test(cat);
    test(dog)
}
//接受Animal Trait类型的
fn test(animal : impl Animal){
    animal.make_sound()
}

定义了方法传入参数是trait。这一种在实例化的时候是具体的类型,在传参的时候编译器能推断出来具体是cat还是dog,能调用具体方法

还有一种方式可以通过动态分发,还以上面那段代码,比如

rust">trait Animal {
    fn make_sound(&self);
}

struct Cat {}

impl Animal for Cat {
    fn make_sound(&self) {
        println!("Meow");
    }
}

struct Dog {}

impl Animal for Dog {
    fn make_sound(&self) {
        println!("Woof");
    }
}

fn main() {
    let cat: Box<dyn Animal> = Box::new(Cat {});
    let dog: Box<dyn Animal> = Box::new(Dog {});

    test(cat);
    test(dog)
}

fn test(animal: Box<dyn Animal>) {
    animal.make_sound()
}

这种方式相对于上面更加灵活,因为实例化参数变量类型是trait类型。现在说说关键点

dyn关键字

dyn关键字是在Rust中用于创建和使用动态分发的trait对象的关键字。trait对象允许我们以统一的方式处理不同类型的对象,并使用相同的方法调用语法。使用动态分发,编译器无需在编译时知道具体的类型,而是在运行时根据对象的实际类型来确定要调用的方法。要创建一个trait对象,需要在trait名称前加上dyn关键字。例如,对于名为TraitName的trait,我们可以使用dyn TraitName来创建一个trait对象。

rust">trait TraitName {
    // trait定义
}

fn main() {
    let trait_obj: Box<dyn TraitName> = Box::new(ConcreteType);
    // 在这里使用trait对象
}

在上面的代码中,trait_obj是一个Box指向动态分发的trait对象的指针。它可以存储实现了TraitName trait的任何具体类型的对象。通过dyn关键字,我们可以在运行时根据实际类型来调用trait定义的方法。
这里要注意,trait对象通过指针或引用来操作,因此通常结合使用Box、&或&mut来创建和使用trait对象。为了在运行时确定对象的大小,我们需要将它们放置在一个固定大小的容器中。这就是为什么要使用Box来包装trait对象的原因。Box类型表示一个动态分发的trait对象。它在堆上分配一块内存,该内存用于存储对象的数据,并提供一个指向虚函数表(vtable)的指针,该表用于在运行时查找和调用正确的方法。
这种方式相对于静态方式会更加灵活,但会有运行时性能损失,看情况决定使用哪一种

泛型方式

rust">use std::fmt::{Display, Formatter};

struct Cat {}

struct Dog {}

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

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

fn make_sound<T: Display>(animal: T) {
    println!("{}", animal);
}

fn main() {
    make_sound(Cat{});
    make_sound(Dog{});
}

通过在函数签名中使用泛型类型参数,函数可以接受不同类型的参数,并在编译时生成对应的具体化代码。这种方式不依赖于trait,而是基于类型推断和编译时的静态分发

枚举方式

还有一种方式是使用枚举方式,例如

rust">enum Shape {
    Circle(f64),
    Square(f64),
    Rectangle(f64, f64),
}

impl Shape {
    fn area(&self) -> f64 {
        match *self {
            Shape::Circle(radius) => std::f64::consts::PI * radius * radius,
            Shape::Square(side_length) => side_length * side_length,
            Shape::Rectangle(length, width) => length * width,
        }
    }
}

fn main() {
    let circle = Shape::Circle(5.0);
    let square = Shape::Square(4.0);
    let rectangle = Shape::Rectangle(3.0, 6.0);

    test(circle);
    test(square);
    test(rectangle);
}

fn test(shape: Shape) {
    println!("shape area: {}", shape.area());
}

枚举在实现多态性方面有一些优点和缺点。以下是其中的一些:

优点:

简洁性:枚举提供了一种紧凑的方式来定义和组织具有不同变体的数据类型。它能够在一个地方集中描述和管理多种可能的状态或情况。
静态类型检查:由于枚举的变体是预先定义的,编译器可以在编译时验证变体的正确性。这可以帮助捕捉到潜在的错误,并提供类型安全性。
模式匹配:枚举与模式匹配相结合,可以使代码更具表达力和可读性。模式匹配可以根据具体的变体类型执行相应的逻辑,同时处理所有可能的情况,避免遗漏。

缺点:

限制的扩展性:当需要添加新的变体时,枚举需要进行修改。这可能涉及到修改已有的代码,以适应新的变体。这对于外部库或包的枚举类型来说尤其困难,因为无法直接修改其定义。
冗余的结构:枚举的每个变体都可以存储不同的数据结构,这可能会导致某些变体拥有与其他变体不相关的冗余数据。这可能会浪费内存空间,尤其是当只使用其中的一部分变体时。
灵活性的限制:枚举要求提前定义所有可能的变体。如果需要在运行时动态添加新的变体,或者处理不确定的类型集合,那么枚举可能不适合。

总结

以上就是今天要说的内容,不对的地方望指正


http://www.niftyadmin.cn/n/4964967.html

相关文章

视频云存储平台EasyCVR视频汇聚平台关于机电设别可视化管理平台可实施设计方案

随着工业化进程的不断发展&#xff0c;机电设备在各行各业中扮演着重要的角色。然而&#xff0c;由于机电设备种类繁多、数量庞大&#xff0c;包括生产机械、建筑器械、矿用器械、制药器械、食品机械等&#xff0c;传统的手动管理方式已经无法满足对设备进行精细化管理的需求。…

【leetcode】第六章 二叉树part01

递归遍历 144. 二叉树的前序遍历 // 前序遍历 public List<Integer> preorderTraversal(TreeNode root) {List<Integer> res new ArrayList<>();preOrder(root, res);return res;}private void preOrder(TreeNode root, List<Integer> res) {if (ro…

IT人如何维护“战友”

无论是学习还是工作&#xff0c;电脑都是IT人必不可少的重要武器&#xff0c;一台好电脑除了自身配置要经得起考验&#xff0c;后期主人对它的维护也是决定它寿命的重要因素&#xff01; 你日常是怎么维护你的“战友”的呢&#xff0c;维护电脑运行你有什么好的建议吗&#xff…

elasticSearch数据的导入和导出

http://wget https://nodejs.org/dist/v10.13.0/node-v10.13.0-linux-x64.tar.gz 解压即可&#xff1a; 1、将索引中的数据导出到本地 myindex 是我的索引 elasticdump --inputhttp://localhost:9200/myindex--output/home/date/myindex.json2、将本地数据导入es中 elasticdum…

校园跑腿小程序开发方案详解

校园跑腿小程序App的功能有哪些&#xff1f; 1、用户注册与登录 用户可以通过手机号、社交账号等方式进行注册和登录&#xff0c;以便使用跑腿服务。 2、下单与发布任务 用户可以发布各类跑腿任务&#xff0c;包括食品外卖、快递代收、文件送达、帮我买、帮我取、帮我送等等…

LeetCode--HOT100题(35)

目录 题目描述&#xff1a;23. 合并 K 个升序链表&#xff08;困难&#xff09;题目接口解题思路1代码解题思路2代码 PS: 题目描述&#xff1a;23. 合并 K 个升序链表&#xff08;困难&#xff09; 给你一个链表数组&#xff0c;每个链表都已经按升序排列。 请你将所有链表合…

胖小酱之补考也不会

小胖&#xff01;补考还是要重视起来呀&#xff01; 挂科了就要补考&#xff0c;如果不参加补考&#xff0c;毕业时重修该课程。补考不一定能过。挂科后一般都会有补考的&#xff0c;补考一般是在下个学期开学的前几周进行&#xff0c;因此如果自己挂科了要认真复习&#xff0…

【嵌入式开发 Linux 常用命令系列 13 -- 统计文件中出现某个字符(串)的次数 】

文章目录 统计字符串出现的次数 统计字符串出现的次数 在Linux中&#xff0c;你可以通过很多种方式来统计某个字符在文件中出现的次数&#xff0c;下面是一种常用的方法&#xff1a; 使用grep命令配合-o和-c选项&#xff0c;-o选项让grep只输出匹配的部分&#xff0c;-c选项让…