基本数据结构
在本章中,我们将学习 Rust 中的基本数据结构,包括数组、向量(Vector)和哈希表(HashMap)。这些数据结构是构建复杂程序的基础,它们提供了存储和操作数据集合的不同方式。
数组
在前面的章节中,我们已经简单介绍过数组。数组是固定长度的、元素类型相同的数据集合。
创建数组
rust
// 显式指定类型和长度
let a: [i32; 5] = [1, 2, 3, 4, 5];
// 类型和长度由编译器推断
let b = [1, 2, 3, 4, 5];
// 创建包含相同值的数组
let c = [3; 5]; // 等同于 [3, 3, 3, 3, 3]
访问数组元素
使用索引访问数组元素,索引从 0 开始:
rust
let a = [1, 2, 3, 4, 5];
let first = a[0]; // 1
let second = a[1]; // 2
Rust 会在运行时检查数组访问是否越界:
rust
let a = [1, 2, 3, 4, 5];
let index = 10;
// 以下代码会导致运行时错误(panic)
// let element = a[index];
数组切片
可以创建数组的切片,引用数组的一部分:
rust
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3]; // 包含 a[1] 和 a[2]
切片类型写作 &[T]
,其中 T
是元素类型。
向量(Vector)
向量是可增长的、元素类型相同的集合,存储在堆上。与数组不同,向量的大小可以在运行时改变。
创建向量
rust
// 创建空向量
let v: Vec<i32> = Vec::new();
// 使用宏创建并初始化向量
let v = vec![1, 2, 3];
更新向量
向量是可变的,可以添加和删除元素:
rust
let mut v = Vec::new();
// 添加元素
v.push(5);
v.push(6);
v.push(7);
// 删除并返回最后一个元素
let last = v.pop(); // Some(7)
访问向量元素
有两种方式访问向量元素:
- 使用索引:
rust
let v = vec![1, 2, 3, 4, 5];
let third = &v[2]; // 3
- 使用
get
方法,它返回Option<&T>
:
rust
let v = vec![1, 2, 3, 4, 5];
let third = v.get(2); // Some(&3)
let sixth = v.get(5); // None
使用 get
方法可以安全地处理越界访问,而使用索引访问越界会导致程序 panic。
遍历向量
rust
let v = vec![100, 32, 57];
// 遍历不可变引用
for i in &v {
println!("{}", i);
}
// 遍历可变引用并修改元素
let mut v = vec![100, 32, 57];
for i in &mut v {
*i += 50;
}
存储不同类型的元素
向量只能存储相同类型的元素,但可以使用枚举来存储不同类型的值:
rust
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
哈希表(HashMap)
哈希表(HashMap<K, V>
)存储键值对,通过键快速查找值。
创建哈希表
rust
use std::collections::HashMap;
// 创建空哈希表
let mut scores = HashMap::new();
// 插入键值对
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
也可以使用迭代器和 collect
方法从元组向量创建哈希表:
rust
use std::collections::HashMap;
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();
访问哈希表中的值
使用 get
方法获取值,它返回 Option<&V>
:
rust
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
let team_name = String::from("Blue");
let score = scores.get(&team_name); // Some(&10)
遍历哈希表
rust
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
for (key, value) in &scores {
println!("{}: {}", key, value);
}
更新哈希表
覆盖值
rust
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25); // 覆盖之前的值
只在键不存在时插入
rust
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
// 只在 Yellow 不存在时插入
scores.entry(String::from("Yellow")).or_insert(50);
// Yellow 不存在,所以插入 50
// Blue 已存在,不会插入新值
scores.entry(String::from("Blue")).or_insert(50);
根据旧值更新
rust
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);
*count += 1;
}
// 结果: {"hello": 1, "world": 2, "wonderful": 1}
哈希函数
Rust 默认使用 SipHash 哈希算法,它提供了较好的安全性,但不是最快的。如果性能是主要考虑因素,可以指定不同的哈希算法。
其他常用集合
除了上述三种基本数据结构外,Rust 标准库还提供了其他有用的集合类型:
VecDeque
双端队列,可以在两端高效地添加和删除元素:
rust
use std::collections::VecDeque;
let mut deque = VecDeque::new();
deque.push_back(1); // 在尾部添加
deque.push_front(0); // 在头部添加
let first = deque.pop_front(); // 从头部移除
let last = deque.pop_back(); // 从尾部移除
HashSet
存储不重复元素的集合:
rust
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(1);
set.insert(2);
set.insert(1); // 不会添加,因为 1 已存在
println!("{}", set.contains(&1)); // true
BTreeMap 和 BTreeSet
基于 B 树的有序映射和集合:
rust
use std::collections::BTreeMap;
let mut map = BTreeMap::new();
map.insert(3, "c");
map.insert(1, "a");
map.insert(2, "b");
// 按键的顺序遍历: 1, 2, 3
for (key, value) in &map {
println!("{}: {}", key, value);
}
选择合适的集合类型
- Vec:当需要按顺序存储元素并只在末尾添加或删除元素时
- VecDeque:当需要在两端添加或删除元素时
- HashMap:当需要通过任意键快速查找值时
- HashSet:当只关心值是否存在,而不需要关联值时
- BTreeMap:当需要按键排序的映射时
- BTreeSet:当需要有序集合时
示例程序
让我们编写一个程序,展示 Rust 中基本数据结构的用法:
rust
use std::collections::HashMap;
fn main() {
// 数组示例
println!("===== 数组示例 =====");
let months = ["January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December"];
println!("第三个月是: {}", months[2]);
println!("一年有 {} 个月", months.len());
// 数组切片
let spring = &months[2..5];
print!("春季月份: ");
for month in spring {
print!("{} ", month);
}
println!();
// 向量示例
println!("\n===== 向量示例 =====");
let mut fibonacci = vec![1, 1, 2, 3, 5];
println!("初始斐波那契数列: {:?}", fibonacci);
// 添加元素
fibonacci.push(8);
fibonacci.push(13);
println!("添加元素后: {:?}", fibonacci);
// 删除元素
let last = fibonacci.pop();
println!("移除的最后一个元素: {:?}", last);
println!("移除后的向量: {:?}", fibonacci);
// 遍历并修改
for num in &mut fibonacci {
*num *= 2;
}
println!("所有元素乘以 2 后: {:?}", fibonacci);
// 哈希表示例
println!("\n===== 哈希表示例 =====");
let mut student_scores = HashMap::new();
// 插入数据
student_scores.insert("Alice", 92);
student_scores.insert("Bob", 85);
student_scores.insert("Charlie", 90);
println!("学生成绩: {:?}", student_scores);
// 获取值
match student_scores.get("Alice") {
Some(score) => println!("Alice 的成绩是: {}", score),
None => println!("找不到 Alice 的成绩"),
}
// 更新值
*student_scores.entry("Bob").or_insert(0) += 5;
println!("更新后 Bob 的成绩: {}", student_scores["Bob"]);
// 只在键不存在时插入
student_scores.entry("Dave").or_insert(88);
println!("添加 Dave 后的成绩表: {:?}", student_scores);
// 单词计数示例
println!("\n===== 单词计数示例 =====");
let text = "rust is a great language rust has great performance";
let mut word_count = HashMap::new();
for word in text.split_whitespace() {
let count = word_count.entry(word).or_insert(0);
*count += 1;
}
println!("单词计数: {:?}", word_count);
}
练习题
-
编写一个函数,接受一个整数数组,返回其中的最大值、最小值和平均值。
-
实现一个简单的电话簿程序,使用
HashMap
存储姓名和电话号码,支持添加、查找和删除联系人。 -
编写一个函数,接受一个字符串,返回一个
HashMap
,其中键是字符,值是该字符在字符串中出现的次数。 -
实现一个函数,接受两个向量,返回它们的交集(即两个向量中都出现的元素)。
-
创建一个程序,使用
HashSet
找出一个文本中的所有唯一单词,并按字母顺序打印出来。
总结
在本章中,我们学习了:
- 数组:固定长度的同类型元素集合
- 向量(Vector):可增长的同类型元素集合
- 哈希表(HashMap):键值对的集合,通过键快速查找值
- 其他集合类型:VecDeque、HashSet、BTreeMap 和 BTreeSet
- 如何选择合适的集合类型
这些数据结构是构建复杂程序的基础,掌握它们的用法对于编写高效的 Rust 代码至关重要。在下一章中,我们将学习 Rust 的错误处理机制,这是编写健壮程序的关键部分。