Rust常见集合

迄今为止,我们前面遇到的数据类型基本都是栈上存储的。Rust 标准库中包含一系列被称为 集合collections)的非常有用的数据结构。这些集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就已知,并且还可以随着程序的运行增长或缩小。本篇我们将了解三个在 Rust 程序中被广泛使用的集合:

  • vector 允许我们一个挨着一个地储存一系列数量可变的值
  • 字符串string )是字符的集合。我们之前见过 String 类型,不过在本章我们将深入了解。
  • 哈希 maphash map )允许我们将值与一个特定的键(key)相关联。这是一个叫做 map 的更通用的数据结构的特定实现。

vector

我们要讲到的第一个类型是 Vec<T>,也被称为 vector。vector 允许我们在一个单独的数据结构中储存多于一个的值,它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。

vector的创建

新建一个空的vector:

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

为了方便 Rust 提供了 vec! 宏,这个宏会根据我们提供的字面值来创建一个新的 vector。

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

增加元素

向vector增加元素:

rust 复制代码
fn main() {
    let mut v = Vec::new();

    v.push(5);
    v.push(6);
    v.push(7);
    v.push(8);
}

访问元素

通过索引或使用 get 方法读取vector里的元素:

rust 复制代码
fn main() {
    let v = vec![1, 2, 3, 4, 5];

    let third: &i32 = &v[2];
    println!("The third element is {third}");

    let third: Option<&i32> = v.get(2);
    match third {
        Some(third) => println!("The third element is {third}"),
        None => println!("There is no third element."),
    }
}

两者区别是使用不合法的索引值会让索引方式的代码发生panic,而get方法因为是返回一个Option<&T>,所以只是返回一个None。

遍历元素

使用for in遍历vector:

rust 复制代码
fn main() {
    let v = vec![100, 32, 57];
    for i in &v {
        println!("{i}");
    }
}

或者在遍历可变vector时进行修改:

rust 复制代码
fn main() {
    let mut v = vec![100, 32, 57];
    for i in &mut v {
        *i += 50;
    }
}

使用枚举存储不同类型的数据

由于vector只能存储同类型的数据,对于不同类型的数据,可以将它们跟枚举值进行关联,这样vector就能存储这些枚举值。

元素的生命周期

丢弃vector的时候,也会丢弃其中的元素。

string

很多 Vec 可用的操作在 String 中同样可用,事实上 String 被实现为一个带有一些额外保证、限制和功能的字节 vector 的封装。

创建字符串

新建一个空字符串:

let mut s = String::new();

使用 to_string 方法从任何实现了 Display trait 的类型,比如字符串字面值,创建字符串:

rust 复制代码
fn main() {
    let data = "initial contents";

    let s = data.to_string();

    // 该方法也可直接用于字符串字面值:
    let s = "initial contents".to_string();
}

从字符串字面值创建:

let s = String::from("initial contents");

字符串或者字符的附加

可以通过 push_str 方法来附加字符串 slice,从而使 String 变长:

rust 复制代码
fn main() {
    let mut s = String::from("foo");
    s.push_str("bar");
}

通过push 方法来附加一个单独的字符到string后面:

rust 复制代码
fn main() {
    let mut s = String::from("lo");
    s.push('l');
}

使用+来拼接字符串,需要注意这里使用的是引用:

rust 复制代码
fn main() {
    let s1 = String::from("tic");
    let s2 = String::from("tac");
    let s3 = String::from("toe");

    let s = s1 + "-" + &s2 + "-" + &s3;
}

使用 format! 宏格式化拼接字符串:

rust 复制代码
fn main() {
    let s1 = String::from("tic");
    let s2 = String::from("tac");
    let s3 = String::from("toe");

    let s = format!("{s1}-{s2}-{s3}");
}

不支持索引

Rust 的字符串不支持索引。使用下标索引字符串可能发生错误。

创建字符串slice

索引字符串通常是一个坏点子,因为字符串索引应该返回的类型是不明确的:字节值、字符、字形簇或者字符串 slice。因此,如果你真的希望使用索引创建字符串 slice 时,Rust 会要求你更明确一些。为了更明确索引并表明你需要一个字符串 slice,相比使用 [] 和单个值的索引,可以使用 [] 和一个 range 来创建含特定字节的字符串 slice:

rust 复制代码
#![allow(unused)]
fn main() {
let hello = "Здравствуйте";

let s = &hello[0..4];
}

里,s 会是一个 &str,它包含字符串的头四个字节。早些时候,我们提到了这些字母都是两个字节长的,所以这意味着 s 将会是 "Зд"。而当你访问到了非字符边界,你就会得到一个panic。

遍历字符串

使用 chars 方法遍历字符串的每个char:

rust 复制代码
#![allow(unused)]
fn main() {
for c in "Зд".chars() {
    println!("{c}");
}
}

bytes 方法返回每一个原始字节:

rust 复制代码
#![allow(unused)]
fn main() {
for b in "Зд".bytes() {
    println!("{b}");
}
}

HashMap

HashMap<K, V> 类型储存了一个键类型 K 对应一个值类型 V 的映射。它通过一个 哈希函数hashing function)来实现映射,决定如何将键和值放入内存中。很多编程语言支持这种数据结构,不过通常有不同的名字:哈希、map、对象、哈希表或者关联数组。

创建HashMap

可以使用 new 创建一个空的 HashMap,并使用 insert 增加元素:

rust 复制代码
fn main() {
    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);
}

访问值

可以通过 get 方法并提供对应的键来从哈希 map 中获取值:

rust 复制代码
fn main() {
    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);

    let team_name = String::from("Blue");
    let score = scores.get(&team_name).copied().unwrap_or(0);
}

get 方法返回 Option<&V>,如果某个键在哈希 map 中没有对应的值,get 会返回 None。程序中通过调用 copied 方法来获取一个 Option<i32> 而不是 Option<&i32>,接着调用 unwrap_orscore 中没有该键所对应的项时将其设置为零。

遍历HashMap

可以使用与 vector 类似的方式来遍历哈希 map 中的每一个键值对,也就是 for 循环:

rust 复制代码
fn main() {
    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);

    for (key, value) in &scores {
        println!("{key}: {value}");
    }
}

HashMap和所有权

对于像 i32 这样的实现了 Copy trait 的类型,其值可以拷贝进哈希 map。对于像 String 这样拥有所有权的值,其值将被移动而哈希 map 会成为这些值的所有者:

rust 复制代码
fn main() {
    use std::collections::HashMap;

    let field_name = String::from("Favorite color");
    let field_value = String::from("Blue");

    let mut map = HashMap::new();
    map.insert(field_name, field_value);
    // 这里 field_name 和 field_value 不再有效,
    // 尝试使用它们看看会出现什么编译错误!
}

insert 调用将 field_namefield_value 移动到哈希 map 中后,将不能使用这两个绑定。

如果将值的引用插入哈希 map,这些值本身将不会被移动进哈希 map。但是这些引用指向的值必须至少在哈希 map 有效时也是有效的。

更新值

insert不仅可以插入一个键值对,还可以更新键值对。

map 有一个特有的 API,叫做 entry,它获取键对应的Entry。Entry代表了可能存在也可能不存在的值。Entryor_insert 方法在键对应的值存在时就返回这个值的可变引用,如果不存在则将参数作为新值插入并返回新值的可变引用:

rust 复制代码
fn main() {
    use std::collections::HashMap;

    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);

    scores.entry(String::from("Yellow")).or_insert(50);
    scores.entry(String::from("Blue")).or_insert(50);

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

or_insert 方法返回这个键的值的一个可变引用(&mut V)。这里我们将这个可变引用储存在 count 变量中,所以为了赋值必须首先使用星号(*)解引用 count:

rust 复制代码
fn main() {
    use std::collections::HashMap;

    let text = "hello world wonderful world";

    let mut map = HashMap::new();

    for word in text.split_whitespace() {
        let count = map.entry(word).or_insert(0);
        *count += 1;
    }

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

参考:

常见集合 - Rust 程序设计语言 简体中文版

相关推荐
StayInLove1 分钟前
G1垃圾回收器日志详解
java·开发语言
无尽的大道9 分钟前
Java字符串深度解析:String的实现、常量池与性能优化
java·开发语言·性能优化
爱吃生蚝的于勒12 分钟前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
binishuaio22 分钟前
Java 第11天 (git版本控制器基础用法)
java·开发语言·git
zz.YE24 分钟前
【Java SE】StringBuffer
java·开发语言
就是有点傻28 分钟前
WPF中的依赖属性
开发语言·wpf
洋24036 分钟前
C语言常用标准库函数
c语言·开发语言
进击的六角龙38 分钟前
Python中处理Excel的基本概念(如工作簿、工作表等)
开发语言·python·excel
wrx繁星点点39 分钟前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式
NoneCoder1 小时前
Java企业级开发系列(1)
java·开发语言·spring·团队开发·开发