Rust-base-learning-5

·

Rust基础学习–(五)

十一、动态数组 Vector

时隔三个月,rust base learning又被捡起来了嘻嘻()实则舍友开始学rust,被卷到了所以捡起来了…回归正题。

vector实际上在cpp里经常见到了,这么好用的功能居然在这么恶心的语言里有,良心了。

1、创建动态数组

vec::new

1
let v: Vec<i32> = Vec::new();

最经典的创建方式,如果不显式标注数据类型,编译器并不能得知其中的数据类型,我们可以通过输入一共数据的方式【即v.push(1);】

(如果预先知道要存储的元素个数,可以使用 Vec::with_capacity(capacity) 创建动态数组,这样可以避免因为插入大量新数据导致频繁的内存分配和拷贝,提升性能)

vec![]

1
let v = vec![1, 2, 3];

2、更新vector

使用push方法即可,同样需要声明为mut

1
2
let mut v = Vec::new();
v.push(1);

注:作用域和结构体一样,超过{}即会自动删除

3、读取元素

下标索引&get方法

1
2
3
4
5
6
7
8
9
let v = vec![1, 2, 3, 4, 5];

let third: &i32 = &v[2];
println!("第三个元素是 {}", third);

match v.get(2) {
Some(third) => println!("第三个元素是 {third}"),
None => println!("nop!"),
}

二者均可,下标越界即造成报错,而get()方法不会报错,会返回none

显然,get()更安全,对应下标更高效

同时借用多个元素

1
2
3
4
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6);
println!("The first element is: {first}");

因为let first = &v[0];是个不可变借用,而v.push(6);是个可变借用,所以可以编译,但是由于在使用了可变借用之后又调用了first,这是因为数组的大小是可变的,当旧数组的大小不够用时,Rust 会重新分配一块更大的内存空间,然后把旧数组拷贝过来。这种情况下,之前的引用显然会指向一块无效的内存

迭代遍历元素

允许这样迭代元素,同时也可以进行修改元素的操作

1
2
3
4
5
let v = vec![1, 2, 3];
for i in &v {
println!("{i}");
*i += 10
}

4、存储不同类型元素

枚举类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#[derive(Debug)]
enum IpAddr {
V4(String),
V6(String)
}
fn main() {
let v = vec![
IpAddr::V4("127.0.0.1".to_string()),
IpAddr::V6("::1".to_string())
];

for ip in v {
show_addr(ip)
}
}

fn show_addr(ip: IpAddr) {
println!("{:?}",ip);
}

特征对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
trait IpAddr {
fn display(&self);
}

struct V4(String);
impl IpAddr for V4 {
fn display(&self) {
println!("ipv4: {:?}",self.0)
}
}
struct V6(String);
impl IpAddr for V6 {
fn display(&self) {
println!("ipv6: {:?}",self.0)
}
}

fn main() {
let v: Vec<Box<dyn IpAddr>> = vec![
Box::new(V4("127.0.0.1".to_string())),
Box::new(V6("::1".to_string())),
];

for ip in v {
ip.display();
}
}

特征对象数组远多于枚举数组,这是因为特征对象数组可以动态增加类型

常用方法

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let mut v = Vec::with_capacity(10);
v.extend([1, 2, 3]); // 附加数据到 v
println!("Vector 长度是: {}, 容量是: {}", v.len(), v.capacity());

v.reserve(100); // 调整 v 的容量,至少要有 100 的容量
println!("Vector(reserve) 长度是: {}, 容量是: {}", v.len(), v.capacity());

v.shrink_to_fit(); // 释放剩余的容量,一般情况下,不会主动去释放容量
println!("Vector(shrink_to_fit) 长度是: {}, 容量是: {}", v.len(), v.capacity());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let mut v =  vec![1, 2];
assert!(!v.is_empty()); // 检查 v 是否为空

v.insert(2, 3); // 在指定索引插入数据,索引值不能大于 v 的长度, v: [1, 2, 3]
let slice = &v[1..=2];//可以以这种像数组切片的方式获取 vec 的部分元素
assert_eq!(slice, &[2, 3]);
assert_eq!(v.remove(1), 2); // 移除指定位置的元素并返回, v: [1, 3]
assert_eq!(v.pop(), Some(3)); // 删除并返回 v 尾部的元素,v: [1]
assert_eq!(v.pop(), Some(1)); // v: []
assert_eq!(v.pop(), None); // 记得 pop 方法返回的是 Option 枚举值
v.clear(); // 清空 v, v: []

let mut v1 = [11, 22].to_vec(); // append 操作会导致 v1 清空数据,增加可变声明
v.append(&mut v1); // 将 v1 中的所有元素附加到 v 中, v1: []
v.truncate(1); // 截断到指定长度,多余的元素被删除, v: [11]
v.retain(|x| *x > 10); // 保留满足条件的元素,即删除不满足条件的元素

let mut v = vec![11, 22, 33, 44, 55];
// 删除指定范围的元素,同时获取被删除元素的迭代器, v: [11, 55], m: [22, 33, 44]
let mut m: Vec<_> = v.drain(1..=3).collect();

let v2 = m.split_off(1); // 指定索引处切分成两个 vec, m: [22], v2: [33, 44]

排序:

分为稳定的排序 sortsort_by,以及非稳定排序 sort_unstablesort_unstable_by。总体而言,非稳定排序的算法的速度会优于 稳定排序算法,同时,稳定排序还会额外分配原数组一半的空间。

但是,对于浮点数的排序并不一定可以实现,报错可能来自浮点数的nan值,无法与其他浮点数对比,浮点数的ord全序可比较性rust都没做到,只能做到PartialOrd偏序可比较性!

1
2
3
4
5
fn main() {
let mut vec = vec![1.0, 5.6, 10.3, 2.0, 15f32];
vec.sort_unstable();
assert_eq!(vec, vec![1.0, 2.0, 5.6, 10.3, 15f32]);
}//报错!

【我查了一下,其他语言基本都有制定规则,像JavaDouble.compare/Double::compareTo 明确规定了全序

  • NaN 为等于自身,且大于所有其他 double(含 +∞);
  • 规定 +0.0 > -0.0

其实,rust不规定全序反而更加安全合理,可以避免一些极端问题出现,当然,我们可以通过其他方式(见下代码)使其强制可比较

1
2
3
4
5
fn main() {
let mut vec = vec![1.0, 5.6, 10.3, 2.0, 15f32];
vec.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());
assert_eq!(vec, vec![1.0, 2.0, 5.6, 10.3, 15f32]);
}

结构体同样可以使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#[derive(Debug)]
struct Person {
name: String,
age: u32,
}

impl Person {
fn new(name: String, age: u32) -> Person {
Person { name, age }
}
}

fn main() {
let mut people = vec![
Person::new("Zoe".to_string(), 25),
Person::new("Al".to_string(), 60),
Person::new("John".to_string(), 1),
];
// 定义一个按照年龄倒序排序的对比函数
people.sort_unstable_by(|a, b| b.age.cmp(&a.age));

println!("{:?}", people);
}

自然,我们可以drive OrdEqPartialEqPartialOrd 这些属性,使很多原本PartialOrd的数据类型实现ord。

这样我们就可以比较结构体,这时候需要确保你的结构体中所有的属性均实现了 Ord 相关特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#[derive(Debug, Ord, Eq, PartialEq, PartialOrd)]
struct Person {
name: String,
age: u32,
}

impl Person {
fn new(name: String, age: u32) -> Person {
Person { name, age }
}
}

fn main() {
let mut people = vec![
Person::new("Zoe".to_string(), 25),
Person::new("Al".to_string(), 60),
Person::new("Al".to_string(), 30),
Person::new("John".to_string(), 1),
Person::new("John".to_string(), 25),
];

people.sort_unstable();

println!("{:?}", people);
}

derive 的默认实现会依据属性的顺序依次进行比较,如上述例子中,当 Personname 值相同,则会使用 age 进行比较。

十二、KV 存储 HashMap

与vector的区别在于:存储的是一一映射的KV键值对,并且提供了平均复杂度为 O(1) 的查询方法,也就是可以通过一个key去快速查询值。

1、创建

new

同样可以使用new方法创建,通过insert方法插入

1
2
3
4
5
6
7
8
9
use std::collections::HashMap;

// 创建一个HashMap,用于存储宝石种类和对应的数量
let mut my_gems = HashMap::new();

// 将宝石类型和对应的数量写入表中
my_gems.insert("红宝石", 1);
my_gems.insert("蓝宝石", 2);
my_gems.insert("河边捡的误以为是宝石的破石头", 18);

熟悉的感觉,区别就是需要 use std::collections::HashMap;将其引入作用域,这是因为hashmap并没有和string和vector一样被自动引入到作用域。

使用迭代器和collect创建

1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
use std::collections::HashMap;

let teams_list = vec![
("中国队".to_string(), 100),
("美国队".to_string(), 10),
("日本队".to_string(), 50),
];

let teams_map: HashMap<_,_> = teams_list.into_iter().collect();

println!("{:?}",teams_map)
}

从另一个数据结构中获取数据并且存储时,可以通过这种方式,极大简化过程

所有权转移

所有权规则:

  • 若类型实现 Copy 特征,该类型会被复制进 HashMap,因此无所谓所有权
  • 若没实现 Copy 特征,所有权将被转移给 HashMap

另外,值得新人吐槽的是,使用引用类型放入hashmap时,需要保证该引用的生命周期和hashmap一样长,否则报错;同时又不能直接再次使用该引用类型,否则同样报错…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fn main() {
use std::collections::HashMap;

let name = String::from("h2q");
let age = 18;

let mut handsome_boys = HashMap::new();
handsome_boys.insert(name, age);

println!("想什么呢,{}xnn怎么能是handsomeboy呢", name);
println!("还有,他的真实年龄远远不止{}岁", age);
std::mem::drop(name);
println!("自然,{:?}已经被从handsomeboy中除名", handsome_boys);
}

是的,这两个都会报错,悲

查询


Rust-base-learning-5
http://example.com/2025/10/12/Rust-base-learning-5/
作者
oxygen
发布于
2025年10月12日
许可协议