首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >11-Rust 教程 - 集合类型

11-Rust 教程 - 集合类型

作者头像
LarryLan
发布2026-04-13 15:03:55
发布2026-04-13 15:03:55
890
举报

集合类型

Vec、HashMap、String 详解:你的数据终于有地方住了

🎬 引入

还记得我们之前学的所有权吗?那时候你的数据就像流浪汉,不知道往哪儿放。现在好了,Rust 给你提供了三种"精装房":Vec(数组的升级版)、HashMap(键值对存储)、和 String(可变字符串)。

你是不是也遇到过这种情况:想用数组,但不知道要存多少个元素?想用字典,但 Rust 的数组只能用数字索引?别急,今天这三个家伙就是来拯救你的。

我当初学的时候,看到 Vec<String> 这种写法,心想:这是啥套娃操作?后来才明白,这不就是"装着字符串的动态数组"嘛!

📌 核心概念

Vec - 动态数组

想象你有个书架,一开始只能放 5 本书。但你是个书虫,书越来越多怎么办?Vec 就是个智能书架,书多了它会自动变大!

核心特点:

  • 动态大小,自动扩容
  • 元素类型必须相同
  • 连续内存存储(访问快)
  • 所有权管理(元素被移出时)

HashMap - 键值对存储

这就像你的通讯录:名字是键,电话号码是值。想查谁的电话,直接找名字就行,不用从头翻到尾。

核心特点:

  • 键值对存储
  • 键必须唯一
  • 查找速度快(哈希算法)
  • 不保证顺序(别指望它按插入顺序排列)

String - 可变字符串

还记得 &str 吗?那是个"只看不动"的字符串视图。String 才是真正能修改的字符串,就像可擦写的白板。

核心特点:

  • 可增长、可修改
  • UTF-8 编码
  • 堆上分配
  • 所有权明确

💻 代码示例

Vec 基础操作

代码语言:javascript
复制
fn main() {
    // 创建 Vec 的几种方式
    let mut nums = Vec::new();           // 空 Vec
    let nums2: Vec<i32> = vec![];        // 也是空 Vec
    let nums3 = vec![, , , , ];    // 带初始值
    
    // 添加元素
    nums.push();
    nums.push();
    nums.push();
    
    // 访问元素(注意:可能 panic!)
    let first = nums[];                 // 直接索引
    let second = nums.get();            // 安全访问,返回 Option
    
    // 修改元素
    nums[] = ;
    
    // 删除元素
    let last = nums.pop();               // 删除并返回最后一个
    
    // 长度
    println!("长度:{}", nums.len());
    
    // 遍历
    for num in &nums {
        println!("{}", num);
    }
}

HashMap 基础操作

代码语言:javascript
复制
use std::collections::HashMap;

fn main() {
    // 创建 HashMap
    let mut scores = HashMap::new();
    
    // 插入键值对
    scores.insert("Alice", );
    scores.insert("Bob", );
    scores.insert("Charlie", );
    
    // 访问值(可能不存在!)
    let alice_score = scores.get("Alice");      // Some(95)
    let david_score = scores.get("David");      // None
    
    // 更新值
    scores.entry("Alice").and_modify(|e| *e += );
    
    // 如果不存在则插入默认值
    scores.entry("David").or_insert();
    
    // 遍历
    for (name, score) in &scores {
        println!("{}: {}", name, score);
    }
    
    // 删除
    scores.remove("Bob");
}

String 操作

代码语言:javascript
复制
fn main() {
    // 创建 String
    let mut s1 = String::from("Hello");
    let s2 = String::new();
    
    // 添加内容
    s1.push('!');                    // 添加单个字符
    s1.push_str(" World");           // 添加字符串
    
    // 拼接
    let s3 = format!("{} {}", s1, "Rust");  // 推荐!
    
    // 访问(注意:不能直接用索引!)
    let first_char = s1.chars().next();
    
    // 长度
    println!("字节数:{}", s1.len());
    println!("字符数:{}", s1.chars().count());
    
    // 切片(返回 &str)
    let slice = &s1[..];
    
    // 替换
    s1.replace_range(.., "Hi");
    
    // 清空
    s1.clear();
}

🐛 错误示例 - 这些坑你别踩

错误 1:Vec 索引越界

代码语言:javascript
复制
fn main() {
    let nums = vec![, , ];
    let four = nums[];  // ❌ panic! index out of bounds
}

编译器说:

代码语言:javascript
复制
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 3'

正确做法:

代码语言:javascript
复制
let four = nums.get();  // 返回 None,不会 panic

错误 2:HashMap 访问不存在的键

代码语言:javascript
复制
fn main() {
    let mut scores = HashMap::new();
    scores.insert("Alice", );
    let score = scores["Bob"];  // ❌ panic! no entry found
}

正确做法:

代码语言:javascript
复制
let score = scores.get("Bob");  // 返回 Option<&V>

错误 3:String 索引切片(UTF-8 陷阱)

代码语言:javascript
复制
fn main() {
    let s = String::from"你好");
    let c = s[];  // ❌ 编译错误!
}

编译器说:

代码语言:javascript
复制
the trait `Index<{integer}>` is not implemented for `String`

解释: 中文字符在 UTF-8 中占 3 个字节,直接索引可能切到一半!

正确做法:

代码语言:javascript
复制
let chars: Vec<char> = s.chars().collect();
let first = chars[];  // '你'

🐛 常见坑点

1. Vec 的所有权转移

代码语言:javascript
复制
fn main() {
    let v1 = vec![, , ];
    let v2 = v1;  // v1 的所有权转移给 v2
    // println!("{:?}", v1);  // ❌ 编译错误!v1 已失效
}

解决:clone() 或借用

代码语言:javascript
复制
let v2 = v1.clone();  // 深拷贝
// 或者
let v2 = &v1;         // 借用

2. HashMap 的键必须是可哈希的

代码语言:javascript
复制
fn main() {
    let mut map = HashMap::new();
    let key = vec![, , ];
    map.insert(key, "value");  // ❌ 编译错误!Vec 没有实现 Hash
}

解决: 用实现了 Hash 的类型做键(String、&str、数字等)

3. 迭代时修改集合

代码语言:javascript
复制
fn main() {
    let mut nums = vec![, , ];
    for num in &nums {
        nums.push(*num);  // ❌ 编译错误!不能边遍历边修改
    }
}

解决: 先收集要添加的,再统一添加

代码语言:javascript
复制
let to_add: Vec<_> = nums.iter().copied().collect();
for num in to_add {
    nums.push(num);
}

🎯 实战案例

案例 1:统计单词频率

代码语言:javascript
复制
use std::collections::HashMap;

fn count_words(text: &str) -> HashMap<String, usize> {
    let mut counts = HashMap::new();
    
    for word in text.split_whitespace() {
        // 转小写并去除标点
        let word = word.to_lowercase()
                       .trim_matches(|c: char| !c.is_alphabetic())
                       .to_string();
        
        if !word.is_empty() {
            *counts.entry(word).or_insert() += ;
        }
    }
    
    counts
}

fn main() {
    let text = "Rust is great Rust is fast Rust is safe";
    let counts = count_words(text);
    
    for (word, count) in &counts {
        println!("{}: {}", word, count);
    }
}

案例 2:学生成绩管理系统

代码语言:javascript
复制
use std::collections::HashMap;

struct Student {
    name: String,
    scores: Vec<f64>,
}

fn main() {
    let mut students = HashMap::new();
    
    // 添加学生
    students.insert("Alice".to_string(), vec![95.0, 87.0, 92.0]);
    students.insert("Bob".to_string(), vec![88.0, 91.0, 85.0]);
    
    // 计算平均分
    for (name, scores) in &students {
        let avg = scores.iter().sum::<f64>() / scores.len() as f64;
        println!("{} 的平均分:{:.2}", name, avg);
    }
    
    // 添加新成绩
    students.entry("Alice".to_string())
            .and_modify(|scores| scores.push(98.0));
}

案例 3:简单的缓存系统

代码语言:javascript
复制
use std::collections::HashMap;

struct Cache<K, V> {
    data: HashMap<K, V>,
    capacity: usize,
}

impl<K: Eq + std::hash::Hash + Clone, V> Cache<K, V> {
    fn new(capacity: usize) -> Self {
        Cache {
            data: HashMap::new(),
            capacity,
        }
    }
    
    fn get(&self, key: &K) -> Option<&V> {
        self.data.get(key)
    }
    
    fn put(&mut self, key: K, value: V) {
        // 简单实现:满了就清空(实际应该用 LRU)
        if self.data.len() >= self.capacity {
            self.data.clear();
        }
        self.data.insert(key, value);
    }
}

fn main() {
    let mut cache = Cache::new();
    cache.put("a", );
    cache.put("b", );
    cache.put("c", );
    
    println!("{:?}", cache.get(&"a"));  // Some(1)
}

🧠 思维导图

11-集合类型
11-集合类型

📝 小结

  1. Vec 是动态数组:自动扩容,访问快,但要注意所有权转移
  2. HashMap 是键值对:查找快,键必须唯一且实现 Hash
  3. String 是可变字符串:UTF-8 编码,不能直接索引,用 format! 拼接
  4. 安全访问:用 get() 而不是直接索引,避免 panic
  5. 迭代时别修改:边遍历边修改会触发借用检查器

下篇预告: 程序出错了怎么办?难道只能眼睁睁看着它 panic?下篇我们讲讲 Rust 的错误处理机制,让你优雅地处理各种异常情况!

🔗 参考资料

  • Rust Book - Vectors
  • Rust Book - HashMaps
  • Rust Book - Strings
  • std::vec::Vec
  • std::collections::HashMap
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-04-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Larry的Hub 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 集合类型
    • 🎬 引入
    • 📌 核心概念
      • Vec - 动态数组
      • HashMap - 键值对存储
      • String - 可变字符串
    • 💻 代码示例
      • Vec 基础操作
      • HashMap 基础操作
      • String 操作
      • 🐛 错误示例 - 这些坑你别踩
    • 🐛 常见坑点
      • 1. Vec 的所有权转移
      • 2. HashMap 的键必须是可哈希的
      • 3. 迭代时修改集合
    • 🎯 实战案例
      • 案例 1:统计单词频率
      • 案例 2:学生成绩管理系统
      • 案例 3:简单的缓存系统
    • 🧠 思维导图
    • 📝 小结
    • 🔗 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档