Rust容器常用操作
读取文件内容到字符串
let content = fs::read_to_string(input_file)?;以 UTF-8 格式读取
let bytes = fs::read(input_file)?;
let content = String::from_utf8_lossy(&bytes);它 Rust 里宏大多是小写开头,然后类是大写开头,所以
let a = vec![1, 3]; //vec 是小写
println!("{}", count);Vec
构建
let mut vec0 = Vec::new();
let mut vec1 = vec![1, 2, 3]; //建议是这个宏
let vec2 = Vec::from([1, 2, 3, 4]);求最大
nums.iter().max()
nums.into_iter().max()求和
nums.iter().sum()
nums.into_iter().sum()排序
nums.sort()访问元素
let a = nums[8];
let a = nums.get(8); // a:Option<&i32>前者没有越界检查,越界就死。后者有检查,返回 Option。
备注:各个标准容器都有 get 方法
遍历
正常遍历
for x in &vec {}
for (index, x) in nums.iter().enumerate()
for index in 0..vec.len() {}反向遍历
for &item in vec.iter().rev() // 推荐
for item in vec.into_iter().rev()
for (i, &value) in vec.iter().enumerate().rev()差异:
let v = vec![1, 2, 3];
for &x in v.iter(){}
for x in v.iter(){}
for x in v{}
for &x in &v{}
for x in v.into_iter(){}| 写法 | 元素类型 | 所有权 | v 是否可用 | 等价写法 |
|---|---|---|---|---|
for &x in v.iter() | i32 | 借用 | ✅ | - |
for x in v.iter() | &i32 | 借用 | ✅ | - |
for x in v | i32 | 移动 | ❌ | for x in v.into_iter() |
for &x in &v | i32 | 借用 | ✅ | for x in v.iter().copied() |
for x in &v | &i32 | 借用 | ✅ | for x in v.iter() |
关键点:for 循环中的 x 是一个模式(pattern),而不仅仅是变量名
最常写的是第三种 for x in v ,但是这会消耗掉 v
第一种:
&x是一个模式,它匹配一个引用并将其解构当迭代器产生
&i32时,模式&x可以匹配它匹配后,
x绑定到i32值对于实现了
Copytrait 的类型(如i32),会 Copy 到 x 上,如果没有实现,不让这样写
第二种:
x是一个简单的标识符模式- 它会匹配任何值
如果想用 for &x 这种写法,结构体必须实现 Copy trait。
链式调用
在 Rust 中实现链式调用的关键在于每个方法都返回self(或同类型)以支持连续调用。
let a = vec![1,2,3];
let mut b = Vec::new();
for x in &a {
if x > 2 {
b.push(x * 2);
}
}
// Rust 链式风格
let b: Vec<_> = a.iter()
.filter(|&&x| x > 2)
.map(|&x| x * 2)
.collect();1、第一步,**into_iter **取出迭代器,而不是用 “增强 for 循环” 取出元素
2、如果你需要过滤,则考虑 filter,它接受一个 lambda 表达式
3、利用** map **函数进行操作,它也接受一个 lambda 表达式
4、用 take、skip 做跳过
5、利用** collect **将迭代器转会容器
vec![1, 2, 3, 4, 5]
.into_iter() // 转为迭代器
.filter(|x| x % 2 == 0) // 过滤偶数
.map(|x| x * 2) // 映射
.take(3) // 取前 3 个
.skip(1) // 跳过第 1 个
.chain(vec![10, 11]) // 连接其他迭代器
.enumerate() // 添加索引
.collect::<Vec<_>>(); // 收集结果Map
所有权:
HashMap 持有值的所有权,所以值会被转移进去
因此,在函数设计上,它的访问函数,比如 get,它是需求引用,而 insert 是需求值
构建
构建一个容器,你需要的是把泛型写在前面,而后面只需要写类名
let mut a: HashMap<i32, Vec<i32>> = HashMap::new();
let mut b: Vec<i32> = Vec::new();遍历
类似于 C++ 用 it 来遍历键值对(取 it,然后访问 it->second)是:
for (k, v) in my_map.iter_mut(){
v.sort();
}rust 更灵活一些,可以仅遍历值或者键
for k in my_map.keys()
for v in my_map.values_mut()
for v in my_map.values()修改
比如当前键值是 a->1 ,修改为 a->2
这样写不对
if let Some(index) = char_map.get(&c){
char_map.insert(c, index2);
max_len = right - *index; // 我的版本里,这里写不行,因为这样属于是在 insert 之后用 index,如果你放前面,我现在是可以的,老版本据说不行
}建议是 Entry 函数
match char_map.entry(c) {
Entry::Occupied(entry) => {
let old_index = *entry.get();
entry.insert(right);
max_len = right - old_index;
}
Entry::Vacant(entry) => {
entry.insert(right);
// 其他逻辑
}
}let a :HashMap<i32, Vec<i32>>
if let Some(vec_x) = a.get_mut(&y){
vec_x.push(x);
}else{
y_map_unorder.insert(y,vec![x]);
}
// 改为
a.entry(x).or_default().push(y);Option
模式匹配取值
1、if let
一般情况下只关心 Some
备注:在这里可以考虑变量遮蔽
let map : HashMap<i32, char>
if let Some(v) = map.get(k){
//
}else{
//
}2、如果需要关心 Some
备注:依然可以考虑变量遮蔽
let v = map.get(k)
match v{
Some(v) => {
}
None => //
}取值
unwrap 系列方法
let x = Some(10).unwrap(); // 如果 None,则 Panic
let x = Some(10).unwrap_or(0)unwrap() 返回的是一个引用
String
遍历
for (i, c) in s.chars().enumerate()读写文件
use std::path::Path;
use std::fs::File;
use std::io::{BufRead, BufReader};1、指定文件路径
let path : &Path = Path::new("./JRoPlatform.h");备注:类型是 &Path 是因为 Path 类似于 str 是动态大小的,所以无法编译前确定,因此只能返回引用
而 Vec 那种,实际上 Vec 就是个智能指针,就直接返回指针本体了
2、打开文件
let file : File = File::open(file_path)
.with_context(|| format!("无法打开文件:{}", file_path.display()))?;3、构造 Buffer 读取
let reader : BufReader<File> = BufReader::new(file);4、按行遍历
for line in reader.lines() {
let line : String = line?;
}直接读取到字符串里:
let s = fs::read_to_string("xxx.h")?;&str
理解
按照 Rust 文档的描述:
str 是一个类型,它的地位和 bool、i32 一样
它常常以借用的形式 &str 出现
&str 就像 C 语言的 const char*,但 Rust 的借用检查器确保它永远不会悬空
你在使用 &str 的时候,不需要管,&str 到底指向了哪里。是静态区,还是临时 String,还是某个结构体,总之,它是有效的。
1、&str 的本质
指针+长度
长度是字节数,而不是字符数
2、str 的编码要求必须是 utf-8
3、&str 是不可变引用,所以不可以修改。也不拥有数据
4、字符的个数 .chars().count()
5、len() 字节为单位。换句话说,它可能与人类所理解的字符串长度有所不同。
使用场景
- 数据需要被修改吗? → 用
String - 需要存储数据,而不仅仅是查看吗? → 用
String - 只是读取已有数据,不获取所有权? → 用
&str - 编写库函数,希望用户友好? → 参数用
&str(&str 可以接受 &String,而反之不行) - 处理字符串字面量或临时切片? → 用
&str
| 场景 | 适合使用 &str | 原因 |
|---|---|---|
| 配置/元数据 | ✅ | 通常是编译时已知的常量 |
| 临时视图 | ✅ | 短时间借用,不需要所有权 |
| 解析器状态 | ✅ | 指向正在解析的文本 |
| 数据库字段 | ❌ | 需要长期存储,用 String |
| 用户输入 | ❌ | 需要拥有数据,用 String |