数据结构
之前简单看了看,发现和 c++ 的 stl 很像…
那么其实就很好理解了
不过 stl 里的 map 是用红黑树写的,rust 中是用 BTree写的
vector
和 stl 的 vector 功能基本一致,能模拟栈
- 创建vector
let v = Vec::<i32>::new();
let v: Vec<i32> = Vec::new();
let v = vec![2, 3, 4];
更喜欢第三种,但是第三种有时候没法确定数据类型,所以第一种也不错,少打字万岁
- 在尾部添加元素
let mut v = Vec::<i32>::new();
v.push(2);
必须要可变才能添加元素
- 移除尾部元素
let x = v.pop();
if let Option::Some(i) = x {
println!("{}", i);
}
如果尾部元素不为空,返回 Option::Some
,否则返回 Option::None
- 删除指定下标
v.remove(index);
会返回那个元素
- 拼接两个 vector
let mut v1 = vec![1, 2, 3];
let mut v2 = vec![10, 20, 30];
v1.append(&mut v2);
for i in v1 {
println!("{}", i);
}
// v2 为空
需要两个都为可变,另一个会被清空
因为随即访问有时候会访问到错误下标出问题,所以下面就不用这种「不安全」的方法了
- 安全地取值
let value = v1.get(6);
if let Option::Some(i) = value {
println!("{}", i);
}
else {
println!("not exists");
}
所谓的安全,就是能从容的应对错误的情况罢
- 取首尾
v1.first();
v1.last();
会返回一个 Option
- 安全地赋值
if let Option::Some(tmp) = v1.get_mut(2) {
*tmp += 20;
}
for i in &mut v1 {
*i += 50;
}
所有基本操作的前提都是存在该元素,这就很安全
刚刚开始觉得这就是浪费时间,后来想想一个安全的程序,报错是要避免的,与其报错,不如多写点代码
VecDeque
双端向量,和普通 vector 的方法基本一致独特之处是能模拟队列
vector 不需要use,但是 vecDeque 就需要 use
use std::collections::VecDeque;
- 创建
let mut queue = VecDeque::<i32>::new();
- 头尾插、头尾出
queue.push_back(1);
queue.push_back(2);
queue.push_front(3);
// [3, 1, 2]
queue.pop_back();
for i in &queue {
println!("{}", i);
}
// [3, 1]
queue.pop_front();
for i in &queue {
println!("{}", i);
}
// [1]
这个 for 居然也会触发 queue 的所有权机制,所以要记得用引用
LinkedList
rust 中的链表的方法还都不稳定,所以没必要的时候不要使用,略
Map
和 stl 中的 map、python 中的字典差不多,有两种,一种是散列,另一种是树形结构的
map 需要用 use
use std::collections::HashMap;
fn main() {
let mut map = HashMap::<i32, i32>::new();
map.insert(2, 233);
map.insert(45, 54);
for i in &map {
println!("{}:{}", i.0, i.1);
}
let tmp = map.get(&45);
if let Option::Some(i) = tmp {
println!("value is {}", i);
}
if let Option::Some(i) = map.get(&233) {
println!("value is {}", i);
}
else {
println!("not exists");
}
}
在 for 循环中迭代值相当于一个元组,第一个是键的值,第二个是值的值,stl 中就是 first 和 second
上面是散列 map,但是其实散列 map 和树状 map 的使用没有什么区别
use std::collections::BTreeMap;
fn main() {
let mut map = BTreeMap::<i32, i32>::new();
...
}
他们之间还有一个区别就是键为自定义类型时需要实现的特性
HashMap 的键需要实现的特性:
- Hash
- Eq:
- PartialEq
BTreeMap 的键需要实现的特性:
- Eq:
- PartialEq
- Ord:
- PartialOrd
Eq特性是空的,但是需要先实现 PartialEq
Set
和 map 一样,同样分为两种,一种哈希散列,一种树状结构
use std::collections::HashSet;
fn main() {
let mut set = HashSet::<i32>::new();
set.insert(233);
set.insert(3);
if let None = set.get(&5) {
println!("empty")
}
for i in &set {
println!("{}", i);
}
set.remove(&233);
}
树状:
use std::collections::BTreeSet;
fn main() {
let mut set = BTreeSet::<i32>::new();
...
}
自定义需要实现的特性还是对应一样的
理论上是散列更快,但是 stl 里可以使用 upper_bound
这种,BTree 还是有存在的必要的
堆
其实就是 stl 里的优先队列,二叉树实现,有大根堆和小根堆的区别
默认是大根堆
use std::collections::BinaryHeap;
fn main() {
let mut pq = BinaryHeap::<i32>::new();
pq.push(5);
pq.push(6);
pq.push(8);
pq.push(1);
pq.push(2);
pq.push(3);
pq.push(4);
loop {
let top = pq.pop();
if let Some(i) = top {
println!("pop: {}", i);
} else {
break;
}
}
println!("done.")
}
如果需要小根堆需要在创建二叉堆的时候使用 std::cmp::Reverse
指定数据类型,实现 Ord 特性的类型比较大小与原有结果相反,从而构造出小根堆
use std::collections::BinaryHeap;
use std::cmp::Reverse;
fn main() {
let mut pq = BinaryHeap::<Reverse<i32>>::new();
pq.push(Reverse(5));
pq.push(Reverse(8));
pq.push(Reverse(1));
pq.push(Reverse(2));
pq.push(Reverse(4));
loop {
let top = pq.pop();
if let Some(i) = top {
println!("pop: {}", i.0);
} else {
break;
}
}
println!("done.")
}
能从 vector 导入进一个优先队列
use std::collections::BinaryHeap;
fn main() {
let vec = vec![1, 3, 2, 5, 4];
let mut heap = BinaryHeap::from(vec);
loop {
let top = heap.pop();
if let Some(i) = top {
println!("pop: {}", i);
} else {
break;
}
}
println!("done.")
}
从数组导入也是可以的
字符串
首先,rust 中的字符串相关的数据类型有:
- String,字符串
- &str,字符串切片
- char,字符
和一般语言的主要区别在于字符串和切片上
fn main() {
let one = 1.to_string();
let float = 2.33.to_string();
let slice = "hello".to_string();
}
上面三个变量的数据类型都是 String,单独的一个 “hello” 并不是 String 类型的,而是 &str
类型,熟悉的 to_string
方法,如果要想给一个类型加上 to_string
方法,那么就需要实现 ToString 特性,要求实现一个 to_string(&self) -> String {}
字符串还可以完成拼接
如果需要拼接 &str 或者 char 类型,可以调用方法
fn main() {
let mut one = 1.to_string();
one.push_str("hello");
one.push('2');
println!("{}", one);
}
对于 String 类型,直接用 +
号即可完成拼接
let xx = one + &float + &slice;
println!("{}", xx);
但是完成拼接后,第一个字符串的所有权会转移到这个字符串上
利用宏 format!
可以用于拼接字符串,类似 Sprintf 那种
fn main() {
let (year, month, day, hour, minute) = (2023, 4, 21, 18, 50);
let string = format!("{}-{}-{} {:02}:{:02}",year, month, day, hour, minute);
println!("{}", string);
}
另一个问题的话就是字符串的截取
fn main() {
let string = "STRING".to_string();
let m = &string[0..4];
println!("{}", m);
}
截出来的 m 是字符串切片类型的 [0..4]
代表从第0个字符到第3个字符
除了字符串切片,还有对字符的截取
fn main() {
let string = "STRING".to_string();
let m = string.chars().nth(2).unwrap();
println!("{}", m);
}
最后提一嘴编码问题,rust 之允许使用 utf-8,中文也是支持的,但是要注意中文单字符有3字节那么大,长度也会因此改变,所以截取如果只截取一个汉字一半的话会报错的,这个要注意,不过用 chars 转成字符集的话就字的位置和序号就对应起来了
老是因为没有打 mut 被编译器教做人 (´_>`)
面向对象
首先需要说明,rust 并不是一门面向对象的语言,需要解构面向对象这个概念
面向对象包括:
- 类
- 对象
- 封装
- 继承
- 多态
rust 没有学 cpp 在有 struct 的基础上再引入了 class 这个 OOP 的东西,这两个东西比较起来就是只有访问权的区别了,只留了 struct,很纯净
如果你要用 OOP 的思想,在 rust 中也是可以用的,rust 中的结构体就是类和对象那种,没什么好说的;然后封装,默认情况下都是对外私有,只有 pub 关键字对外公开;继承是 rust 没有的,如果需要的话可以在 struct 里放一个父类,感觉没必要的说(´・ω・`);最后的多态就是可以用特性
2023/04/21困死我了,就剩八章了,明天要爆肝三章(ง •̀_•́)ง