0、背景
-
1、vector
基本操作
新建
直接使用关联方法new新建,需要手动标注类型,因为没有数据来做类型推断
使用初始值来创建,不需要指定类型,利用 vec! 宏,间接调用了new
let v1: Vec<i32> = Vec::new();
let v2 = vec![1, 2, 3];
更新
push
- 需要标注vec可变mut
下面的实例中Rust可以根据push的数据做出类型推断,不需要手动标注类型
let mut v = Vec::new();
v.push(3);
丢弃vector
vec离开作用域时会被释放,其内部元素也会被丢弃
{
let v = vec![1, 2, 3, 4];
// 处理变量 v
} // <- 这里 v 离开作用域并被丢弃
读取vector元素
有两种方式
索引:let x: &i32 = &v[2]; 返回一个引用
-
为什么要两种方式
让程序员选择如何处理索引值在vector中没有对应值的情况
- 直接使用索引时,访问不存在元素会造成panic:当越界访问是个大问题时,最好让程序崩溃
使用get时,越界访问不会panic而是返回None:偶尔出现越界访问很正常时可以用get
访问vec元素时用到了引用,既然有引用,那么就得检查所有权和借用规则
- 为什么呢?因为要保证对vector中元素的引用要和其他引用保持有效
- 下面的例子为啥就编译不过呢?好离谱。。。
- vec底层实现时,增加新元素时,没足够空间存放的话,可能会申请全新的、更大的内存空间,并将原vec中的内容拷贝过去,同时此时会析构原来的空间,而下面代码中引用的 &v[0] 指向被释放的内存
- 一想到borrow checker连这个都能查出来,头皮发麻
let mut v = vec![1, 2, 3];
let first = &v[0]; //immut borrow
v.push(6); //mut borrow
print!("first element is {}", first); //immut borrow used
遍历vec
for i in &v {} //遍历不可变引用
for i in &mut v { *i+=5;} //遍历可变引用和解引用
使用枚举来在vec中存储多种类型
内心os:啊这,啊这,啊这
场景:电子表格中,一行可能有数字、字符串很多类型,但我想针对单元格统一不同类型,定义一个存不同类型值的枚举,于是把很多实际的类型当成统一的类型来看待 ```rust enum SpreadsheetCell { Int(i32), Float(f64), Text(String), }
let row = vec![ SpreadsheetCell::Int(3), SpreadsheetCell::Text(String::from(“blue”)), SpreadsheetCell::Float(10.12), ];
<a name="xZtJs"></a>
# 2、String
字符串是字符集合,有集合类型的通用操作<br />字符串和其他集合不同,它很复杂
<a name="RdaLp"></a>
## 什么是字符串
- **Rust核心语言中只有一种字符串类型 str**
- 字符串slice,以借用形式&str,是对存在别处的字符串数据的引用
- String是标准库提供的,不在Rust核心语言中
- 是可变的、有所有权的、UTF-8的字符串类型
- String和字符串slice都是UTF-8编码
- 标准库还有其他字符串类型 OsString OsStr CString等
- 后缀String或Str对应他们提供的所有权,String是拥有所有权的。String需要额外的堆空间来存储,str不需要
- 给C API传字符串时不能用Rust字符串,而是CString,是不存储长度,以0结尾的字符序列
<a name="lkxlU"></a>
## 新建字符串
- String::from 和 .to_string做完全相同工作,选啥是风格问题
```rust
let data = "data";
let s = data.to_string();
//同上方法
let s = "data".to_string();
//同上方法
let s = String::from("data");
let mut s = String::new(); //新建空串
附加和拼接字符串
附加的push_str函数采用的是字符串slice,并不需要将所有权trap进去,因此下面的s2还能使用
let mut s1 = String::from("foo");
let s2 = "bar";
s1.push_str(s2);
println!("s2 is {}", s2);
push方法用于单个字符:s.push(‘l’)
拼接字符串
- +运算符使用add函数,于是函数签名中后一个数会使用引用
- 根据签名,我们只能将&str和String相加,不能将两个String相加
- 问题:s2是&String,签名里加的是我&str,和&String有什么关系?
- &String可以被强转成&str
- add调用时,Rust使用解引用强制多态
- 可以理解为 &s2 变成了 &s2[..]
s1+&s2 中
- s1的所有权陷入了add的self中
- s2用引用,于是s2还能继续用
- 整个语句,首先获得s1所有权,再通过引用从s2拷贝,最终返回结果的所有权
使用format!替代多次字符串拼接fn add(self, s: &str) -> String {}
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用
参数的所有权都不会陷入进来
白拿一个结果字符串s,美滋滋
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{}-{}-{}", s1, s2, s3);
索引字符串
Rust字符串不支持索引!!!
为什么不支持索引呢????——UTF-8编码的问题普通字符每一个字母UTF-8编码都是一个字节,Unicode标量值需要两个字节存储,于是索引在这里会有歧义,大家都不希望Unicode只返回一半 ```rust let len = String::from(“Hola”).len(); //看上去4,实际4 let len = String::from(“Здравствуйте”).len(); //看上去12,实际24
let hello = “Здравствуйте”; let answer = &hello[0];//看上去是3,实际上UTF-8编码中对应两个字节,返回的是第一个字节208
**索引字符串总是不好的,因为索引应该返回的类型不明确**
- 字节值
- 字符
- 字形簇
- 字符串slice
**如果真想要索引字符串,你需要表达出诚意,如下**
```rust
let hello = "Здравствуйте";
let s = &hello[0..4]; //s的类型是&str,包含前四个字节,即“Зд”
let s1 = &hello[0..1]; //这种情况会panic
老实人遍历字符串的做法
需要指定遍历的每个对象,是操作单个Unicode,还是操作原始字节
for c in "नमस्ते".chars() { //操作单个Unicode
println!("{}", c);
}
for b in "नमस्ते".bytes() { //操作单个字节
println!("{}", b);
}
3、哈希 map 存储键值对
基本操作
新建
HashMap没有prelude自动引用
下面是常规操作
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
}
使用zip+collect方法从两个vector创建
用zip创建一个元组的vector
- 用collect将元组的vector转成HashMap
注意HashMap<_,_>类型注解是必要的,因为可能collect很多不同的数据结构,需要显式指定我要的数据结构,而key和value的类型可以推断
let teams = vec![String::from("lpc"), String::from("pcl")];
let numbers = vec![10, 46];
let pairs: HashMap<_,_> = teams.iter().zip(numbers.iter()).collect();
map的所有权
i32这种实现了Copy trait的类型可以直接拷贝
String这种拥有所有权的值,map会称为它们的所有权
返回Option
:有结果,返回Some;没结果,返回None - 使用引用key参数,不会把所有权陷入进去
遍历map每一个键值对
let score = scores.get(&team_name);
for (key, value) in &scores {
println!("{}: {}", key, value);
}
更新map
覆盖一个值:直接用相同的key来insert不同的value
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25); //上面的10被覆盖了
key没有对应value时插入:entry函数
- entry函数返回值是一个枚举Entry,代表了可能存在也可能不存在的值
- Entry的or_insert方法,在key存在时返回可变引用;不存在时则将参数作为新值插入,并返回新的可变引用
scores.entry(String::from("Blue")).or_insert(50);
根据旧值更新一个值
- wordcount例子
let text = "hello world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0); //count是可变引用 &mut i32
*count += 1;
}
- wordcount例子