特性
特性(Trait)是对方法集合的抽象,类似于 java 中的接口,特性是类型的行为规范,它宏观地对类型的功能做出要求,一次实现对多类别在特定情况下的使用统一化
定义特性
关键字是 trait
trait <特性名称> {
<方法...>
}
下面是对比较大小特性的定义
trait Comparable {
fn greater(&self, b: &Self) -> bool;
fn less(&self, b: &Self) -> bool;
fn equals(&self, b: &Self) -> bool;
}
&Self
表示的是和自己相同的类型
实现特性
实现特性的语法是通过 impl
语句块,在此基础上加点东西
impl <特性名> for <实现特性的类> {
<实现的函数/方法...>
}
下面演示一个圆结构体的 Comparable 特性
struct Circle {
radius: f64,
center: (f64, f64),
}
impl Comparable for Circle {
fn greater(&self, b: &Circle) -> bool {
self.radius > b.radius
}
fn less(&self, b: &Circle) -> bool {
self.radius < b.radius
}
fn equals(&self, b: &Circle) -> bool {
self.radius == b.radius
}
}
fn main() {
let c1 = Circle {
radius: 10.0,
center: (1.0, 2.0),
};
let c2 = Circle {
radius: 11.0,
center: (1.0, 2.0),
};
println!("c1 > c2 ? {}", c1.greater(&c2));
println!("c1 < c2 ? {}", c1.less(&c2));
println!("c1 = c2 ? {}", c1.equals(&c2));
}
使用 impl
块实现特性时要注意以下几点:
- 同一个类可以实现多个特性
- 每一个 impl 块只能实现一个特性
- 如果一个 impl 块用于实现某个特性,这个 impl 块中不能出现不属于所实现特性的方法定义
默认特性
特性在定义的时候允许直接定义方法作为实现中没有实现的方法的默认替代品,有点继承的感觉
trait Printable {
fn print(&self);
fn println(&self) {
self.print();
println!("[END]");
}
}
struct Text {
content: String
}
impl Printable for Text {
fn print(&self) {
print!("{}", self.content);
}
}
fn main() {
let text = Text {
content: String::from("This is a piece of text."),
};
text.println();
}
特性作参数
如果需要把特性作为一种参数类型出现在函数的参数列表中,在函数调用参数对象时,不必得知参数的具体类型,而能调用各种实现了该特性的类型的具体方法,由于不需要知道参数是什么类型的,所以无法使用参数的其他属性和方法
fn <函数名>(object: impl <特性名称>) -> <返回> {
...
}
下面演示一个基于 Comparable 的选择排序:
trait Comparable {
fn greater(&self, b: &Self) -> bool;
fn less(&self, b: &Self) -> bool;
fn equals(&self, b: &Self) -> bool;
}
fn select_sort(array: &mut [&impl Comparable]) {
for i in 0..array.len() {
let mut k = i;
for j in (i + 1)..array.len() {
if array[j].less(&array[k]) {
k = j;
}
}
if k != i {
let t = array[k];
array[k] = array[i];
array[i] = t;
}
}
}
impl Comparable for f64 {
fn greater(&self, b: &f64) -> bool {
*self > *b
}
fn less(&self, b: &f64) -> bool {
*self < *b
}
fn equals(&self, b: &f64) -> bool {
*self == *b
}
}
fn main() {
let fa = [1.0, 4.0, 5.0, 2.0, 3.0];
let mut ra = [&fa[0], &fa[1], &fa[2], &fa[3], &fa[4]];
select_sort(&mut ra);
for f in ra {
println!("{}", f);
}
}
&mut [&impl Comparable]
是新的语法点,impl Comparable
表示实现了这个特性
这个引用数组 ra 是不是有点小丑?我是不懂这种语法有什么用,看的头晕:dizzy_face:
泛型特性参数
其实是用来简写
fn function(a: impl SomeTrait, b: impl SomeTrait, c: impl SomeTrait) {
...
}
为
fn function<T: SomeTrait>(a: T, b:T, c:T) {
...
}
是不是干净很多,但是使用的前提是这三个参数的具体类型一样,当这三个参数的具体类型不同时,不能使用这个语法
特性叠加
参数需要同时实现多个特性
fn function(a: impl TraitA + TraitB)
复杂的实现关系可以使用 where
关键字优化
fn function(a: impl A + B, b: impl B + C) -> i32
// 简化为
fn function<T, U>(a: T, b: U) -> i32
where T: Display + Clone,
U: Clone + Debug
特性做返回值
特性作为返回值的时候,可以返回同种实现了这个特性可的类型的对象,什么是同种?意思就是:
下面这段代码是无法通过编译的,虽然 i32 和 f64 都实现了 A,但是类型不一样,所以不能这样返回
trait A {}
impl A for i32 {}
impl A for f64 {}
fn give() -> impl A {
let a: i32 = 233;
let b: f64 = 233;
if xxx {
return a;
}
return b;
}
有条件的实现方法
有一个泛型 A<T>
和一个特性 B,现在需要 A 实现一个方法 d, 前提是 A 的泛型 T 实现了特性 B, 请问怎么写
答案是:
struct A<T>;
trait B {}
impl<T: B > A<T> {
}
文件 I/O
会用就行,和大多数语言一样
use std::fs;
use std::fs::File;
use std::io::Read; // 读入必要的use
use std::io::Write; // 写入必要的use
use std::fs::OpenOptions;
fn main() {
// 直接读文本
let text = fs::read_to_string("tmp").unwrap();
print!("{}", text);
// 直接读二进制
let binary = fs::read("tmp").unwrap();
let text = String::from_utf8(binary).unwrap();
println!("text");
// 打开文件读取为File类型
let mut file = File::open("tmp").unwrap();
// File类型直接读为字符串
let mut text = String::new();
file.read_to_string(&mut text);
// File类型读为二进制
let mut binary = Vec::<u8>::new();
file.read_to_end(&mut binary).unwrap();
let text = String::from_utf8(binary).unwrap();
// 使用数组读File
let mut file = File::open("tmp").unwrap();
let mut binary: [u8; 5] = [0_u8; 5];
file.read(&mut binary).unwrap();
// 数组转为Vec
let text = String::from_utf8(Vec::from(binary)).unwrap();
// 逐字读
let mut file = File::open("tmp").unwrap();
let mut buffer = [0_u8];
let mut binary = Vec::<u8>::new();
loop {
let count = file.read(&mut buffer).unwrap();
if count == 0 {
break;
}
binary.push(buffer[0]);
}
let text = String::from_utf8(binary).unwrap();
println!("{}", text);
// 创建文件 need use std::io::Write
let mut file = File::create("ttmp").unwrap();
file.write(b"this is ttmp").unwrap();
// 追加模式
let mut file = OpenOptions::new()
.append(true)
.open("ttmp")
.unwrap();
file.write(b"[suffix]");
}
最后一个追加模式的输入方法其实就是自定义模式的一个 append 模式,自定义模式下能使用 write, read, append 模式
除此之外还有
fn truncate(&mut self, bool) -> &mut Self // 清除现有文件内容并打开
fn create(&mut self, bool) -> &mut Self // 如果存在就打开,没有就创建
fn create_new(&mut self, bool) -> &mut Self // 如果存在就可恢复错误,必须搭配 write 或者 append 使用
除了写文本,还能写二进制文件
use std::fs::File;
use std::io::{Write, Read};
// 写二进制文件
fn write() {
const PI: f64 = 3.1415926535;
let mut file = File::create("PI.bin").unwrap();
file.write(&PI.to_ne_bytes()).unwrap();
}
// 读二进制文件
fn read() {
let mut file = File::open("PI.bin").unwrap();
let mut buffer = [0_u8; 8];
file.read(&mut buffer).unwrap();
let data = f64::from_ne_bytes(buffer);
println!("{}", data);
}
fn main() {
write();
read();
}
另外是创建目录
use std::fs;
fn main() {
// 读取目录
let dir = fs::read_dir("/").unwrap();
for item in dir {
let entry = item.unwrap();
println!("{}", entry.file_name().to_str().unwrap());
}
// 创建目录
fs::create_dir("./data").unwrap();
// 递归创建目录
fs::create_dir_all("./data/1/2").unwrap();
// 删除文件
fs::File::create("./hellooo").unwrap();
fs::remove_file("./hellooo").unwrap();
// 删除空目录
fs::remove_dir("./data").unwrap();
// 删除非空目录
fs::remove_dir_all("./data").unwrap();
}
2023/04/17