rust高级数据结构
切片
切片(Slice)是对数据值的部分引用。
fn main() {
let arr = [1, 3, 5, 7, 9];
let part = &arr[0..3];
for i in part.iter() {
println!("{}", i);
}
}
使用 .. 表示范围的语法在循环章节中出现过。x..y 表示 [x, y) 的数学含义。.. 两边可以没有运算数:
..y 等价于 0..y
x.. 等价于位置 x 到数据结束.. 等价于位置 0 到结束
切片修改
// 必须mut修饰
let mut vec = [1, 3, 5, 7, 9];
// 声明为可修改的切片
let vec_m = &mut vec[1..4];
vec_m[2] = 10;
dbg!(vec_m);
dbg!(vec);
字符串
在 Rust 中有两种常用的字符串类型:str 和 String。str 是 Rust 核心语言类型,就是本章一直在讲的字符串切片(String Slice),常常以引用的形式出现(&str)。
凡是用双引号包括的字符串常量整体的类型性质都是 &str:
let s = "hello";
这里的 s 就是一个 &str 类型的变量。
String 类型是 Rust 标准公共库提供的一种数据类型,它的功能更完善——它支持字符串的追加、清空等实用的操作。String 和 str 除了同样拥有一个字符开始位置属性和一个字符串长度属性以外还有一个容量(capacity)属性。
String 和 str 都支持切片,切片的结果是 &str 类型的数据。
注意:切片结果必须是引用类型,但开发者必须自己明示这一点:
let slice = &s[0..3];
有一个快速的办法可以将 String 转换成 &str:
let s1 = String::from("hello");let s2 = &s1[..];
结构体
Rust 中的结构体(Struct)与元组(Tuple)都可以将若干个类型不一定相同的数据捆绑在一起形成整体,但结构体的每个成员和其本身都有一个名字,这样访问它成员的时候就不用记住下标了。
元组常用于非定义的多值传递,而结构体用于规范常用的数据结构。结构体的每个成员叫做"字段"。
Rust 里 struct 语句仅用来定义,不能声明实例,结尾不需要 ; 符号,而且每个字段定义之后用 , 分隔。
有这样一种情况:你想要新建一个结构体的实例,其中大部分属性需要被设置成与现存的一个结构体属性一样,仅需更改其中的一两个字段的值,可以使用结构体更新语法:
let domain = String::from("www.runoob.com");
let name = String::from("RUNOOB");
let runoob = Site {
domain, // 等同于 domain : domain,
name, // 等同于 name : name,
nation: String::from("China"),
traffic: 2013
};
注意:..runoob 后面不可以有逗号。这种语法不允许一成不变的复制另一个结构体实例,意思就是说至少重新设定一个字段的值才能引用其他实例的值。
元组结构体
有一种更简单的定义和使用结构体的方式:元组结构体。
元组结构体是一种形式是元组的结构体。
与元组的区别是它有名字和固定的类型格式。它存在的意义是为了处理那些需要定义类型(经常使用)又不想太复杂的简单数据:
struct Color(u8, u8, u8);struct Point(f64, f64);let black = Color(0, 0, 0);let origin = Point(0.0, 0.0);
"颜色"和"点坐标"是常用的两种数据类型,但如果实例化时写个大括号再写上两个名字就为了可读性牺牲了便捷性,Rust 不会遗留这个问题。元组结构体对象的使用方式和元组一样,通过 . 和下标来进行访问:
实例
fn main() {
struct Color(u8, u8, u8);
struct Point(f64, f64);
let black = Color(0, 0, 0);
let origin = Point(0.0, 0.0);
println!("black = ({}, {}, {})", black.0, black.1, black.2);
println!("origin = ({}, {})", origin.0, origin.1);
}
运行结果:
black = (0, 0, 0)origin = (0, 0)
结构体所有权
结构体必须掌握字段值所有权,因为结构体失效的时候会释放所有字段。
这就是为什么本章的案例中使用了 String 类型而不使用 &str 的原因。
但这不意味着结构体中不定义引用型字段,这需要通过"生命周期"机制来实现。
结构体方法
方法(Method)和函数(Function)类似,只不过它是用来操作结构体实例的。
如果你学习过一些面向对象的语言,那你一定很清楚函数一般放在类定义里并在函数中用 this 表示所操作的实例。
Rust 语言不是面向对象的,从它所有权机制的创新可以看出这一点。但是面向对象的珍贵思想可以在 Rust 实现。
结构体方法的第一个参数必须是 &self,不需声明类型,因为 self 不是一种风格而是关键字。
结构体关联函数
之所以"结构体方法"不叫"结构体函数"是因为"函数"这个名字留给了这种函数:它在 impl 块中却没有 &self 参数。
这种函数不依赖实例,但是使用它需要声明是在哪个 impl 块中的。
一直使用的 String::from 函数就是一个"关联函数"。
贴士:结构体 impl 块可以写几次,效果相当于它们内容的拼接!
举例
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32
}
fn test_struct() {
let rect1 = Rectangle { width: 30, height: 50 };
println!("rect1 = {:?}; 周长 = {:?}; 面积 = {:?}", rect1, rect1.zhou_chang(), rect1.mian_zi());
let mut rect2 = Rectangle::creat(20, 30);
println!("rect2 = {:?}; 周长 = {:?}; 面积 = {:?}", rect2, rect2.zhou_chang(), rect2.mian_zi());
rect2 = rect1;
println!("rect2 = {:?}; 周长 = {:?}; 面积 = {:?}", rect2, rect2.zhou_chang(), rect2.mian_zi());
}
impl Rectangle {
fn creat(w: u32, h: u32) -> Rectangle {
Rectangle {width: w, height: h}
}
fn zhou_chang(&self) -> u32 {
(self.width + self.height) * 2
}
fn mian_zi(&self) -> u32 {
self.width * self.height
}
}
运行
rect1 = Rectangle { width: 30, height: 50 }; 周长 = 160; 面积 = 1500
rect2 = Rectangle { width: 20, height: 30 }; 周长 = 100; 面积 = 600
rect2 = Rectangle { width: 30, height: 50 }; 周长 = 160; 面积 = 1500
在调用结构体方法的时候不需要填写 self ,这是出于对使用方便性的考虑。
结构体完整性
引用结构体成员给其他变量赋值时,要注意:所有权的转移可能会破坏结构体变量的完整性。
枚举的目的是对某一类事物的分类,分类的目的是为了对不同的情况进行描述。基于这个原理,往往枚举类最终都会被分支结构处理(许多语言中的 switch )。 switch 语法很经典,但在 Rust 中并不支持,很多语言摒弃 switch 的原因都是因为 switch 容易存在因忘记添加 break 而产生的串接运行问题,Java 和 C# 这类语言通过安全检查杜绝这种情况出现。
Rust 通过 match 语句来实现分支结构。先认识一下如何用 match 处理枚举类:
例如:
fn test_union() {
enum Book {
Papery {index: u32},
Electronic {url: String},
}
let book = Book::Papery{index: 1001};
let ebook = Book::Electronic{url: String::from("https://www.runoob.com/rust/rust-enum.html")};
let arr = [book, ebook];
for i in 0..arr.len() {
match &arr[i] {
Book::Papery { index } => {
println!("Papery book {}", index);
},
Book::Electronic { url } => {
println!("E-book {}", url);
}
}
}
for i in arr.iter() {
match i {
Book::Papery { index } => {
println!("Papery book {}", index);
},
Book::Electronic { url } => {
println!("E-book {}", url);
}
}
}
运行
Papery book 1001
E-book https://www.runoob.com/rust/rust-enum.html
Papery book 1001
E-book https://www.runoob.com/rust/rust-enum.html
match 块也可以当作函数表达式来对待,它也是可以有返回值的:
match 枚举类实例 {
分类1 => 返回值表达式,
分类2 => 返回值表达式,
...
}
if let 语法
let i = 0;
if let 0 = i {
println!("zero");
}
if let 语法格式如下:
if let 匹配值 = 源变量 {
语句块
}