第九章 常用的容器类型
Vector
分配在堆内存上的数据类型,可以动态改变大小.与列表类似吧.只能存放相同类型的数据.
Vec<T>
- 由标准库提供
- 可存储多个值
- 只能存储相同类型的数据
- 值在内存中连续存放
创建Vector
-
Vec::new
fn main() { let v: Vec<i32> = Vec::new(); // 因为我们使用Vec::new()创建的是空的vector,无法进行类型推断,所以需要指定类型.指定类型是为了分配存储空间. } -
使用初始值创建
Vec<T>,使用vec!宏fn main() { let v = vec![1,2,3]; // 这里可以推断出类型 }
更新Vector
使用push向vector中添加元素
fn main() {
let mut v = Vec::new();
v.push(1); // 这里有上下文类型推断
v.push(2);
v.push(3);
}
删除Vector
和struct一样,当Vector离开作用域后,它就会被清理掉,它所有的元素也会被清理掉.
如果Vector里面存储了引用,就需要特殊的处理了
读取Vector中的元素
两种方式引用Vector中的值:(他们返回的是索引还是所有权??)
- 索引
- get方法(找不到就返回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 third element"),
}
}
索引与.get
get会返回Option, 当我们可以确保索引不会越界的时候, 就是用索引访问, 否则使用.get访问.
所有权和借用规则在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
fn main() {
let v = vec![2,3,4];
for i in &v {
println!("{}", i);
}
}
fn main() {
let mut v = vec![2,3,4];
for i in &mut v {
*i += 20; // 遍历修改
}
}
使用enum来存储多种数据类型
Enum的变体可以附加不同类型的数据
Enum的变体定义在同一个enum类型下.在编译时知道类型,就可以分配空间,并且知道数据支持哪些操作.
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
fn main() {
let now = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12), // 三个单元格,每个单元格都是SpreadsheetCell中的一种变体
];
}
使用特征对象
trait IpAddr {
fn display(&self);
}
struct V4(String);
impl IpAddr for V4{
fn display(&self) {
println!("ipv4: {:?}", self.0)
}
}
struct V6(String);
impl IpAddr for V6 {
fn display(&self) {
println!("ipv6: {:?}", self.0)
}
}
fn main() {
let v: Vec<Box<dyn IpAddr>> = vec![ // 存入特征对象
Box::new(V4("127.0.0.1".to_string())),
Box::new(V6("::1".to_string())),
];
for ip in v{
ip.display();
}
}
HashMap
创建hashmap
创建Hashmap: new()函数
添加数据: insert()方法
use std::collections::HashMap;
fn main() {
let mut scores: HashMap<String, i32> = HashMap::new(); // 没有加入值时需要指定类型
}
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 20); // 加入数据可以根据上下文推断出来类型
}
-
由于hashmap用的比较少,不在prelude中,需要手动导入
-
标准库对其支持较少,没有内置的宏来创建hashmap
-
数据存储在heap上
-
hashmap是同构的,这是指一个hashmap中:
- 所有的k必须是同一种类型
- 所有的V必须是用一种类型
使用collect方法创建hashmap
在元素类型为tuple的vector上使用collect方法,可以组建一个hashmap:
-
tuple必须有两个值,一个作为k,一个作为v
-
collect 方法可以把数据整合成多种集合类型,包括hashmap
- 返回值需要显式指明类型
use std::collections::HashMap;
fn main() {
let teams = vec![String::from("Blue"), String::from("yellow")];
let intial_scores = vec![10, 50];
let scores: HashMap<_, _> = teams.iter().zip(intial_scores.iter()).collect(); // 这个例子和python中类似将两个vecter 合成一个tuple,然后使用collect返回一个集合,collect可以返回很多类型的集合,需要在接收的变量上指名类型.
}
fn main () {
use std::collections::HashMap;
let teams_list = vec![
("一队".to_string(), 100),
("二队".to_string(), 10),
("三队".to_string(), 50),
];
let teams_map: HashMap<_,_> = teams_list.into_iter().collect();
println!("{:?}", teams_map)
}
hashmap和所有权
对于实现了copy trait 的类型如i32,值会被复制到hashmap中
对于拥有所有权的值如String,值会被移动,所有全会转移给hashmap
如果将值的引用插入到hashmap,值本身不会移动
use std::collections::HashMap;
fn main() {
let field_name = String::from("favourite color");
let field_value = String::from("bule");
let mut map = HashMap::new();
map.insert(field_name, field_value); // 所有权发生了移动,我们不能再在之后使用他们
println!("{}:{}", field_name, field_value);
map.insert(&field_name, &field_value); // 仅仅传递引用,不会产生所有权移动
println!("{}:{}", field_name, field_value);
}
但是 在hashmap有效的期间,被引用的值必须保持有效.
访问hashmap中的值
get方法
参数k, 返回:Option<&V>
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("yellow"), 10);
scores.insert(String::from("blue"), 20);
let team_name: String = String::from("blue");
let score: Option<&i32> = scores.get(&team_name); // 键是字符串引用, 返回引用
match score {
Some(s: &i32) => println!("{}", s),
None => println!("team not exist"),
}
}
遍历hashmap
使用for循环
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("blue"), 10);
scores.insert(String::from("yellow"), 20);
for (k, v) in &scores {
println!("{}:{}", k,v);
}
}
更新hashmap<k, v>
- hashmap大小可变,每个时刻都可以改变他的大小.
- 但是每个k同时只能对应一个v
更新hashmap中的数据,分为以下几种情况:
-
k已经存在,对应一个V
- 替换现有的v
- 保留现有的V,忽略新的V
- 合并现有的V和新的V
-
k不存在
- 添加一对k, v
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("blue"), 10);
scores.insert(String::from("blue"), 25); // 替换了原有的值
println!("{:?}", scores);
scores.entry(String::from("blue")).or_insert(30); // 使用entry判断是否存在blue这个键.若不存在键则使用or_insert插入
let count = scores.entry(String::from("blue")).or_insert(0);
*count += 5; // 解引用返回值,修改原有的值
println!("{:?}", scores);
}
entry方法: 检查指定的k是否对应一个V
- 参数是k
- 返回enum Entry:代表值是否存在
entry 的or_insert()方法返回:
- 如果k存在,返回到对应的V的一个可变引用.
- 如果k不存在,将方法参数作为k的新值插进去,返回到这个值的可变引用.
HashMap的内存布局
hashmap在最开始时是没有空间分配的,随着哈希表不断插入数据, 它会以2的幂减一的方式增长, 最小是3, 当删除表中的数据时,原有的表大小不变,只有显式地调用 shrink_to_fit,才会让哈希表变小.
创建自己的HashMap
需要实现三个trait, Hash, PartialEq, Eq, 这三个都可以自动生成.
Hash可以让数据结构计算哈希PartialEq/Eq让数据结构进行相等和不相等的比较.Eq实现了比较的自反性(a == a)、对称性(a == b 则 b == a)以及传递性(a == b,b == c,则 a == c),PartialEq没有实现自反性.
use std::{
collections::{hash_map::DefaultHasher, HashMap},
hash::{Hash, Hasher},
};
// 如果要支持 Hash,可以用 #[derive(Hash)],前提是每个字段都实现了 Hash
// 如果要能作为 HashMap 的 key,还需要 PartialEq 和 Eq
#[derive(Debug, Hash, PartialEq, Eq)]
struct Student<'a> {
name: &'a str,
age: u8,
}
impl<'a> Student<'a> {
pub fn new(name: &'a str, age: u8) -> Self {
Self { name, age }
}
}
fn main() {
let mut hasher = DefaultHasher::new();
let student = Student::new("Tyr", 18);
// 实现了 Hash 的数据结构可以直接调用 hash 方法
student.hash(&mut hasher);
let mut map = HashMap::new();
// 实现了 Hash / PartialEq / Eq 的数据结构可以作为 HashMap 的 key
map.insert(student, vec!["Math", "Writing"]);
println!("hash: 0x{:x}, map: {:?}", hasher.finish(), map);
}