rust 学习记录 VIII 常见集合

常见集合

使用 vector 存储一列值

vector 允许我们在一个单独的数据结构中储存多个值,所有值在内存中彼此相邻排列。vector 只能储存相同类型的值。

新建 vector

新建一个 vector,需要我们提供一个类型,然后调用 Vec 上的 new 方法

fn main() {
    let v: Vec<i32> = Vec::new();
    println!("{:#?}", v);
}

但是一般我们在创建的时候,rust 会根据我们给的值直接判断类型,所以我们不需要特意去强调泛型里的值,所以也可以像下面这样写:

fn main() {
    let v = vec![1, 2, 3];
    println!("{:#?}", v);
}

更新 vector

可以使用 push 函数增加元素,当 vec 离开作用域是,会自动被回收。

fn main() {
    let mut v = vec![1, 2, 3];
    v.push(4);
    println!("{:?}", v);
}

读取 vector

我们有两种方式来读取一个 vector,get 或者 索引的方式。

索引

fn main() {
    let mut v = vec![1, 2, 3];
    let first = &v[0];
    println!("The first element is: {}", first);
}

get

fn main() {
    let mut v = vec![1, 2, 3];
    match v.get(3) {
        Some(item) => println!("{}", item),
        None => println!("None"),
    }
}

由此可见,get 返回的类型实际上是一个 Option<&T>

一个注意点

fn main() {
    let v = vec![1, 2, 3];
    let fourth = &v[3];
    println!("The fourth element is {}", fourth);
}

上面的代码在编写的时候并不会引起代码恐慌,但是在编译的时候肯定会报错。

但是注意下面的这种情况,看似你是使用了与是否添加新值没有关系的第一个元素,但是如果 vector 检测到你添加了一个元素之后,原来的内存地址需要迁移,那么第一个元素的引用就也需要改变,所以这里会引起恐慌。

fn main() {
    let mut v = vec![1, 2, 3];
    let fourth = &v[0];
    // 这里会引起 rust panic,因为 fourth 使用了不可变引用,而 v.push(4) 会改变 v 的长度
    // 这里主要是一个什么问题呢:可能内存地址会改变
    v.push(4);
    println!("The fourth element is {}", fourth);
}

遍历 vector

fn main() {
    let mut v = vec![1, 2, 3];
    for i in &v {
        println!("{}", i);
    }
}

修改 vector

fn main() {
    let mut v = vec![1, 2, 3];
    for i in &mut v {
        *i = *i + 100;
        println!("{}", i);
    }
}

定义不同类型 vector

如果我们想在一个 vector 中定义不同的类型呢?

这时候我们就可以使用枚举,因为枚举被认为说是同一个类型。


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

使用字符串存储 utf8 编码的文本

什么是字符串

rust 的核心语言中只有一种字符串类型 str

称做 String 的类型是由标准库,提供的,没有写进核心语言部分,是一个可以增长的、可变的、有所有权的、utf8 编码的字符串类型。

新建字符串

fn main() {
    // 新建一个空字符串
    let mut str: String = String::new();
    // 向字符串中添加内容
    str.push_str("Hello, ");
    str.push_str("world!");
    println!("{}", str);
}

也可以在新建的时候直接加入字符串的值:

let hello = String::from("السلام عليكم");
let hello = String::from("Dobrý den");
let hello = String::from("Hello");
let hello = String::from("שָׁלוֹם");
let hello = String::from("नमस्ते");
let hello = String::from("こんにちは");
let hello = String::from("안녕하세요");
let hello = String::from("你好");
let hello = String::from("Olá");
let hello = String::from("Здравствуйте");
let hello = String::from("Hola");

更新字符串

可以使用 push_strpush 在字符串的结尾附加字符串,只不过 push 的参数是一个单独的字符。

索引字符串

rust 是不允许通过索引来获取字符串的!

fn main() {
    let s1 = String::from("hello");
    // 这样写会报错,因为 s1 是 String 类型,而不是 Vec 类型
    // 也就是说,String 类型不支持索引操作
    // let h = s1[0];
}
内部表现

因为存储字符时最终都是存储字符的 utf8 编码需要的字节,而有的语言中的字符每个值需要两个字节来存储,所以并不是字符串的索引对应一个有效的 unicode 标量值。

image.png

所以,请看下面的代码:

let hello = "Здравствуйте";
let answer = &hello[0];

如果可以返回的话,返回的结果就会是 208,因为第一个 Unicode 值为 3,实际上存储的字节需要两个,一个是 208,一个是 151,根据索引就会返回 0 位置上的数据,就是 208。

所以 rust 不允许通过索引获取字符串。

字符串 slice

所以根据以上的描述,如果在创建切片引用的时候,一个 unicode 包含了两个字节,那么也会创建失败。

#![allow(unused)]
fn main() {
    let hello = "Здравствуйте";
    // 创建成功
    let s = &hello[0..4];
    // 创建失败
    // let s = &hello[0..1];
    println!("{}", s);
}

遍历字符串

chars()

如果你需要淡出的 Unicode 标量值,可以选择 chars() 方法。

#![allow(unused)]
fn main() {
    for c in "नमस्ते".chars() {
        println!("{}", c);
    }
}

结果:

न
म
स
这个字符没打出来
त
这个字符没打出来

bytes()

chars() 之间的区别就在于,bytes() 返回的结果是字节,而不是单独的 unicode 字符。

#![allow(unused)]
fn main() {
	for b in "नमस्ते".bytes() {
		println!("{}", b);
	}
}

结果:

224
164
168
224
164
174
224
164
184
224
165
141
224
164
164
224
165
135

返回的结果就是字节。

此外

如果真的想通过索引获取字符串中的某一个值的话,可以这些写:

#![allow(unused)]
fn main() {
    let s = String::from("Здравствуйте");

    if let Some(third_char) = s.chars().nth(2) {
        println!("Third character is: {}", third_char);
    } else {
        println!("String is too short.");
    }
}

返回的就是第三个字符 p

哈希 map 存储键值对

什么是 map

hash map 是非常常用的集合类型。

HashMap<K, V> 类型存储了一个键类型 K,对应一个值类型 V 的映射。底层是通过哈希函数来实现的映射。

新建一个 map

使用 HashMap():

fn main() {
    // 首先必须 use 标准库中的 HashMap,因为在这几个集合类型中,HashMap 是最不常用的,所以需要引入
	// 所以并没有被 preclude 导入
    use std::collections::HashMap;
	// vscode 会提示,第一个参数的类型是字符串,第二个是整数
	// 所有的键的类型必须相同,所有的值的类型也要相同
    let mut scores = HashMap::new();
    scores.insert(String::from("blue"), 10);
    scores.insert(String::from("yellow"), 50);
}

使用 vector 中的 collect,其中的每一个元组都是一个键值对。

#![allow(unused)]
fn main() {
    use std::collections::HashMap;

    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();

    print!("{:?}", scores); // {"Blue": 10, "Yellow": 50}
}

hashmap 和 所有权

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

fn main() {
    use std::collections::HashMap;
    let field_name = "Favorite color";
    let field_value = "Blue";
    let mut map = HashMap::new();
    map.insert(field_name, field_value);
    // 之后 filed_name 和 field_value 不再有效
    // println!("{}: {}", field_name, field_value);
    println!("{:?}", map);
}

访问 map 的值

使用 get() 方法即可访问对应的值。

fn main() {
    use std::collections::HashMap;
    let field_name = "Favorite color";
    let field_value = "Blue";
    let mut map = HashMap::new();
    map.insert(field_name, field_value);
    // 通过引用获取值
    let color = map.get(&field_name);
    println!("{:?}", color);
    // 通过值获取值
    let color = map.get(field_name);
    println!("{:?}", color);
}

遍历 map

fn main() {
    use std::collections::HashMap;
    let field_name = "Favorite color";
    let field_value = "Blue";
    let mut map = HashMap::new();
    map.insert(field_name, field_value);
    // 再插入一个值
    map.insert("Favorite color1", "Red");
    println!("{:?}", map);
    // 为什么不能 map.insert(String::from("123"),123)
    // 因为map的key是String类型,而不是&str类型
    // 这是因为一开始第一个插入的是&str类型,所以map的key类型就是&str类型
    // 如果一开始插入的是String类型,那么map的key类型就是String类型
    for (key, value) in map {
        println!("{}: {}", key, value);
    }
}
  • 46
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

城南顾北

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值