49、静态数组(Arrays)

一旦我们想要做“票务管理”,我们就需要考虑一种存储多张tickets的方法。反过来,这意味着我们需要考虑集合的问题,特别是同质集合:我们希望存储多个相同类型的实例。

在这方面,Rust能提供什么呢?

Arrays

 

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

 

我们可以这样定义一个数组:

// Array type syntax: [ <type> ; <number of elements> ]
// 数组类型语法:[<数据类型>;<元素数量>]
let numbers: [u32; 3] = [1, 2, 3];

 

这将创建一个包含3个整数的数组,初始化值分别为123。数组的类型是[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,这下明白了。

 

那么这节的学习就先到这里了。

阅读剩余
THE END