数组

一旦开始讨论“票务管理”,我们就需要考虑一种存储多个票据**的方法。这进而意味着我们需要考虑集合,尤其是同质集合:我们希望存储同一类型的多个实例。

在这方面,Rust 提供了哪些工具呢?

数组

初次尝试可以是使用数组
Rust 中的数组是固定大小、元素类型相同的集合。

定义数组的方法如下:

#![allow(unused)]
fn main() {
// 数组类型语法:[ <类型> ; <元素数量> ]
let numbers: [u32; 3] = [1, 2, 3];
}

这创建了一个包含三个整数的数组,并初始化为值 123。数组的类型是 [u32; 3],意为“长度为3的 u32 类型数组”。

访问元素

你可以使用方括号访问数组的元素:

#![allow(unused)]
fn main() {
let first = numbers[0];
let second = numbers[1];
let third = numbers[2];
}

索引必须为 usize类型。数组是从零开始索引的,这在 Rust 中很常见。你之前在字符串切片和元组/类似元组变体的字段索引中见过这一点。

越界访问

如果你试图访问越界的元素,Rust 会引发恐慌:

#![allow(unused)]
fn main() {
let numbers: [u32; 3] = [1, 2, 3];
let fourth = numbers[3]; // 这将引发恐慌
}

这是通过边界检查在运行时强制执行的。它会带来一点性能开销,但也是 Rust 防止缓冲区溢出的方式。
在某些场景下,Rust 编译器可以优化掉边界检查,特别是涉及到迭代器时——我们稍后会详细讨论这一点。

如果你不想触发恐慌,可以使用 get 方法,它返回 Option<&T>

#![allow(unused)]
fn main() {
let numbers: [u32; 3] = [1, 2, 3];
assert_eq!(numbers.get(0), Some(&1));
// 如果尝试访问越界索引,你会得到 `None` 而不是恐慌。
assert_eq!(numbers.get(3), None);
}

性能

由于数组的大小在编译时已知,编译器可以将数组分配在栈上。如果运行以下代码:

#![allow(unused)]
fn main() {
let numbers: [u32; 3] = [1, 2, 3];
}

你将得到以下内存布局:

       +---+---+---+
Stack:  | 1 | 2 | 3 |
       +---+---+---+

换句话说,数组的大小是 std::mem::size_of::<T>() * N,其中 T 是元素的类型,N 是元素的数量。
你可以以 O(1) 时间复杂度访问和替换每个元素。