49、静态数组(Arrays)
一旦我们想要做“票务管理”,我们就需要考虑一种存储多张tickets的方法。反过来,这意味着我们需要考虑集合的问题,特别是同质集合:我们希望存储多个相同类型的实例。
在这方面,Rust能提供什么呢?
Arrays
首先可以尝试使用数组,Rust中的数组是相同类型元素的固定大小的集合。
我们可以这样定义一个数组:
// Array type syntax: [ <type> ; <number of elements> ]
// 数组类型语法:[<数据类型>;<元素数量>]
let numbers: [u32; 3] = [1, 2, 3];
这将创建一个包含3个整数的数组,初始化值分别为1
,2
和3
。数组的类型是[u32;3]
,读作:“长度为3的u32
数组”。
如果所有数组元素都相同,那么我们可以简化语法:
// [ <value> ; <number of elements> ]
let numbers: [u32; 3] = [1; 3];
[1;3]
创建一个包含三个元素的数组,所有元素都为1
。
访问元素
我们可以使用方括号访问数组的元素,就像大多数语言那样那样。
let first = numbers[0];
let second = numbers[1];
let third = numbers[2];
索引必须是usize
类型。
数组是zero-indexed
(零索引)的,就像Rust中的所有东西一样。之前我们学习到的tuples/tuples-like(元组/类元组变体)中的字符串切片和字段索引中看到了这一点。
越界访问
如果我们尝试访问一个越界的元素,Rust会panic
let numbers: [u32; 3] = [1, 2, 3];
let fourth = numbers[3]; // This will panic
这是在运行时使用bounds checking
边界检查强制执行的。虽然会带来少量的性能开销,但这就是Rust防止缓冲区溢出测方法。
在某些情况下,Rust编译器可以优化边界检查,尤其是在设计迭代器的情况下——稍后会详细学习这一点。
如果不想panic,我们可以使用get
方法,该方法会返回一个Option<&T>
:
let numbers: [u32; 3] = [1, 2, 3];
assert_eq!(numbers.get(0), Some(&1));
// You get a `None` if you try to access an out-of-bounds index
// rather than a panic.
assert_eq!(numbers.get(3), None);
性能
由于数组的大小在编译时是已知的,因此编译器可以在堆栈上分配数组。如果我们运行代码:
let numbers: [u32; 3] = [1, 2, 3];
我们将会获得以下的内存布局:
+---+---+---+
Stack: | 1 | 2 | 3 |
+---+---+---+
换句话说,数组的大小是std::mem::size_of::<T>()*N
,其中T
是元素的类型,N
是元素的个数。访问和替换元素的时间复杂度是O(1)
。
练习
这是第五章的第一个练习,练习的内容和前面的依然有很大的关系,一共有两个文件,lib文件和测试文件:
tests.rs:
#[cfg(test)]
mod tests {
use task_arrays::*;
#[test]
fn test_get_temperature() {
let mut week_temperatures = WeekTemperatures::new();
assert_eq!(week_temperatures.get_temperature(Weekday::Monday), None);
assert_eq!(week_temperatures.get_temperature(Weekday::Tuesday), None);
assert_eq!(week_temperatures.get_temperature(Weekday::Wednesday), None);
assert_eq!(week_temperatures.get_temperature(Weekday::Thursday), None);
assert_eq!(week_temperatures.get_temperature(Weekday::Friday), None);
assert_eq!(week_temperatures.get_temperature(Weekday::Saturday), None);
assert_eq!(week_temperatures.get_temperature(Weekday::Sunday), None);
week_temperatures.set_temperature(Weekday::Monday, 20);
assert_eq!(week_temperatures.get_temperature(Weekday::Monday), Some(20));
week_temperatures.set_temperature(Weekday::Monday, 25);
assert_eq!(week_temperatures.get_temperature(Weekday::Monday), Some(25));
week_temperatures.set_temperature(Weekday::Tuesday, 30);
week_temperatures.set_temperature(Weekday::Wednesday, 35);
week_temperatures.set_temperature(Weekday::Thursday, 40);
week_temperatures.set_temperature(Weekday::Friday, 45);
week_temperatures.set_temperature(Weekday::Saturday, 50);
week_temperatures.set_temperature(Weekday::Sunday, 55);
assert_eq!(week_temperatures.get_temperature(Weekday::Monday), Some(25));
assert_eq!(
week_temperatures.get_temperature(Weekday::Tuesday),
Some(30)
);
assert_eq!(
week_temperatures.get_temperature(Weekday::Wednesday),
Some(35)
);
assert_eq!(
week_temperatures.get_temperature(Weekday::Thursday),
Some(40)
);
assert_eq!(week_temperatures.get_temperature(Weekday::Friday), Some(45));
assert_eq!(
week_temperatures.get_temperature(Weekday::Saturday),
Some(50)
);
assert_eq!(week_temperatures.get_temperature(Weekday::Sunday), Some(55));
}
}
lib.rs:
// TODO: Flesh out the `WeekTemperatures` struct and its method implementations to pass the tests.
pub struct WeekTemperatures {
/* TODO */,
}
pub enum Weekday {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
}
impl WeekTemperatures {
pub fn new() -> Self {
/* TODO */ }
}
pub fn get_temperature(&self, day: Weekday) -> Option<i32> {
/* TODO */ex]
}
pub fn set_temperature(&mut self, day: Weekday, temperature: i32) {
/* TODO */e);
}
/* TODO: Create weekday2index method which converts a Weekday enum variant into its corresponding zero-based index. */ }
}
我们要做的是完善结构体和一些方法。
其实这次的代码也不难,一共有四个地方需要完善。首先是结构体定义的地方:
pub struct WeekTemperatures {
temperature: [Option<i32>; 7],
}
因为测试函数里,明显可以看到temperature要用Option包裹,所以我们也用Option包裹,并设置长度为7。
然后我们完成new函数,创建一个新的WeekTemperatures
结构体,里面初始化一个都是None的长度为7的数组。
pub fn new() -> Self {
WeekTemperatures {
temperature: [None; 7],
}
}
然后完善set函数。
pub fn set_temperature(&mut self, day: Weekday, temperature: i32) {
self.temperature[Self::weekday2index(day)] = Some(temperature);
}
一行代码搞定,就是单纯赋个值。
然后完成weekday枚举变体转索引的代码
pub fn weekday2index(day: Weekday) -> usize {
match day {
Weekday::Monday => 0,
Weekday::Tuesday => 1,
Weekday::Wednesday => 2,
Weekday::Thursday => 3,
Weekday::Friday => 4,
Weekday::Saturday => 5,
Weekday::Sunday => 6,
}
}
有一点需要注意,我在做题的时候把weekday2index
函数的返回值设置为i32了,然后有难以读懂的报错,在群里问了一下大佬,又搜了一下,才明白:在 Rust 中,任何访问数组(或切片)时的索引都必须是 usize
类型。
OK,这下明白了。
那么这节的学习就先到这里了。