Rust从入门到精通之入门篇:8.基本数据结构

基本数据结构

在本章中,我们将学习 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)

访问向量元素

有两种方式访问向量元素:

  1. 使用索引:
rust 复制代码
let v = vec![1, 2, 3, 4, 5];

let third = &v[2]; // 3
  1. 使用 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);
}

练习题

  1. 编写一个函数,接受一个整数数组,返回其中的最大值、最小值和平均值。

  2. 实现一个简单的电话簿程序,使用 HashMap 存储姓名和电话号码,支持添加、查找和删除联系人。

  3. 编写一个函数,接受一个字符串,返回一个 HashMap,其中键是字符,值是该字符在字符串中出现的次数。

  4. 实现一个函数,接受两个向量,返回它们的交集(即两个向量中都出现的元素)。

  5. 创建一个程序,使用 HashSet 找出一个文本中的所有唯一单词,并按字母顺序打印出来。

总结

在本章中,我们学习了:

  • 数组:固定长度的同类型元素集合
  • 向量(Vector):可增长的同类型元素集合
  • 哈希表(HashMap):键值对的集合,通过键快速查找值
  • 其他集合类型:VecDeque、HashSet、BTreeMap 和 BTreeSet
  • 如何选择合适的集合类型

这些数据结构是构建复杂程序的基础,掌握它们的用法对于编写高效的 Rust 代码至关重要。在下一章中,我们将学习 Rust 的错误处理机制,这是编写健壮程序的关键部分。

相关推荐
申朝先生4 分钟前
JS:什么是闭包,以及它的应用场景和缺点是什么?
开发语言·javascript·ecmascript
代码AC不AC31 分钟前
【数据结构】栈 与【LeetCode】20.有效的括号详解
数据结构·学习·leetcode·练习·
Phoebe鑫32 分钟前
数据结构每日一题day5(顺序表)★★★★★
数据结构·算法
Vitalia32 分钟前
并查集(Union-Find)数据结构详解
数据结构·并查集
学习是种信仰啊40 分钟前
QT计算器开发
开发语言·c++·qt
郭涤生1 小时前
Chapter 2:A Tale of Two Values_《clean architecture》notes
开发语言·c++·笔记
<但凡.1 小时前
C++修炼:string类的使用
开发语言·c++·算法
helbyYoung1 小时前
【零基础JavaScript入门 | Day7】三大交互案例深度解析|从DOM操作到组件化开发
开发语言·javascript
小张-森林人2 小时前
C#中,什么是委托,什么是事件及它们之间的关系
开发语言·c#
何似在人间5752 小时前
JAVA实现动态IP黑名单过滤
java·开发语言·tcp/ip·nacos·黑名单过滤