《Rust权威指南》 第8章 通用集合类型

集合包含多个值,这些集合将自己持有的数据存储在堆上。
本章讨论下面三个集合

  • 动态数组(vector):连续的存储多个值
  • 字符串(String)是字符的集合
  • Hash map 将值关联到一个特定的键上

使用动态数组储存多个值

创建动态数组

Vec<T>可以储存T类型的
调用函数Vec::new来创建一个空动态数组

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

或者使用vec!宏来根据提供的初始值创建动态数组

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

更新动态数组

使用push方法将新的元素添加到动态数组

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

销毁动态数组时也会销毁其中的元素

动态数组一离开作用域就会被销毁,其内部的所有内容也会被销毁

读取动态数组的元素

使用索引

let third:&i32 = &v[2];

注意这里使用的引用
如果读取到非法的位置,会引起一个panic

get方法

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

该方法返回一个Option<T>,如果数组越界,返回None

不成功的引用案例

let mut v = vec![1,2,3];
let firts = &v[0];
v.push(6);

编译器报错的是:在firtt这里声明了不可变引用,在v.push这里使用了可变引用
进一步思考内涵:vector的实现问题。当进行扩容时,会重新分配内存,此时原引用有可能成为悬垂引用

遍历动态数组中的值

使用for循环获得数组中的每个元素的引用
不可变、可变都可以

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

使用枚举储存多个类型的值

一个小技巧,如果想在动态数组中储存不同类型的值,可以将这几个类型先打包成枚举

enum IntFloat {
	Int(i32),
	Float(f64),
}
let v = vec![IntFloat::Int(32),IntFloat::Float(1.2)];

使用字符串存储UTF-8编码的文本

创建字符串

空串:

let mut s = String::new();

根据字符串字面量创建

let s = String::from("hello");

对于实现了Display trait 的类型调用to_string()

let s = "hello".to_string();

更新字符串

使用push_str或push向字符串添加内容

push_str方法将一个字符串切片中的内容添加到字符串后

s.push_str("str");

push方法将单个字符添加到字符串末尾

s.push('哈');

使用+运算符或者format!宏拼接字符串

+运算符会调用一个类似fn add(self,s:&str) -> String的方法。简单来说该方法是取得一个String类型的所有权,将一个&str添加到后面,然后返回该String变量

let s1 = "hello".to_str();
let s2 = " world".to_str();
let s3 = s1 + &s2;//这样之后,s1的所有权转移,s1这个名字不再有效

注意,此处使用一种称为 解引用强制转换 的技术,将&s2强制转换成&s2[..]

对于复杂的字符串合并,使用format!宏

let s1 = "hello".to_str();
let s2 = "world".to_str();
let s3 = format!("{} {}!",s1,s2);

其使用方法和println!宏是一样的
这里也不会取得参数的所有权

字符串索引

字符串不支持使用索引,原因与字符串的内存布局有关

内存布局

字符串实际上是基于Vec<u8>的封装类型,但是并不是一个字符占据一个字节。
对于有ASCII码的字符,占据一个字节;对于其它字符,比如汉字,占据两个字节
所以在使用索引时,很可能只是索引到某个字符的“一半”
更详细的解释可以看这篇知乎文章UTF-8编码

字符串切片

指定所需要的字节内容

let hello = "你好";
let s = &hello[0..2];

这样就是取出来了“你”。另外,如果我们尝试用let s = &hello[0..1]取“汉字的一半”,就会在运行时发生panic

遍历字符串的方法

对字符串中的每一个unicode标量值进行处理,使用chars()方法

for c in "你好".chars() {
	println!("()",c);
}

对字符串内存中的每个原始字节进行处理,使用bytes方法

for c in "你好".bytes() {
	println!("()",c);
}

在HashMap中储存键值对

HashMap<K,V>储存了从K类型键到V类型值之间的映射关系

创建一个新的HashMap

需要使用use将HashMap引入作用域
使用new方法创建一个新的哈希映射
使用insert方法来插入键值对

use std::collections::HashMap;

let mut scores:HashMap<String,i32> = HashMap::new();

scores.insert(String::from("A"),10);
scores.insert(String::from("B"),20);

使用zip方法,配合iter(),collect(),来将两个动态数组合并成一个HashMap。
下面这个例子获得和上面例子一样的HashMap

use std::collections::HashMap;

let team = vec![String::from("A"),String::from("A")];
let sc   = vec![10,20];

let scores:HashMap<_,_> =
	teams.iter().zip(sc.iter()).collect();

这里HashMap<_,_>不能省略

  • 一方面,collect可以用于不同数据结构,必须指明是HAshMap
  • 一方面,编译器可以自动推断出键值的类型,所以只用_占位即可

HashMap与所有权

  • 对于实现了 copy trait 的类型,他们的值会简单的复制到HashMap中
  • 对于持有所有权的值,其值会转移,所有权会转交给HashMap
  • 将值的引用插入哈希映射,这些引用指向的值必须保证在哈希映射有效时也是有效的,这通过生命周期实现

访问哈希映射中的值

将值传入get方法来获得值

  • 返回一个Option<&T>
    • 存在这个键值对,返会Some(T)
    • 不存在,返回None
let team_name = "A".to_str();
let score = scores.get(&team_name);
match score {
	//...
	//...
}

使用for循环遍历所有键值对

for (key,value) in &scores {
	//...
}

遍历的顺序是随机的

更新哈希映射

覆盖旧值

将已有的键并配以不同的值来继续拆入,新的值就会覆盖旧的值

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

检测键值对存在性并插入数据

检查一个键是否存在对应值,不存在,为其插入一个值
使用entry API 实现

  • entry方法返回一个Entry枚举类型来指明键所对应的值是否存在
  • Entry类型的or_insert方法定义为返回一个Entry键所指向值的可变引用
    • 如果这个值不存在,就将参数作为新值插入到HashMap中
scores.entry(String::from("C")).or_insert(10);

依据旧值来更新新值

仍然是使用entry

比如统计单词出现次数

use std::collections::HashMap;
let text = "hello world world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
	let count = map.entry(word).or_insert(0); //若是新单词,初始化为0
	*count += 1; //对于以出现过的单词,计数加一
}

哈希函数

为了提供抵御拒绝服务攻击(DoS, Denial of Service)的能力, HashMap默认使用了一个在密码学上安全的哈希函数

你也可以通过指定不同的哈希计算工具来使用其他函数。这里的哈希计算工具特指实现了BuildHasher trait的类型

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值