当前位置: 首页 > news >正文

玩转Rust高级应用 如何让让运算符支持自定义类型,通过运算符重载的方式是针对自定义类型吗?

运算符重载

Rust 允许一部分运算符重载,用户可以让这些运算符支持自定义类型。运算符重载 的方式是:针对自定义类型, impl 一 些在标准库中预定义好的trait,这 些trait 都存在于std::ops模块中。比如前面已经讲过了的Deref trait就属于运算符重载。

本章我们以最基本的Add trait来做讲解。Add 代表的是加法运算符+重载。它的定义是:

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

它具备一个泛型参数RHS 和一个关联类型Output。 其 中RHS 有一个默认值Self。标准库早已经为基本数字类型实现了这个trait。比如:

impl Add<i32>for i32 {type Output =i32;
}

而且还有:

impl < 'a>Add<i32>for	&'a i32 type Output = <i32 as Add < i32 >> ::Output;
impl < 'a>Add<&'a i32 >
for i32 type Output = <i32 as Add < i32 >> ::Output;
impl < 'a,'b > Add < &'a	i32>for	&'b i32 type Output = <i32 as Add < i32 >> ::Output;

这意味着,不仅i32+i32 是允许的,而且i32+&i32 、&i32+i32 、&i32+&i32 这几种形式也都是允许的。它们的返回类型都是i32。
假如我们现在自己定义了一个复数类型,想让它支持加法运算符,示例如下:

use std: :ops: :Add;# [derive(Copy, Clone, Debug, PartialEq)] struct Complex {real: i32,imaginary: i32,
}
impl Add
for Complex {type Output = Complex;fn add(self, other: Complex) - >Complex {Complex {real: self.real + other.real,imaginary: self.imaginary + other.imaginary,}}fn main() {let c1 = Complex {real: 1,imaginary: 2};let c2 = Complex {real: 2,imaginary: 4};println ! ("{:?}", c1 + c2);}

在这个实现中,我们没有指定泛型参数RHS, 所以它就采用了默认值,在此示例中就相 当于Complex 这个类型。同理,如果我们希望让这个复数能支持与更多的类型求和,可以 继 续 写 多 个 impl:

impl < 'a>Add<&'a Complex >
for Complex {type Output = Complex;fn add(self, other: &'a Complex{
real:self.real
imaginary:self.imaginary	Complex)->Complex{+other.real,+other.imaginary,
}
impl Add<i32>for Complex{
type  Output  =Complex;
fn add(self,other:i32)->Complex{
Complex{ real:self.real+other,imaginary:self.imaginary,
}

I/O

标准库中也提供了一系列I/O相关的功能。虽然功能比较基础,但好在是跨平台的。如 果用户需要更丰富的功能,可以去寻求外部的开源库。

平台相关字符串

要跟操作系统打交道,首先需要介绍的是两个字符串类型:OsString 以及它所对应的 字符串切片类型OsStr。 它们存在于std::ffi 模块中。

Rust标准的字符串类型是String 和 str。 它们的一个重要特点是保证了内部编码是 统一的utf-8。但是,当我们和具体的操作系统打交道时,统一的 utf-8 编码是不够用的,某 些操作系统并没有规定一定是用的 utf-8编码。所以,在和操作系统打交道的时候,String /str 类型并不是一个很好的选择。比如在Windows 系统上,字符一般是用16位数字来表 示的。

为了应付这样的情况,Rust在标准库中又设计了OsString /OsStr来处理这样的情 况。这两种类型携带的方法跟String /str非常类似,用起来几乎没什么区别,它们之 间也可以相互转换。

举个需要用到OsStr 场景的例子:

use std: :path: :PathBuf;
fn main() {let mut buf = PathBuf: :from("/");buf.set_file_name("bar");if let Some(s) = buf.to_str() {println ! ("{}", s);} else {println ! ("invalid path");}
}

上面这个例子是处理操作系统中的路径,就必须用OsString /OsStr这两个类型。 PathBuf 的 set_file_name 方法的签名是这样的:它要求,第二个参数必须满足ASRef 可以看到的约束。而查看 str 类型的文档,我们所以,&str 类型可以直接作为参数在这个方法中使用。

另外,当我们想把&PathBuf转为 &str 类型的时候,使用了to_str方法,返回的是 一 个Option<&str> 类型。这是为了错误处理。因为 PathBuf 内部是用Osstring 存储 的字符串,它未必能成功转为utf-8编码。而想要把&PathBuf 转 为 &OsStr 则简单多了, 这种转换不需要错误处理,因为它们是同样的编码。


文件和路径

Rust 标 准 库 中 用PathBuf 和 Path 两个类型来处理路径。它们之间的关系就类似 String 和 str 之间的关系: 一个对内部数据有所有权,还有一个只是借用。实际上,读源 码 可 知 ,PathBuf里面存的是 一个OsString,Path里面存的是一个OsStr 。 这两个类型定义在std::path 模块中。

Rust对文件操作主要是通过std::fs::File 来完成的。这个类型定义了一些成员方 法,可以实现打开、创建、复制、修改权限等文件操作。 std::fs模块下还有一些独立函 数,比如 remove_file 、soft_link等,也是非常有用的。

对文件的读写,则需要用到std::io 模块了。这个模块内部定义了几个重要的trait, 比 如Read/Write。File类型也实现了Read 和 Write 两 个trait, 因此它拥有一系列方便读写文件的方法,比如 read 、read_to_end 、read_to_string 等。这个模块还定义了 BufReader等类型。我们可以把任何一个满足Read trait的类型再用BufReader包 一 下,实现有缓冲的读取。

下面用一个示例来演示说明这些类型的使用方法:

use std: :io: :prelude: :*;
use std: :io: :BufReader;
use std: :fs: :File;
fn test_read_file() - >Result < (),
std: :io: :Error > {let mut path = std: :env: :home_dir().unwrap();path.push(".rustup");path.push("settings");path.set_extension("toml");let file = File: :open( & path) ? ;let reader = BufReader: :new(file);for line in reader.lines() {println ! ("Read a line:{}", line ? );}Ok(())
}
fn main() {match test_read_file() {Ok(_) = >{}Err(e) = >{println ! ("Error occured:{}", e);}}

标准输入输出

前面我们已经多次使用了println!宏输出一些信息。这个宏很方便,特别适合在小程 序中随手使用。但是如果你需要对标准输入输出作更精细的控制,则需要使用更复杂一点的 办法。

在 C++ 里面,标准输入输出流cin 、cout是全局变量。在Rust 中,基于线程安 全的考虑,获取标准输入输出的实例需要调用函数,分别为std::io::stdin()和 std::io::stdout()。stdin()函数返回的类型是Stdin 结构体。这个结构体本身已经实现了Read trait,所以,可以直接在其上调用各种读取方法。但是这样做效率比较低,因 为为了线程安全考虑,每次读取的时候,它的内部都需要上锁。提高执行效率的办法是手动 调用lock() 方法,在这个锁的期间内多次调用读取操作,来避免多次上锁。

示例如下:

use use fn let let letstd: :io: :prelude: :*;
std: :io: :BufReader;
test_stdin() - >Result < (),
std: :io: :Error > {stdin = std: :i: :stdin();handle = stdin.lock();reader = BufReader: :new(handle);for line in reader.lines() {let line = line ? ;if line.is_empty() {return Ok(());println ! ("Read a line:{}", line);}Ok(())}fn main() {match test_stdin() {Ok(_) = >{}Err(e) = >{println ! ("Error occured:{}", e);} {}
}

进程启动参数

大家应该注意到了,Rust的 main 函数的签名和C/C++不一致。在C/C++ 里面,一般 进程启动参数是直接用指针传递给main 函数的,进程返回值是通过main 函数的返回值来决 定 的 。

在Rust 中,进程启动参数是调用独立的函数std::env::args()来得到的,或者使 用std::env::args_os()来得到,进程返回值也是调用独立函数std::process::

exit()来指定的。示例如下:

fn main() {if std: :env: :args().any( | arg | arg == "-kill") {std: :process: :exit(1);}for arg in std: :env: :args() {println ! ("{}", arg);}
}

同样,标准库只提供最基本的功能。如果读者需要功能更强大、更容易使用的命令行参 数解析器,可以到crates.io上搜索相关开源库,clap 或者getopts 都是很好的选择。

http://www.dtcms.com/a/585875.html

相关文章:

  • 基于Keras的MNIST手写数字识别卷积神经网络设计与实现
  • 百度资料怎么做网站型云网站建设
  • IP配置的基本要求
  • 单母线接线典型操作顺序
  • LightGBM三部曲:LightGBM原理
  • 【C++】C++中的文件IO
  • wordpress手机站如何做负面口碑营销案例
  • 谷歌黑客语法挖掘 SQL 注入漏洞
  • ps做网站logo青海做网站多少钱
  • Qt开发——环境搭建
  • 32HAL——RTC时钟
  • C#知识补充(一)——ref和out、成员属性、万物之父和装箱拆箱、抽象类和抽象方法、接口
  • 专业的设计网站建设网站做地区定位跳转
  • 网站做三层结构南京建设网站哪家好
  • pyautocad 获取选择线段的近似最小包围盒 (OBB) 三分搜索
  • Git Commit 高频提示详解:用户名邮箱配置及其他常见提示解决方案
  • 打开网站图片弹入指定位置代码网络域名备案查询
  • 豆包 Spring 常用注解详解及分类
  • 企业网站建设收费最大的网站开发公司
  • 服务器运维(六)跨域配置 Preflight 问题——东方仙化神期
  • 第三次作业-第四章网站搭建
  • React 17
  • Linux:多路转接
  • 为什么国内禁用docker呢?
  • 石家庄行业网站深圳建筑工地招工招聘信息
  • 云溪网络建站宝盒wordpress发文章套模版
  • 人在虚弱的时候真的能看到鬼
  • zabbix原生高可用集群应用实战
  • flink1.20.2环境部署和实验-1
  • 网站主目录程序开发步骤不包括