文章

Rust基础——常用集合

开始

本章节介绍rust中的三个常用集合

  • vector
  • String
  • HashMap

三种集合在内存分配上遵循相同的模式,即在栈上保存一个固定大小的结构体,其中包含一个指向存储元素的堆内存的指针,直接赋值时发生所有权移动

vector

Vector表示一个长度可变的数组,是一个类型为Vec<T>的结构体

创建vector

使用new()关联函数创建一个空的vector

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

使用初始值创建vector时,使用vec!宏,支持类型推断

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

访问vector

有两种方式可以访问vector元素

  • 索引访问:索引越界时会抛出panic

    1
    2
    3
    4
    5
    6
    7
    
    let v = vec![1, 2, 3];
    let a: i32 = v[0];  // 发生数据移动或拷贝
    let b: &i32 = &v[0];  // 获取元素的不可变引用,遵循借用期规则
    let s: &[i32] = &v[0..2];  // slice引用
      
    let mut v1 = vec![1, 2, 3];
    let c: &mut i32 = &mut v1[0];  // 获取元素的可变引用,遵循借用期规则
    

    注意,访问时应该使用引用,直接访问会发生数据的移动或拷贝

  • get方法:返回Option<T>,数组越界时返回None

    1
    2
    3
    4
    5
    6
    
    let v = vec![1, 2, 3];
    let a = v.get(0);
    match a {
        Some(n) => {}
        None => {}
    }
    

更新vector

使用更新vector的方法时,会隐式传入vector的可变引用,因此需要声明变量为可变的

  • 添加元素

    1
    2
    
    let mut v = vec![1, 2, 3];
    v.push(1);  // 添加元素
    
  • 修改元素

    1
    2
    3
    4
    5
    6
    
    let mut v = vec![1, 2, 3];
    v[0] = 1;
      
    // 通过引用修改
    let a: &mut i32 = &mut v[0];  // 获取可变引用
    *a = 2;  // 解引用后获取引用的值,可访问或修改
    
  • 删除元素

    1
    2
    
    let mut v = vec![1, 2, 3];
    let removed = v.remove(0); // 移除第一个元素并返回它
    

遍历vector

不可变引用遍历

1
2
3
4
let v = vec![100, 32, 57];
for i in &v {
    println!("{i}");
}

可变引用遍历

1
2
3
4
let mut v = vec![100, 32, 57];
for i in &mut v {
    *i += 50;
}

String

创建字符串

1
2
let empty = String::new();  // 创建一个空字符串
let from = String::from("hello");  // 使用字面量创建字符串对象

添加字符串

1
2
3
4
5
6
7
8
9
let s = String::new();
let s1 = String::from("world");

// push_str接收一个&str类型参数
s.push_str("hello");  // 添加字符串字面量
s.push_str(&s1);  // 附加其他字符串,不移动所有权

// push接受一个char类型参数
s.push('a');

拼接字符串

1
2
3
4
5
6
7
8
9
10
// 使用+运算符拼接两个字符串
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // 注意s1被移动了,不能继续使用

// 使用format!宏拼接字符串
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{s1}-{s2}-{s3}");  // 传入引用,不移动所有权

注意,使用+运算符实际是调用了add方法,它的函数签名为fn add(self, s: &str) -> String,左操作数传入值,会发生所有权移动,右操作数传入&str引用,可以接收String引用和字符串字面量

遍历字符串

由于rust内对于存储的字符串字节支持多种方式来解释(字节、Unicode标量值等),因此字符串不支持索引访问

遍历字符串需要使用字符数组或字节数组的迭代器访问

1
2
3
4
5
6
7
8
9
// 字符数组迭代器
for c in "hello".chars() {
    println!("{c}");
}

// 字节数组迭代器
for c in "hello".chars() {
    println!("{c}");
}

HashMap

使用HashMap需要从模块中引入

1
use std::collections::HashMap;

创建HashMap

1
2
use std::collections::HashMap;
let map: HashMap<String, i32> = HashMap::new();  // 创建空的HashMap

插入键值对

1
2
let mut map = HashMap::new();  // 可根据插入数据推断类型
map.insert(10, String::from("hello"));

注意,若在插入时传入变量,则会发生数据的移动或拷贝

1
2
3
4
let mut map = HashMap::new();
let key = 10;
let value = String::from("hello");
map.insert(key, value);  // key发生拷贝,value发生移动

访问HashMap

1
2
3
4
let mut map = HashMap::new();
map.insert(String::from("hello"), 20);
let key = String::from("hello");
let value: Option<&i32> = map.get(&key);  // 传入引用,返回Option<&V>

遍历HashMap

1
2
3
4
5
6
7
8
9
// 通过不可变引用遍历
for (key, value) in &map {
    println!("{key}: {value}");
}

// 通过可变引用遍历,value为可变引用
for (key, value) in &mut map {
    println!("{key}: {value}");
}

更新HashMap

  • 覆盖旧值

    当多次对同一个键调用insert方法时,新值会覆盖旧值

  • 缺失值插入

    使用entry方法获取键值对,返回Entry<K, V>枚举,其中包含OccupiedVacant两种状态

    Entry中包含许多原地操作方法,如and_modifyor_insert

    1
    2
    3
    4
    
    let mut map = HashMap::new();
    map.insert(String::from("a"), 1);
    // 获取键值对后,若为Occupied,返回value可变引用,若为Vacant,则插入指定值,返回value可变引用
    let value: &mut i32 = map.entry(String::from("b")).or_insert(1);
    
本文由作者按照 CC BY 4.0 进行授权