Tailwind CSSTailwind CSS
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • 搜索未来:SEO与GEO双引擎实战手册
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • Rust 开发入门
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • 搜索未来:SEO与GEO双引擎实战手册
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • Rust 开发入门
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain

5.2 向量(Vec)与字符串

在 Rust 中,向量(Vec<T>)和字符串(String 与 &str)是两种最常用、最基础且功能强大的集合类型。与固定大小的数组和元组不同,它们可以在运行时动态地增长和缩小。理解它们的内部机制、所有权关系以及常用操作,对于编写高效、安全的 Rust 代码至关重要。

5.2.1 向量(Vec<T>)

向量是一种可动态增长的数组,它在堆上分配内存来存储相同类型 T 的元素。Vec<T> 是 Rust 标准库提供的核心数据结构之一。

创建向量

有多种方式可以创建向量:

  1. 使用 Vec::new() 方法:创建一个空的向量。

    let mut v: Vec<i32> = Vec::new();
    v.push(1);
    v.push(2);
    v.push(3);
    

    这里我们显式地指定了类型 Vec<i32>,因为 Vec::new() 无法推断元素类型。

  2. 使用 vec! 宏:这是最常用、最便捷的方式,可以同时创建并初始化向量。

    let v = vec![1, 2, 3]; // 类型被自动推断为 Vec<i32>
    let v2 = vec![0; 5]; // 创建一个包含 5 个 0 的向量,等价于 [0, 0, 0, 0, 0]
    

更新向量

  • 添加元素:使用 push 方法在向量末尾添加一个元素。
  • 移除元素:使用 pop 方法移除并返回向量最后一个元素(返回 Option<T>)。也可以使用 remove 方法移除指定索引的元素,但这会导致后续元素向前移动,效率较低。
let mut v = vec![1, 2, 3];
v.push(4); // v 变为 [1, 2, 3, 4]
let last = v.pop(); // last 为 Some(4),v 变为 [1, 2, 3]
let second = v.remove(1); // second 为 2,v 变为 [1, 3]

读取向量元素

访问向量中的元素主要有两种方式:

  1. 使用索引和 [] 运算符:如果索引越界,程序会直接 panic。

    let v = vec![1, 2, 3, 4, 5];
    let third: &i32 = &v[2]; // 获取第三个元素的引用
    println!("The third element is {}", third);
    
  2. 使用 get 方法:返回一个 Option<&T> 类型。如果索引有效,返回 Some(&element);如果索引越界,返回 None,不会导致 panic。

    let v = vec![1, 2, 3, 4, 5];
    match v.get(2) {
        Some(third) => println!("The third element is {}", third),
        None => println!("There is no third element."),
    }
    

    在不确定索引是否有效时,推荐使用 get 方法。

遍历向量

可以使用 for 循环来遍历向量中的元素。

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;
}

向量的所有权

向量拥有其内部的所有元素。当向量被丢弃时,其所有元素也会被丢弃。当你将元素插入向量时,元素的所有权会转移给向量。当你通过索引或 get 获取元素的引用时,你借用了向量,必须遵守借用规则。

5.2.2 字符串(String 与 &str)

Rust 中的字符串处理比许多其他语言要复杂一些,这主要归因于其内存安全和编码方式。Rust 的核心语言层面只有一种字符串类型:字符串切片 &str,它通常以不可变引用的形式存在。而标准库提供的 String 类型是可变的、拥有所有权的、在堆上分配的 UTF-8 编码字符串。

&str (字符串切片)

  • 定义:对存储在别处的 UTF-8 编码字符串数据的引用。它通常以 &str 类型出现。
  • 创建:字符串字面量就是 &str 类型。
    let s = "hello"; // s 的类型是 &str,它指向程序二进制文件中的一段只读内存。
    
  • 特性:不可变,固定大小(指向数据的指针和长度),不拥有数据。

String (拥有所有权的字符串)

  • 定义:一个可变的、在堆上分配的、拥有所有权的 UTF-8 编码字符串。
  • 创建:
    1. String::new():创建一个空字符串。
      let mut s = String::new();
      s.push_str("hello");
      
    2. to_string() 方法:从 &str 创建 String。
      let s = "hello".to_string();
      
    3. String::from():与 to_string() 类似。
      let s = String::from("hello");
      
  • 更新:
    • push_str(&str):追加一个字符串切片。
    • push(char):追加一个字符。
    • + 运算符:连接字符串。注意,+ 运算符会取得左侧 String 的所有权,并借用右侧 &str。
      let s1 = String::from("Hello, ");
      let s2 = String::from("world!");
      let s3 = s1 + &s2; // 注意 s1 被移动了,不能再使用
      
    • format! 宏:更灵活、更清晰的字符串连接方式,不会取得任何参数的所有权。
      let s1 = String::from("tic");
      let s2 = String::from("tac");
      let s3 = String::from("toe");
      let s = format!("{}-{}-{}", s1, s2, s3);
      

字符串的内部表示

String 和 &str 内部存储的都是 UTF-8 编码的字节序列。这意味着:

  • 它们不是字符数组,而是字节数组。
  • 一个英文字符占用 1 个字节,一个中文字符占用 3 个字节。
  • 不能直接通过索引访问字符串中的字符(例如 s[0] 是不允许的),因为索引操作的时间复杂度是 O(1),但 UTF-8 编码的字符是变长的,无法保证 O(1) 的字符访问。

字符串的索引与遍历

由于 UTF-8 编码的特性,Rust 提供了三种不同的方式来看待字符串:

  1. 字节(Bytes):使用 .bytes() 方法。
  2. 标量值(Scalar Values):使用 .chars() 方法,返回 char 类型。
  3. 字形簇(Grapheme Clusters):最接近我们所说的“字母”,但 Rust 标准库不提供此功能,需要第三方 crate(如 unicode-segmentation)。
let s = String::from("नमस्ते");
for b in s.bytes() {
    println!("{}", b); // 输出 18 个字节
}
for c in s.chars() {
    println!("{}", c); // 输出 6 个 char 值
}

5.2.3 向量与字符串的协同

向量和字符串经常一起使用。例如,你可以有一个字符串向量 Vec<String> 或 Vec<&str>。

let mut string_vec: Vec<String> = Vec::new();
string_vec.push(String::from("apple"));
string_vec.push(String::from("banana"));

let str_slice_vec: Vec<&str> = vec!["hello", "world"];

当处理字符串切片和 String 的集合时,需要仔细考虑所有权和借用。例如,一个函数如果接受 &[&str] 参数,可以同时接受 Vec<String> 的引用(通过 & 和 as_slice())和字符串字面量数组。

5.2.4 性能考量

  • 容量(Capacity):Vec 和 String 都有一个 capacity 属性和一个 length 属性。length 是当前元素/字节数,capacity 是已分配内存可以容纳的最大元素/字节数。当 length 接近 capacity 时,再添加新元素会触发重新分配,将数据复制到更大的内存块中。频繁的重新分配会影响性能。
  • 预分配:如果你能大致知道最终需要存储多少元素,可以使用 Vec::with_capacity(n) 或 String::with_capacity(n) 预分配足够的空间,避免不必要的重新分配。
  • shrink_to_fit:如果你需要释放多余的内存,可以使用 shrink_to_fit 方法,但这可能会触发重新分配。

5.2.5 总结

  • 向量(Vec<T>):动态数组,提供高效的随机访问和末尾增删操作。使用 vec! 宏创建,使用 push、pop、get 和索引进行读写。
  • 字符串(String 与 &str):String 是拥有所有权的可变 UTF-8 字符串,&str 是不可变的字符串切片。使用 push_str、push、+ 或 format! 来构建 String。由于 UTF-8 编码,不能直接通过索引访问字符,应使用 .chars() 或 .bytes() 进行遍历。

掌握向量和字符串是 Rust 开发的基础。理解它们的区别、所有权模型以及内部编码方式,将帮助你避免许多常见的陷阱,并编写出更健壮、更高效的代码。

Last Updated:: 5/9/26, 3:13 PM