Rust 学习笔记(六)|Rust 中的常用集合(Vector、String、HashMap)

Rust标准库提供了一些常用的集合,例如Vector、String和HashMap,这些集合的共同特点是数据存储在Heap(堆)内存上,可以在运行时动态地确定他们的大小。

1 Vector

1.1 定义 Vector

Vector由Rust标准库提供,写法为Vec<T>,允许在内存中存放多个相同类型的数据,这些数据在内存中连续存放。使用下面的方法创建一个Vector。

let v1: Vec<i32> = Vec::new();	// 编译器无法自动推断时显式指定Vector的类型

let mut v2 = Vec::new();
v2.push(1);		// 编译器自动推断v2类型

let v3 = vec![1, 2, 3];		// 使用vec!宏

Vector离开作用域时数据就会被清理(Drop)。

1.2 访问 Vector中的元素

使用下面两种方式读取Vector中的元素:

  • 使用索引:访问越界时程序会panic
  • get方法:返回Option枚举,访问越界时程序会返回None
fn main() {
    let v = vec![1, 2, 3, 4, 5];

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

    match v.get(2) {
        Some(third) => println!("The third element is {}", third),
        None => println!("There is no element!"),
    }
}

之前在介绍所有权时了解过,一个变量不可以同时拥有可变的引用和不可变的引用。这一特性对Vector也有效,即使只引用了Vector中的一部分:

fn main() {
    let mut v = vec![1, 2, 3, 4, 5];

    let first = &v[0];          // 不可变借用
    v.push(6);           // 可变借用,非法
    
    println!("The first element is {}", first);
}

Vector在内存中是连续的,如果允许在引用部分元素时在末尾添加元素,那么添加元素时就可能重新分配内存,这样就可能导致原来数据的位置发生改变,所以Rust直接杜绝了这种行为。

1.3 遍历 Vector

通常使用for循环遍历Vector:

fn main() {
    let mut v = vec![100, 50, 150];

    for i in &v {
        println!("{}", i);
    }

    for i in &mut v {
        *i += 50;       // 解引用
        println!("{}", i);
    }
}

1.4 使用 Vector 和 Enum 配合存放多种数据

在之前介绍枚举时,我们了解到可以使用枚举嵌入数据的方式定义枚举,既然枚举是确定的一种类型,那么就可以将其放入Vector中。

enum SpreadsheetCell {
    Int(i32),
    Float(f64),
    Text(String),
}

fn main() {
    let row = vec![
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Text(String::from("blue")),
        SpreadsheetCell::Float(10.12),
    ];
}

注意,这种方法只适用于Vector中“存放”的可能的数据类型是详尽已知的,如果数据类型有无限种可能,那么枚举也无法定义。这时候可以使用Trait对象来解决,这种方法以后再了解。

2 String

在Rust中,字符串是由标准库提供的基于字节组织的集合,并提供了一系列方法使得我们可以解码字符串的内容。Rust核心代码层面字符串指&str,在标准库层面又提供了StringString&str都采用UTF-8编码,其中存放的字符都是Unicode字符,它不仅支持汉字,甚至支持阿拉伯语、梵文等特殊字符的解码。标准库中还提供了其他的字符串类型,例如OsStringOsStrCStringCStr,但是这里不作细致讨论,入门时仅了解String即可。

2.1 定义 String

定义String可以使用to_string()方法或者String::from()函数:

fn main() {
    let s1 = "initial string".to_string();		// 适用所有实现了Display方法的类型
    let s2 = 123.to_string();
    
    let s3 = String::from("hello");
    let mut s4 = String::new();		// 创建一个空 String
}

2.2 更新 String

可以对String进行添加或者拼接操作:

fn main() {
    let mut s1 = "initial string".to_string();

    s1.push_str(" foo");    // 添加一个&str
    s1.push('b');       // 添加一个字符

    println!("{}", s1);
}

特别地,可以使用+对字符串进行拼接操作,+前后的数据类型如下所示,+前的String所有权会发生丢失(所有权移入add函数):

fn main() {
    let s1 = String::from("hello");
    let s2 = String::from(" world");

    let s3 = s1 + &s2;

    println!("{}", s3);
    // println!("{}", s1);     // s1 所有权丢失
    println!("{}", s2);
}

使用format!()宏可以更加方便得拼接字符串,并且这种方法不会获得任何变量的所有权:

fn main() {
    let s1 = String::from("tic");
    let s2 = String::from("tac");
    let s3 = String::from("toe");

    let s = format!("{}-{}-{}", s1, s2, s3);    // 不会获得s1, s2, s3的所有权
    println!("{}", s);
}

2.3 访问 String 的一部分

Rust不支持使用索引访问String类型的部分元素。String本质是对Vec<u8>的包装,使用len()方法可以获得该String占用的字节数。

如果要访问字符串的一部分,可以使用之前学习过的字符串切片&str,但是切片时必须保证切片的位置是字符的边界,如果切片位置不是字符边界,程序就会panic

Rust有三种看待字符的方式:字节、Unicode标量值、字形簇,其中字形簇是最接近我们理解字符的方式。但是获取字形簇相对较为复杂,标准库没有实现遍历它的方法,这里仅作了解即可。

fn main() {
    let s = "你好";

    for b in s.bytes() {	// 遍历字节
        println!("{}", b);
    }

    for b in s.chars() {	// 遍历Unicode标量值
        println!("{}", b);
    }
}

3 HashMap

3.1 定义 HashMap

HashMap是一种通过键值对组织数据的数据类型,类型为HashMap<K, V>,其中KV都是确定的类型。一个HashMap中所有K必须是同一种类型,所有V也必须是同一种类型(同构)。它内部存在一个Hash函数,描述了如何在内存中存储数据。

这种数据结构有点像Python中的字典。HashMap并没有预导入,需要使用use关键字对它进行导入,使用下面的方法定义一个HashMap:

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();

    scores.insert("blue", 50);   // 类型自动推断
}

另一种常用的创建HashMap的方法是使用collect方法,它使用元组Tuple进行创建。collect方法科技把数据整合成多种数据类型,包括HashMap,所以在创建时需要显式指明返回值的类型。

use std::collections::HashMap;

fn main() {
    let teams = vec![String::from("Blue"), String::from("Yellow")];
    let initial_scores = vec![10, 50];
    
    let scores: HashMap<_, _> = 
        teams.iter().zip(initial_scores.iter()).collect();  // 两个迭代器通过zip方法返回一个元组
}

3.2 HashMap 的所有权

对于实现了Copy Trait的数据,值会被复制到HashMap中;对于拥有所有权的类型,值会发生移动(Move),所有权会转移给HashMap,除非使用了引用。使用引用初始化时,数据也只有一个,所以在作用域内必须保证数据一直有效。

use std::collections::HashMap;

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

    let mut map = HashMap::new();
    // map.insert(field_name, field_value);     // 初始化时所有权会发生转移
    map.insert(&field_name, &field_value);

    println!("{}: {}", field_name, field_value);
}

3.3 访问 HashMap

使用get方法访问HashMap中的元素,get方法会返回一个Option枚举,所以就很适合使用match语句。

use std::collections::HashMap;

fn main() {
    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);

    match score {
        None => println!("Team not exist"),
        Some(s) => println!("{}", s),
    }
}

也可以使用for语句遍历HashMap

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();

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

    for (k, v) in &scores {		// 模式匹配
        println!("{}: {}", k, v);
    }
}

3.4 更新 HashMap

在更新HashMap时,会出现以下几种情况,对每一种情况Rust标准库都内置了方案来方便地解决:

  • K不存在:将新的K和V添加进HashMap
  • K存在:
    • 替换现有的V
    • 保留现有的V,忽略新的V
    • 合并现有的和新的V

替换现有的V:如果向HashMap插入一对KV,然后再插入相同的K,但是V不同,这时新的V就会替换掉现有的V。

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();

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

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

只有插入的K不存在才会执行插入:使用entry方法。entry方法会返回一个Entry枚举,通过这个枚举判断K是否存在,配合or_insert方法将K不存在时的新的KV插入HashMap,如果K存在则不执行任何操作:

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();

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

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

    println!("{:?}", scores);   // {"Yellow": 20, "Blue": 10}
}

基于现有V来更新V:or_insert方法总会返回V的可变引用,如果K存在,则返回K对应的V的可变引用;如果K不存在,则返回新插入的值的可变引用。

use std::collections::HashMap;

fn main() {
    let text = "hello world wonderful hello";

    let mut scores = HashMap::new();

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

    println!("{:?}", scores);   // {"hello": 2, "world": 1, "wonderful": 1}
}

3.5 Hash 函数

默认情况下,HashMap使用加密功能强大的Hash函数,可以抵抗拒绝服务(Dos)攻击,它不是最快的Hash算法,但是拥有较好的安全性。

可以指定不同的hasher来切换Hash函数,hasher是实现BuildHasher trait的类型。这里仅作了解即可。

转载声明:

除特殊声明外,本站所有文章均由 debussy 原创,均采用 CC BY-NC-SA 4.0 协议,转载请注明出处:Include Everything 的博客
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇