Skip to content

Rust - 数组

Rust - 数组(Arrays)、向量(Vectors)和切片(Slices)

Section titled “Rust - 数组(Arrays)、向量(Vectors)和切片(Slices)”

在本章中,我们将学习 Rust 中用于数据序列的主要集合类型。我们将从简单、固定大小的集合类型数组开始,然后介绍更灵活和常用的类型:向量(vectors)和切片(slices)。

一个简单的变量一次只能容纳一个值。要存储值的列表,例如游戏中排名前 10 的分数,我们需要一个可以容纳多个值的数据结构。数组是实现此目的最基本的方式。

Rust 中的数组是相同类型(T)的多个值的集合,具有固定长度(N)。

  • 固定大小(Fixed Size):数组一旦声明,其大小就永远不能改变。这是它与向量等更灵活的类型之间的关键区别。
  • 同质性(Homogeneous):数组中的所有元素都必须具有相同的数据类型。
  • 栈分配(Stack Allocated):数组在栈上分配内存,这使得它们的访问速度非常快。当你在编译时就知道所需元素的精确数量时,这是理想的选择。
  • 连续内存(Sequential Memory):数组的元素在内存中是连续存储的,这允许高效、可预测的访问。
  • 索引访问(Indexed Access):元素通过其索引(从 0 开始)访问。

以下是在 Rust 中声明和初始化数组的常见方式。

// Syntax 1: Type inference
// The compiler infers the type and size.
let my_array = [1, 2, 3, 4, 5];
// Syntax 2: Explicit type and size annotation
// [type; size]
let my_array_typed: [i32; 5] = [1, 2, 3, 4, 5];
// Syntax 3: Initialize all elements to the same value
// [default_value; size]
let all_zeros = [0; 10]; // Creates an array of 10 zeros.

此示例展示了如何创建数组、访问元素以及获取其长度。我们使用调试格式 {:?} 来打印整个数组。

fn main() {
// Declare an array of 4 integers.
let numbers: [i32; 4] = [10, 20, 30, 40];
// Print the entire array.
println!("Array: {:?}", numbers);
// Get the length of the array.
println!("Array size: {}", numbers.len());
// Access a single element by index.
let first_element = numbers[0];
println!("The first element is: {}", first_element);
}
Array: [10, 20, 30, 40]
Array size: 4
The first element is: 10

对数组中的每个元素执行操作是很常见的。符合 Rust 习惯的做法是为此使用迭代器(iterator),它比手动基于索引的循环更安全、更具表达力。

.iter() 方法创建一个迭代器,允许你循环遍历每个元素。要同时获取索引和值,请使用 .enumerate()。

fn main() {
let weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"];
println!("Iterating over values:");
for day in weekdays.iter() {
println!("Day: {}", day);
}
println!("\nIterating with index and value:");
for (index, day) in weekdays.iter().enumerate() {
println!("Day {}: {}", index + 1, day);
}
}
Iterating over values:
Day: Monday
Day: Tuesday
Day: Wednesday
Day: Thursday
Day: Friday
Iterating with index and value:
Day 1: Monday
Day 2: Tuesday
Day 3: Wednesday
Day 4: Thursday
Day 5: Friday

默认情况下,Rust 中的变量是不可变的。要修改数组的元素,必须使用 mut 关键字声明它。

fn main() {
let mut scores: [i32; 5] = [88, 92, 79, 100, 95];
println!("Original scores: {:?}", scores);
// Update the score at index 2
scores[2] = 85;
println!("Updated scores: {:?}", scores);
}
Original scores: [88, 92, 79, 100, 95]
Updated scores: [88, 92, 85, 100, 95]

数组很有用,但其固定大小限制了其用途。对于大多数实际场景,你将使用向量(Vectors)和切片(Slices)。

  • 向量(Vec<T>):一个可增长、可调整大小的列表。它存储在堆上。当你需要一个项目列表时,这是你最常用的类型。
  • 切片(&[T]):是对连续元素序列的“视图”或“引用”。切片允许你安全高效地访问数组或向量的一部分,而无需复制数据。它们对于编写灵活的函数非常强大。

与其编写一个只接受特定大小数组(如 [i32; 3])的函数,不如接受一个切片(&[i32])。这使得你的函数更通用,因为它现在可以处理任何大小的数组,也可以处理向量。

// This function takes a slice of integers, making it very flexible.
fn sum_slice(numbers: &[i32]) -> i32 {
let mut total = 0;
for &num in numbers {
total += num;
}
total
}
fn main() {
let arr1: [i32; 5] = [1, 2, 3, 4, 5];
let arr2: [i32; 3] = [10, 20, 30];
let vec1: Vec<i32> = vec![100, 200, 300];
// We can pass a reference to the whole array, which is coerced into a slice.
println!("Sum of arr1: {}", sum_slice(&arr1));
// Or a slice of a portion of the array.
println!("Sum of part of arr1: {}", sum_slice(&arr1[1..4])); // Elements at index 1, 2, 3
// The same function works for arr2!
println!("Sum of arr2: {}", sum_slice(&arr2));
// And it works for vectors too!
println!("Sum of vec1: {}", sum_slice(&vec1));
}
Sum of arr1: 15
Sum of part of arr1: 9
Sum of arr2: 60
Sum of vec1: 600
  • 越界访问(Out-of-Bounds Access):使用不存在的索引访问数组会导致程序 panic。例如,对大小为 5 的数组访问 numbers[10]。始终检查长度或使用迭代器来避免此问题。
  • 选择正确的类型(Choosing the Right Type):当你确定元素数量不会改变并且需要栈性能时,使用数组 [T; N]。其他所有情况使用向量 Vec<T>。在函数参数中使用切片 &[T] 以使其更灵活。
  • 常数作为大小(Constants for Size):数组的大小必须在编译时已知。如果你需要为大小使用命名值,它必须是一个 const 常量。
fn main() {
const ARRAY_SIZE: usize = 20;
// This is valid because ARRAY_SIZE is a constant.
let my_array = [0; ARRAY_SIZE];
println!("Element 10 is {}", my_array[10]);
}