【2025 Rust学习 --- 10 运算符重载】

news2025/1/10 20:49:43

重载操作符

算术运算符与按位运算符

Rust 中,表达式 a + b 实际上是 a.add(b) 的简写形式,也就是对标准库 中 std::ops::Add 特型的 add 方法的调用。Rust 的标准数值类型都实现了 std::ops::Add。

trait Add<Rhs = Self> {
 type Output;
 fn add(self, rhs: Rhs) -> Self::Output;
}

复数实现特型

use std::ops::Add;
impl Add for Complex<i32> {
 type Output = Complex<i32>;
 fn add(self, rhs: Self) -> Self {
     Complex {
         re: self.re + rhs.re,
         im: self.im + rhs.im,
     }
 }
}

这只实现了i32的支持,使用泛型支持更多:

use std::ops::Add;
impl<T> Add for Complex<T> where T: Add<Output=T>,
{
 type Output = Self;
 fn add(self, rhs: Self) -> Self {
     Complex {
         re: self.re + rhs.re,
         im: self.im + rhs.im,
     }
 }
}

where T: Add,将 T 限界到能与自身相加并产生 另一个 T 值的类型。

Add 特型不要求 + 的两个操作数具有相同的类型,也不限制结果类型。因 此,一个尽可能泛化的实现可以让左右操作数独立变化,并生成加法所能生成的 任何组件类型的 Complex 值:

use std::ops::Add;
impl<L, R> Add<Complex<R>> for Complex<L>
where
 L: Add<R>,
{
 type Output = Complex<L::Output>;
 fn add(self, rhs: Complex<R>) -> Self::Output {
     Complex {
         re: self.re + rhs.re,
         im: self.im + rhs.im,
     }
 }
}

一元运算符

Rust 的所有带符号数值类型都实现了 std::ops::Neg,以支持一元取负运算 符 -;整数类型和 bool 实现了 std::ops::Not,以支持一元取反运算符 !。 还有一些是针对这些类型的引用的实现。

! 运算符会对 bool 值进行取反,而对整数执行按位取反,扮演 着 C 和 C++ 中的 ! 运算符和 ~ 运算符。

二元运算符

Rust 不允许 + 的左操作数是 &str 类型,以防止通过在左侧重复接入小型 片段来构建长字符串

(这种方式性能不佳,其时间复杂度是字符串最终长度的平方,一般来说,write! 宏更适合从小型片段构建出字符串)

复合赋值运算符

在 Rust 中,x += y 是方法调用 x.add_assign(y) 的简写形式,其中 add_assign 是 std::ops::AddAssign 特型的唯一方法:

trait AddAssign<Rhs = Self> {
 fn add_assign(&mut self, rhs: Rhs);
}

在这里插入图片描述

Rust 的所有数值类型都实现了算术复合赋值运算符。Rust 的整数类型和 bool 类型都实现了按位复合赋值运算符。

use std::ops::AddAssign;
impl<T> AddAssign for Complex<T>
where
 T: AddAssign<T>,
{
 fn add_assign(&mut self, rhs: Complex<T>) {
     self.re += rhs.re;
     self.im += rhs.im;
 }
}

实现 std::ops::Add并不会自动实现 std::ops::AddAssign,如果想让 Rust 允许你的类型作为 += 运算符的左操作数,就必须自行实现 AddAssign。

相等性比较

Rust 的相等性运算符 == 和 != 是对调用 std::cmp::PartialEq 特型的 eq 和 ne 这两个方法的简写:

assert_eq!(x == y, x.eq(&y));
assert_eq!(x != y, x.ne(&y));

trait PartialEq<Rhs = Self>
where
 Rhs: ?Sized,
{
     fn eq(&self, other: &Rhs) -> bool;
     fn ne(&self, other: &Rhs) -> bool {
     !self.eq(other)
 }
}

impl<T: PartialEq> PartialEq for Complex<T> {
 fn eq(&self, other: &Complex<T>) -> bool {
 self.re == other.re && self.im == other.im
 }
}

PartialEq 的实现就是将左操作数的每个字段与右操 作数的相应字段进行比较。

只需把 PartialEq 添加到类型定义的 derive 属性中即可避免书写:

#[derive(Clone, Copy, Debug, PartialEq)]
struct Complex<T> {
 ...
}

Rust 自动生成的实现与手写的代码本质上是一样的,都会依次比较每个字段或 类型的元素。

与按值获取操作数的算术特型和按位运算特型不同,PartialEq 会通过引用获 取其操作数。这意味着在比较诸如 String、Vec 或 HashMap 之类的非 Copy 值时并不会导致它们被移动

where
 Rhs: ?Sized,

上面这种书写放宽了 Rust 对类型参数必须有固定大小的常规要求,能让我们写出像 PartialEq 或 PartialEq<[T]> 这样的特型。

str 实现了 PartialEq,下面两种等效:

assert!("ungula" != "ungulate");
assert!("ungula".ne("ungulate"));

像 0.0/0.0 和其他没 有适当值的表达式必须生成特殊的非数值,通常叫作 NaN 值。该标准进一步要 求将 NaN 值视为与包括其自身在内的所有其他值都不相等

如果你的泛型代码想要 “完全相等”关系,那么可以改用 std::cmp::Eq 特型作为限界,它表示完全相 等关系

如果类型实现了 Eq,则对于该类型的每个值 x,x == x 都必须为 true。

几乎所有实现了 PartialEq 的类型都实现了 Eq,而 f32 和 f64 是标准库中仅有的两个属于 PartialEq 却不属于 Eq 的类型

Eq 定义为 PartialEq 的扩展

有序比较

Rust 会根据单个特型 std::cmp::PartialOrd 来定义全部的有序比较运算符 <、>、<= 和 >= 的行为:

trait PartialOrd<Rhs = Self>: PartialEq<Rhs>
where
 Rhs: ?Sized,
{
 fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>;
 fn lt(&self, other: &Rhs) -> bool { ... }
 fn le(&self, other: &Rhs) -> bool { ... }
 fn gt(&self, other: &Rhs) -> bool { ... }
 fn ge(&self, other: &Rhs) -> bool { ... }
}

PartialOrd 扩展了 PartialEq:只有可以比较相等性 的类型才能比较顺序性。

enum Ordering {
 Less, // self < other
 Equal, // self == other
 Greater, // self > other
}

如果 partial_cmp 返回 None,那么就意味着 self 和 other 相对于彼 此是无序的,即两者都不大于对方,但也不相等。在 Rust 的所有原始类型中, 只有浮点值之间的比较会返回 None:具体来说,将 NaN 值与任何其他值进行 比较都会返回 None。

要比较 Left 和 Right 这两种类型的值,那么 Left 就必须实现 PartialOrd。像 x < y 或 x >= y 这样的表达式 都是调用 PartialOrd 方法的简写形式

trait Ord: Eq + PartialOrd<Self> {
 fn cmp(&self, other: &Self) -> Ordering;
}

cmp 方法只会返回 Ordering,而不会像 partial_cmp 那样返回 Option:cmp 总会声明它的两个参数相等或指出它们的相对顺 序。几乎所有实现了 PartialOrd 的类型都应该实现 Ord。在标准库中,f32 和 f64 是该规则的例外情况

希望按上限排序一些区间,那么很容易用 sort_by_key 来实现:

intervals.sort_by_key(|i| i.upper);

Index 与 IndexMut

通过实现 std::ops::Index 特型和 std::ops::IndexMut 特型,你可以规 定像 a[i] 这样的索引表达式该如何作用于你的类型。数组可以直接支持 [] 运 算符

a[i] 通常是 *a.index(i) 的简写形式, 其中 index 是 std::ops::Index 特型的方法。但是,如果表达式被赋值或 借用成了可变形式,那么 a[i] 就是对调用 std::ops::IndexMut 特型方法 的 *a.index_mut(i) 的简写。

trait Index<Idx> {
 type Output: ?Sized;
 fn index(&self, index: Idx) -> &Self::Output;
}
trait IndexMut<Idx>: Index<Idx> {
 fn index_mut(&mut self, index: Idx) -> &mut Self::Output;
}

使用单个 usize 对 切片进行索引,以引用单个元素,因为切片实现了 Index。还可以使 用像 a[i…j] 这样的表达式来引用子切片,因为切片也实现了 Index<Range<usize>>

原型:*a.index(std::ops::Range { start: i, end: j })

Rust 的 HashMap 集合和 BTreeMap 集合允许使用任何可哈希类型或有序类型 作为索引。以下代码之所以能运行,是因为 HashMap<&str, i32> 实现了 Index<&str>:

use std::collections::HashMap;
let mut m = HashMap::new();
m.insert("十", 10);
m.insert("百", 100);
m.insert("千", 1000);
assert_eq!(m["十"], 10);
assert_eq!(m["千"], 1000);

当索引表达式出现在需要可变 引用的上下文中时,Rust 会自动选择 index_mut

一下两种相等:

let mut desserts =
 vec!["Howalon".to_string(), "Soan papdi".to_string()];
desserts[0].push_str(" (fictional)");
desserts[1].push_str(" (real)");

use std::ops::IndexMut;
(*desserts.index_mut(0)).push_str(" (fictional)");
(*desserts.index_mut(1)).push_str(" (real)");

IndexMut 有一个限制,即根据设计,它必须返回对某个值的可变引用。这就是 不能使用像 m[" 十 "] = 10;

的赋值语句。因为创建这么一个马上就会因赋值而被丢弃的可变引用是一种浪费

编写 image[row][column] 时,如果 row 超出范围,那么 .index() 方法在试图索引 self.pixels 时也会超出范围,从而引发 panic。 这就是 Index 实现和 IndexMut 实现的行为方式:检测到越界访问并导致 panic,就像索引数组、切片或向量时越界一样。

其他运算符

并非所有运算符都可以在 Rust 中重载。

  • 从 Rust 1.50 开始,错误检查运算符 ? 仅适用于 Result 值和 Option 值

  • 逻辑运算符&& 和 ||仅限于 bool 值。

  • .. 运算符和 ..= 运算符总 会创建一个表示范围边界的结构体

  • & 运算符总是会借用引用

  • = 运算符总是会 移动值或复制值。它们都不能重载。

  • 解引用运算符 *val 和用于访问字段和调用方法的点运算符(如 val.field 和 val.method())可以用 Deref 特型和 DerefMut 特型进行重载,后序介绍。

  • Rust 不支持重载函数调用运算符 f(x)。当你需要一个可调用的值时,通常只需 编写一个闭包即可。后面介绍 Fn、FnMut 和 FnOnce 这几个特殊特型。

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

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

相关文章

node-sass@4.14.1报错的最终解决方案分享

输入npm i全安装文件所需的依赖的时候&#xff0c;博主是使用sass去书写的&#xff0c;使用的是node-sass4.14.1和sass-loader7.3.1的版本的&#xff0c;安装的时候老是出现错误&#xff0c; node-sass4.14.1版本不再被支持的原因 node-sass 是一个基于 LibSass 的 Node.js 绑…

LabVIEW大数据有什么应用场景?

LabVIEW在处理大数据时主要依赖于其强大的数据采集、信号处理、控制、以及实时系统的功能。以下是一些典型的应用场景&#xff1a; ​ 1. 工业自动化与制造 数据采集与监控&#xff1a;在生产线上&#xff0c;LabVIEW可以用来收集大量的传感器数据&#xff08;如温度、压力、湿…

深入理解Mybatis原理》MyBatis的sqlSessi

sqlSessionFactory 与 SqlSession 正如其名&#xff0c;Sqlsession对应着一次数据库会话。由于数据库会话不是永久的&#xff0c;因此Sqlsession的生命周期也不应该是永久的&#xff0c;相反&#xff0c;在你每次访问数据库时都需要创建它&#xff08;当然并不是说在Sqlsession…

【OAuth2系列】如何使用OAuth 2.0实现安全授权?详解四种授权方式

作者&#xff1a;后端小肥肠 &#x1f347; 我写过的文章中的相关代码放到了gitee&#xff0c;地址&#xff1a;xfc-fdw-cloud: 公共解决方案 &#x1f34a; 有疑问可私信或评论区联系我。 &#x1f951; 创作不易未经允许严禁转载。 姊妹篇&#xff1a; 【OAuth2系列】集成微…

Open WebUI 与 AnythingLLM 安装部署

在前文 Ollama私有化部署大语言模型LLM&#xff08;上&#xff09;-CSDN博客 中通过Ollama来搭建运行私有化大语言模型&#xff0c;但缺少用户交互的界面&#xff0c;特别是Web可视化界面。 对此&#xff0c;本文以Open WebUI和AnythingLLM为例分别作为Ollama的前端Web可视化界…

如何稳定使用 O1 / O1 Pro,让“降智”现象不再困扰?

近期&#xff0c;不少朋友在使用 O1 或 O1 Pro 模型时&#xff0c;都会碰到“降智”或“忽高忽低”的智力波动&#xff0c;比如无法识图、无法生成图片、甚至回答准确度也不稳定。面对这些问题&#xff0c;你是不是也感到头疼呢&#xff1f; 为了找到更可靠的解决办法&#xf…

RK3562编译Android13 ROOT固件教程,触觉智能开发板演示

本文介绍编译Android13 ROOT权限固件的方法&#xff0c;触觉智能RK3562开发板演示&#xff0c;搭载4核A53处理器&#xff0c;主频高达2.0GHz&#xff1b;内置独立1Tops算力NPU&#xff0c;可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。 关闭seli…

58. Three.js案例-创建一个带有红蓝配置的半球光源的场景

58. Three.js案例-创建一个带有红蓝配置的半球光源的场景 实现效果 本案例展示了如何使用Three.js创建一个带有红蓝配置的半球光源的场景&#xff0c;并在其中添加一个旋转的球体。通过设置不同的光照参数&#xff0c;可以观察到球体表面材质的变化。 知识点 WebGLRenderer …

React+redux项目搭建流程

1.创建项目 create-react-app my-project --template typescript // 创建项目并使用typescript2.去除掉没用的文件夹&#xff0c;只保留部分有用的文件 3.项目配置&#xff1a; 配置项目的icon 配置项目的标题 配置项目的别名等&#xff08;craco.config.ts&…

解决GitHub上的README.md文件的图片内容不能正常显示问题

一、问题描述 我们将项目推送到GitHub上后&#xff0c;原本在本地编写配置好可展现的相对路径图片内容&#xff0c;到了GitHub上却不能够正常显示图片内容&#xff0c;我们希望能够在GitHub上正常显示图片&#xff0c;如下图所示&#xff1a; 二、问题分析 现状&#xff1a;REA…

双模充电桩发展前景:解锁新能源汽车未来的金钥匙,市场潜力无限

随着全球能源转型的浪潮席卷而来&#xff0c;新能源汽车行业正以前所未有的速度蓬勃发展&#xff0c;而作为其坚实后盾的充电基础设施&#xff0c;特别是双模充电桩&#xff0c;正逐渐成为推动这一变革的关键力量。本文将从多维度深入剖析双模充电桩的市场现状、显著优势、驱动…

开关不一定是开关灯用 - 命令模式(Command Pattern)

命令模式&#xff08;Command Pattern&#xff09; 命令模式&#xff08;Command Pattern&#xff09;命令设计模式命令设计模式结构图命令设计模式涉及的角色 talk is cheap&#xff0c; show you my code总结 命令模式&#xff08;Command Pattern&#xff09; 命令模式&…

Qt 5.14.2 学习记录 —— 칠 QWidget 常用控件(2)

文章目录 1、Window Frame2、windowTitle3、windowIcon4、qrc机制5、windowOpacity 1、Window Frame 在运行Qt程序后&#xff0c;除了用户做的界面&#xff0c;最上面还有一个框&#xff0c;这就是window frame框。对于界面的元素&#xff0c;它们的原点是Qt界面的左上角或win…

LabVIEW水轮发电机组振动摆度故障诊断

本文介绍了基于LabVIEW的水轮发电机组振动摆度故障诊断系统的设计与实施过程。系统在通过高效的故障诊断功能&#xff0c;实现水轮发电机组的振动、温度等关键指标的实时监控与智能分析&#xff0c;从而提高电力设备的可靠性和安全性。 ​ 项目背景 随着电力行业对设备稳定性…

【JavaEE】—— SpringBoot项目集成百度千帆AI大模型(对话Chat V2)

本篇文章在SpringBoot项目中集成百度千帆提供的大模型接口实现Chat问答效果&#xff1a; 一、百度智能云 百度千帆大模型平台是百度智能云推出的一个企业级一站式大模型与AI原生应用开发及服务平台。 注册地址&#xff1a;https://qianfan.cloud.baidu.com/ 注册成功后&…

ARM交叉编译Boost库

Boost下载&#xff1a;点击跳转 编译过程&#xff1a; 生成project-config.jam ./bootstrap.sh --with-librariesfilesystem,thread --with-toolsetgcc 2. 修改project-config.jam&#xff08;位于第12行附近&#xff09; if ! gcc in [ feature.values <toolset> ] …

Cpp::C++11右值引用与移动构造(30)

文章目录 前言一、左值 & 右值二、左值引用 & 右值引用三、右值引用的意义四、右值引用和移动语义五、与编译器优化做的对比六、右值引用引用左值七、一些小问题能否将函数返回值设为 右值引用&#xff1f;函数传值返回&#xff0c;但在返回时能否手动 move 返回值&…

LeetCode:108.将有序数组转换为二叉搜索树

跟着carl学算法&#xff0c;本系列博客仅做个人记录&#xff0c;建议大家都去看carl本人的博客&#xff0c;写的真的很好的&#xff01; 代码随想录 LeetCode&#xff1a;108.将有序数组转换为二叉搜索树 给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff…

基于Redisson实现重入锁

一. 分布式锁基础 在分布式系统中&#xff0c;当多个客户端&#xff08;应用实例&#xff09;需要访问同一资源时&#xff0c;可以使用分布式锁来确保同一时刻只有一个客户端能访问该资源。Redis作为高性能的内存数据库&#xff0c;提供了基于键值对的分布式锁实现&#xff0c…

React中createRoot函数原理解读——Element对象与Fiber对象、FiberRootNode与HostRootNode

【2024最新版】React18 核心源码分析教程&#xff08;全61集&#xff09; Element对象与Fiber对象 在 React 中&#xff0c;Element 对象 和 Fiber 对象 是核心概念&#xff0c;用于实现 React 的高效渲染和更新机制。以下是它们的详细解读&#xff1a; 1. Element 对象 定…