集合类型:
vector:动态数组,连续存储(相邻排布在内存中)任意多个值,但类型必须相同。
字符串String:字符的集合。
哈希映射HashMap:映射map的特殊实现。
重点:
区别于数组和元组:集合将数据存储在堆上。编译的时候不需要固定大小,因此,集合可以动态存取数据。
不同的集合有着不同的性能与开销。
动态数组:
两种创建方式:
动态数组在实现中使用了泛型,因为动态数组可以存储任意类型的元素,通过泛型来指定元素的类型。
Vec::new(); 创建一个元素类型是默认i32类型的空的动态数组
vec![1,2,3]; 创建一个元素类型是i32的非空的动态数组。
动态数组更新:
添加元素:push方法、pop方法
动态数组一旦离开作用域就会被立即销毁,其中的元素也会被销毁。这样会在处理引用的时候变得复杂。
读取动态数组元素:
两种索引方式:
索引和get方法
let v = vec![1, 2, 3, 4, 5];
let third: &i32 = &v[2];
match v.get(2) {
Some(third) => third,
None => println!("There is no third element."),
}
使用&v[2]返回的是值;get方法返回的是Option<&T>
访问超出动态数组范围,因为索引指向了不存在的元素而导致程序触发panic,会使程序直接崩溃。
get方法会在检测到索引越界时简单地返回None,而不是使程序直接崩溃
动态数组与借用规则:
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6);
println!("The first element is: {}", first);
这段代码会报异常:
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
因为&v[0]是对动态数组的不可变引用
v.push(6)是对动态数组的可变引用
根据借用规则,不可以同时存在可变引用和不可变引用
动态数组遍历:
1. for循环遍历动态数组,不修改元素:
let v = vec![100, 32, 57];
for i in &v { // 这里使用了不可变引用
println!("{}", i);
}
2. for循环遍历动态数组,修改元素:
let mut v = vec![100, 32, 57];
for i in &mut v { // 可变引用
*i += 50; // 使用解引用获取引用的值
}
枚举与动态数组:
动态数组中存储不同类型的元素时,可以定义并使用枚举。
因为动态数组只允许存储同一种类型,枚举的枚举变体都属于同一种类型。但是可以利用媒体变体的存储不同类型的值。
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3), // 枚举变体存储不同类型的值
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
字符串:
字符串种类:
字符串:String类型。String类型被定义在了Rust标准库中而没有被内置在语言的核心部分。
字符串切片:Rust在语言核心部分只有一种字符串类型,那就是字符串切片str,它通常以借用的形式(&str)出现。
字符串字面量:字符串字面量的数据被存储在程序的二进制文件中,而它们本身也是字符串切片的一种。
其他类型:OsString、OsStr、CString及CStr
字符串方法:
String::new(): 创建空的字符串
String::from("xxx"):基于字符串切片创建字符串
to_string(): 基于字符串字面量创建String
let s = "initial contents".to_string();
push_str("xxx"): 参数为字符串切片
push('x'): 参数为字符类型,字符char类型使用单引号
+ : 字符串拼接。函数签名fn add(self,&str) 。let s = s1 + &s2; 根据函数签名,s会夺取s1的所有权。 第二个参数是引用,不会夺取所有权,&s2调用了复制
format!: format!("{}-{}-{}",s1,s2,s3), 不会夺取s1,s2, s3 所有权
字符串编码:UTF-8编码
字符串索引 :
字符串不支持索引。原因:utf-8编码问题。在Rust中,可以通过3种不同的方式来看待字符串中的数据:字节、标量值和字形簇。
字符串切片:
&str[0..2] 使用字符串切片代替索引,但是对字符串切片的范围需要注意。设置的范围有问题,也有可能会触发painic。
遍历字符串的方法:
chars(): 返回char的值
bytes():依次返回每个原始字节值
哈希映射:
HashMap<K,V>:
哈希映射在内部实现中使用了哈希函数。
引入依赖标准库:use std::collections::HashMap; // use将HashMap从标准库的集合部分引入当前作用域
预导入模块(不用使用use再引入):HashMap没有Vector和String使用频率高,因此没有被包含在预导入模块内
同质: 要求HashMap的k和v的类型是相同的(这点从泛型的定义可以看出)
构造HashMap:
HashMap::new(): 创建空的HashMap
insert(): 插入键值
collect():可以将数据收集到很多数据结构
use std::collections::HashMap;
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let scores: HashMap<_, _> = // 这里使用了下划线,rust会自行推导键值类型
teams.iter().zip(initial_scores.iter()).collect(); // collect()方法会根据HashMap<_,_> 将数据收集到HashMap数据结构中
// zip() 函数可以创建元组的数组
get(key): 获取HashMap中的值
entry(key): 返回Entry枚举结果。枚举指明了键所对应的值是否存在。
HashMap与所有权 :
原则:实现了Copy Trait的类型,值会简单复制到HashMap,不会丢失所有权;对于拥有所有权的值,值将会转移所有权给哈希映射。
HashMap与for循环:
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
for (key, value) in &scores { // 这里使用的是索引。解构的是(key,value)
println!("{}: {}", key, value);
}
HashMap的更新:
在任意时刻,每个键都只能对应一个值
覆盖旧值:
insert(): 插入相同的键,后插入的会覆盖
or_insert(): Entry的or_insert方法。返回一个Entry键所指向值的可变引用。如果不存在,则为它插入一个值。
基于旧值来更新值:
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); // or_insert(0) 返回引用
*count += 1; // count需要解引用
}
println!("{:?}", map);