Rust面向对象特性

Rust的面向对象特性

本文已同步至自建博客

Rust在设计的时候受到很多编程范式的影响,包括面向对象。面向对象的语言共有一些共同的特征,即对象、封装和继承。

封装

一个对象的实现细节对使用该对象的代码不可访问。因此,对象交互的唯一方式是通过其公共 API;使用对象的代码不应能直接触及对象的内部并改变数据或行为。这使得程序员能够更改和重构一个对象的内部实现,而无需改变使用该对象的代码。

使用pub 关键字来控制封装,Rust语言使用 pub 关键字来决定代码中的哪些模块、类型、函数和方法是公有的,而默认情况下其他所有内容都是私有的。

示例:

pub struct AveragedCollection {// 集合list: Vec<i32>,// 平均数average: f64,
}impl AveragedCollection {pub fn add(&mut self, value: i32) {self.list.push(value);self.update_average();}pub fn remove(&mut self) -> Option<i32> {let result = self.list.pop();match result {Some(value) => {self.update_average();Some(value)}None => None,}}pub fn average(&self) -> f64 {self.average}fn update_average(&mut self) {let total: i32 = self.list.iter().sum();self.average = total as f64 / self.list.len() as f64;}}#[cfg(test)]
mod tests {use super::*;#[test]fn test() {let mut c = AveragedCollection {list: Vec::new(),average: 0.0,};c.add(1);c.add(5);println!("average value: {}", c.average());c.remove();println!("remove a node, and now average value: {}", c.average());}}

结构体AveragedCollection中有listaverage两个字段,因为没有pub 关键字修饰,默认是私有的,也就是在结构体外部不能直接访问到。通过定义由pub关键字修饰的方法,使得可以通过方法访问并操作结构体中的字段。示例中添加元素,和移除元素是公共的方法,更新平均数方法是私有的。因为不希望外部可以修改平均数,这就是封装。

继承

继承:使得对象可以沿用另外一个对象的数据或行为,且无需重复定义相关代码。

Rust 语言没有继承

使用继承的场景:

  1. 代码复用,Rust可以使用trait,默认的trait方法可以进行代码共享
  2. 多态:泛型和trait约束(限定参数化多态 bounded parametric)

为共有行为定义一个trait

创建一个GUI工具:

  1. 它会遍历某个元素的列表,依次调用元素的draw方法进行绘制,例如:Button,TextField等元素。

在面向对象语言中,惯例是定义一个父类Component,声明一个draw方法。定义Button,TextField 等类,继承Component类。

在Rust中需要trait来实现:

注意:

  1. Rust避免将struct和enum 称之为对象,因为它们与impl块是分开的。

  2. Trait 对象有些类似于其他语言的对象,因为在某种程度上组合了数据与行为。trait 对象与传统对象不同的地方:无法为trait对象添加数据。

  3. trait对象被专门用于抽象某些共有行为,它没有其他语言的对象那么通用。

类比Java 的接口(interface),接口定义方法声明,不同的实现类实现接口来提供不同的具体实现。比如一个Draw接口,定义了一个draw方法,不同的实现类实现Draw接口后,draw方法执行的内容不一样的。

这在Rust中通过 trait也可以实现同样的效果,示例如下:

同一个方法,不同结构体的方法可以进行不同的具体实现

示例代码:

pub trait Draw {fn draw(&self);
}/// `Vec<Box<dyn Draw>>` 在这里的用法含义:无法再编译期确定单一类型,就要使用智能指针在堆上分配
pub struct Screen {pub components: Vec<Box<dyn Draw>>,
}// 为结构体Screen 实现一个方法
impl Screen {pub fn run(&self) {for component in self.components.iter() {component.draw();}}
}pub struct Button {pub width: u32,pub height: u32,pub label: String,
}impl Draw for Button {fn draw(&self) {println!("Button drawing")}
}pub struct SelectBox {pub width: u32,pub height: u32,pub options: Vec<String>,
}impl Draw for SelectBox {fn draw(&self) {println!("SelectBox drawing")}}
    #[test]fn test_trait_oop() {let screen = Screen {components: vec![Box::new(SelectBox {width: 75,height: 10,options: vec![String::from("Yes"), String::from("No"),]}),Box::new(Button {width: 50,height: 10,label: String::from("OK"),}),]};screen.run();}

执行结果:

running 1 test
test tests::test_trait_oop ... oksuccesses:---- tests::test_trait_oop stdout ----
SelectBox drawing
Button drawingsuccesses:tests::test_trait_oop

示例代码中有 SelectBoxButton 两个结构体,同时为两个结构体都实现了Draw 这个trait。

通过 Screen 这个结构体的run方法执行传入trait对象的方法。pub components: Vec<Box<dyn Draw>> 这里使用Box表示这个数组中的对象是动态的。

我们在执行代码的时候,是动态像数组中填充对象的,这无法在编译期确定具体的对象类型。所以这里使用Box

当Button执行draw方法时,执行的是 Button 结构体中的draw方法实现,当SelectBox执行draw方法时,执行的是SelectBox中draw方法的实现。

Trait对象执行的是动作派发

将trait约束作用于泛型时,Rust编译期会执行单态化:

编译器会为我们用来替换泛型类型参数的每一个具体类型生成对应函数和方法的非泛型实现。通过单态化生成的代码会执行静态派发(static dispatch),在编译过程中确定调用的具体方法。

动态派发(dynamic dispach):无法在编译过程中确定调用的究竟是哪一个方法,编译器会产生额外的代码以便运行时找出希望调用的方法。

使用trait对象,会执行动态派发:

产生运行时开销,阻止编译器内联方法代码,使得部分优化操作无法进行

Trait对象必须保证对象安全

只能把满足对象安全(object-safe)的trait转化为trait对象。

Rust采用一系列规则来判定某个对象是否安全,只需要记住两条:

  1. 方法的返回不是self
  2. 方法中不包含任何泛型类型参数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/62521.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前海湾地铁的腾通数码大厦背后的临时免费停车点探寻

临时免费停车点&#xff1a;前海湾地铁的腾通数码大厦背后的桂湾大街&#xff0c;目前看不仅整条桂湾大街停了​车&#xff0c;而且还有工地餐点。可能是这个区域还是半工地状态&#xff0c;故暂时还不会有​罚单的情况出现。 中建三局腾讯数码大厦项目部A栋 广东省深圳市南山…

Python知识分享第十五天

“”" 细节: 1.如下定义的类的几种写法 并无任何区别 最终效果都一样 只是写法不同 2.所有的类都直接或间接继承自object 它是所有类的父类 定义类的格式 格式1 class 类名: pass 格式2 class 类名(): pass 格式3 class 类名(父类名): pass “”" # 需求: 通过上述…

遥感数据集:FTW全球农田边界和对应影像数据,约160万田块边界及7万多个样本

Fields of The World (FTW) 是一个面向农业田地边界实例分割的基准数据集&#xff0c;旨在推动机器学习模型的发展&#xff0c;满足全球农业监测对高精度、可扩展的田地边界数据的需求。该数据集由kerner-lab提供&#xff0c;于2024年8月28日发布&#xff0c;主要特征包括&…

USB Type-C一线通扩展屏:多场景应用,重塑高效办公与极致娱乐体验

在追求高效与便捷的时代&#xff0c;启明智显USB Type-C一线通扩展屏方案正以其独特的优势&#xff0c;成为众多职场人士、娱乐爱好者和游戏玩家的首选。这款扩展屏不仅具备卓越的性能和广泛的兼容性&#xff0c;更能在多个应用场景中发挥出其独特的价值。 USB2.0显卡&#xff…

项目二技巧一

目录 nginx实现根据域名来访问不同的ip端口 配置Maven私服 快照版和发布版的区别 快照版本&#xff08;Snapshot&#xff09; 发布版本&#xff08;Release&#xff09; 导入发布版的父工程 理清楚授权规则 一.首先浏览器发送/manager/**路径请求 第二步&#xff1a;构造…

如何更好地设计SaaS系统架构

SaaS&#xff08;Software as a Service&#xff09;架构设计的核心目标是满足多租户需求、支持弹性扩展和高性能&#xff0c;同时保持低成本和高可靠性。一个成功的SaaS系统需要兼顾技术架构、资源利用、用户体验和商业目标。本文从以下几个方面探讨如何更好地设计SaaS系统架构…

手搓一个不用中间件的分表策略

场景&#xff1a;针对一些特别的项目&#xff0c;不用中间件&#xff0c;以月为维度进行分表&#xff0c;代码详细设计方案 1. 定义分片策略 首先&#xff0c;定义一个分片策略类&#xff0c;用于决定数据存储在哪个分表中 import java.time.LocalDate; import java.time.fo…

详解SpringCloud集成Camunda7.19实现工作流审批(二)

本章将分享的是camunda流程设计器--Camunda Modeler的基本使用&#xff08;对应camunda版本是7.19&#xff09;&#xff0c;包括bpmn流程图画法&#xff0c;各种控件使用以及一些日常业务场景的流程图的实现 参考资料&#xff1a; Camunda BPMN 基础组件-CSDN博客 Camunda: Exe…

webpack(react)基本构建

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 Webpack 是一个现代 JavaScript 应用程序的静态模块打包工具。它的主要功能是将各种资源&#xff08;如 JavaScript、CSS、图片等&#xff09;视为模块&#xff0c;并将它们打包成一个或多个输出文件&#xff0c;以便…

html select下拉多选 修改yselect.js插件实现下拉多选,搜索,限制选中,默认回显等操作

需求&#xff1a;要在select标签实现下拉多选&#xff0c;搜索&#xff0c;限制选中&#xff0c;默认回显等操作&#xff0c;之前同事用的yselect.js&#xff0c;网上用的简直是寥寥无几&#xff0c;找了半天没找到限制选中的方法&#xff0c;看了源代码才发现根本没有&#xf…

c++哈希表(原理、实现、开放寻址法)适合新手

c系列哈希的原理及实现&#xff08;上&#xff09; 文章目录 c系列哈希的原理及实现&#xff08;上&#xff09;前言一、哈希的概念二、哈希冲突三、哈希冲突解决3.1、开放寻址法3.2、删除操作3.3、负载因子四、代码实现 总结 前言 红黑树平衡树和哈希有不同的用途。 红黑树、…

了解HTTPS以及CA在其中的作用

在这个信息爆炸的时代&#xff0c;每一次指尖轻触屏幕&#xff0c;都是一次数据的旅行。但您是否真正了解&#xff0c;这些数据在通往目的地的旅途中&#xff0c;是如何被保护的呢&#xff1f; HTTPS&#xff08;HyperText Transfer Protocol Secure&#xff09;是一种安全的网…

electron-vite_14窗口默认全屏铺满

有时候应用打包后&#xff0c;希望全屏显示;而默认的宽度和高度,是无法满足的;这时需要单独处理; 核心代码 // 1.引入screen对象 import { BrowserWindow, screen } from electron; function createWindow(): void {// 2.获取屏幕尺寸const { width, height } screen.getPrim…

mysql-为什么需要线程池

mysql-为什么需要线程池 MySQL线程池的概述与应用 MySQL线程池是MySQL数据库中的一个重要组件&#xff0c;旨在提高数据库的性能、吞吐量和可伸缩性。它通过管理数据库服务器的线程生命周期&#xff0c;减少了线程的创建和销毁的开销&#xff0c;并通过优化资源使用&#xff…

【接口封装】——10、系统托盘

解释&#xff1a; 1、定义好按钮的状态&#xff1a;创建 map 映射关系&#xff0c;即 一个名字对应一个按钮 2、对不同按钮实现不同的信号槽函数 头文件&#xff1a; #include "SysTrayIcon.h" #include <qwidget.h> #include "define.h" #include &…

Nginx——配置部署域名服务器路由nginx

文章目录 基本配置报错解决只能通过[域名]:[端口]/[API路径]的方式请求 基本配置 user www-data; worker_processes auto;error_log /var/log/nginx/error.log notice; pid /run/nginx.pid;events {worker_connections 1024; }http {include /etc/nginx/mime…

C. Raspberries

time limit per test 2 seconds memory limit per test 256 megabytes You are given an array of integers a1,a2,…,ana1,a2,…,an and a number kk (2≤k≤52≤k≤5). In one operation, you can do the following: Choose an index 1≤i≤n1≤i≤n,Set aiai1aiai1. F…

Flink学习连载文章8--时间语义

Time的分类 (时间语义) EventTime:事件(数据)时间,是事件/数据真真正正发生时/产生时的时间 IngestionTime:摄入时间,是事件/数据到达流处理系统的时间 ProcessingTime:处理时间,是事件/数据被处理/计算时的系统的时间 EventTime的重要性 假设&#xff0c;你正在去往地下停…

sizeof和strlen区分,(好多例子)

sizeof算字节大小 带\0 strlen算字符串长度 \0之前

Simulink的SIL软件在环测试

以基于模型的设计&#xff08;MBD&#xff09;的软件开发时&#xff0c;需要进行SIL&#xff08;软件在环测试&#xff09;。SIL测试就是在PC上验证模型是否与代码功能一致。在项目开展中&#xff0c;用在需要将控制器生成移植到硬件前&#xff0c;把控制器的模块生成代码&…