数组
一旦开始讨论“票务管理”,我们就需要考虑一种存储多个票据**的方法。这进而意味着我们需要考虑集合,尤其是同质集合:我们希望存储同一类型的多个实例。
在这方面,Rust 提供了哪些工具呢?
数组
初次尝试可以是使用数组。
Rust 中的数组是固定大小、元素类型相同的集合。
定义数组的方法如下:
#![allow(unused)] fn main() { // 数组类型语法:[ <类型> ; <元素数量> ] let numbers: [u32; 3] = [1, 2, 3]; }
这创建了一个包含三个整数的数组,并初始化为值 1
、2
和 3
。数组的类型是 [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)
时间复杂度访问和替换每个元素。