rust学习笔记 7
start at 2023/04/17.

数据结构

之前简单看了看,发现和 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
> CLICK TO back <