Skip to main content

Rust 基础入门 - 第三章

整点其他复合类型:枚举、数组

枚举 (enumeration)

枚举,可以通过列举可能的成员来定义一个类型,这些成员称为枚举成员。例如扑克牌的花色,因为花色就只有四种:

enum PokerSuit {
Spades, // 黑桃
Hearts, // 红心
Diamonds, // 方块
Clubs, // 梅花
}

从抽象角度来看,这四种花色尽管各不相同,但都属于扑克牌花色这个概念。

note

枚举类型 是一个类型,它会包含所有可能的 枚举成员 , 而 枚举值 是该类型中的具体某个成员的实例。

枚举值

可以这样创建枚举类型:


let heart = PokerSuit::Hearts;
let spade = PokerSuit::Spades;

如上所示,通过 :: 操作符来访问枚举成员,这里的 PokerSuit 是枚举类型,HeartsSpades 是枚举值。

可以使用枚举成员:

#[derive(Debug)]
enum PokerSuit {
Spades,
Hearts,
Diamonds,
Clubs,
}

fn main() {
let heart = PokerSuit::Hearts;
let spade = PokerSuit::Spades;

print_suit(heart);
print_suit(spade);
}

fn print_suit(suit: PokerSuit) {
println!("Suit is {:?}", suit);
}

第 17 行,print_suit 函数的参数类型是 PokerSuit,所以我们可以把 heartspade 作为参数传入。这两玩意儿都是 PokerSuit 类型,所以可以传入。

但是扑克牌光有花色还不够,加上数字,可以定义一种结构体,其中包含花色和数值:

enum PokerSuit {
Spades,
Hearts,
Diamonds,
Clubs,
}

struct PokerCard {
suit: PokerSuit, // 花色
value: u8, // 点数
}
fn main() {
let card1 = PokerCard {
suit: PokerSuit::Spades,
value: 1,
};
let card2 = PokerCard {
suit: PokerSuit::Hearts,
value: 2,
};
}

但是还有更简洁的方式:

enum PokerSuit {
Spades,
Hearts,
Diamonds,
Clubs,
}
enum PokerCard { // 再定义一个枚举类型
Spades(u8),
Hearts(u8), // 直接把信息关联到枚举成员上
Diamonds(u8),
Clubs(u8),
}
fn main() {
let card1 = PokerCard::Spades(1);
let card2 = PokerCard::Hearts(2);
}

8~11 行,直接把信息关联到枚举成员上,大大减少了代码量。

实际上,可以把任何类型的数据都可以放入枚举成员中。如下:


enum Message {
Quit,
Move {x: i32, y: i32},
Write(String),
ChangeColor(i32, i32, i32),
}

fn main() {
let m1 = Message::Quit;
let m2 = Message::Move{x:1,y:1};
let m3 = Message::ChangeColor(255,255,0);
}

该枚举类型 Message 包含四种不同成员,每个成员都可以携带不同的数据类型:

  • Quit 成员不携带任何数据
  • Move 成员携带了一个结构体
  • Write 成员携带了一个字符串
  • ChangeColor 成员携带了三个整数。

这也相当于定义了四种不同的结构体:


struct QuitMessage; // 单元结构体
struct MoveMessage {
x: i32,
y: i32,
}
struct WriteMessage(String); // 元组结构体
struct ChangeColorMessage(i32, i32, i32); // 元组结构体

这样定义四种不同结构体,就不属于同一种类型了,在后续使用中,就无法被统一使用了。

而且从代码规范的角度来说,枚举的实现方式更简洁、代码 内聚性 强。当然了,前提是这些枚举成员都能抽象出一个共同的特征。

数组

数组 是日常开发中最常用的数据结构之一。

Rust 中,最常用的有两种数组:

  1. 固定长度数组 array:速度快、长度固定
  2. 可变长度数组 vector:可动态增长、有性能损耗

所以一般称 array 数组 ,称 vector 动态数组

这两种数组的关系,和 strString 的关系类似。

先重点学习固定长度数组 array。有如下三要素:

  • 长度固定
  • 元素类型相同
  • 依次线性排列
tip

实际上 vectorPython 中的 list 更类似

创建数组

创建数组的语法和大部分语言类似:

let arr1 = [1, 2, 3, 4, 5];
let arr2: [i32; 5] = [1, 2, 3, 4, 5];
let arr3 = [0; 5]; // [0, 0, 0, 0, 0]

1~3 行,创建了三个数组,分别:

  1. 最基础的语法
  2. 显式指定数组元素类型和长度,也从侧面反应了数组的特性
  3. 语法糖:初始化一个某个值重复出现 N 次的数组:[value; N]

由于它的元素类型大小固定,且长度也是固定,因此数组 array 是存储在栈上,性能也会非常优秀。

访问数组元素

根据数组的特性: 连续存储 ,因此可以通过下标访问数组元素:

fn main() {
let a = [9, 8, 7, 6, 5];

let first = a[0]; // 获取 a 数组第一个元素
let second = a[1]; // 获取第二个元素
}

和大多数语言一样,数组下标从 0 开始。

越界访问

当你尝试使用索引访问元素时,Rust 将检查你指定的索引是否小于数组长度。如果索引大于或等于数组长度,Rust 会出现 panic

当数组元素为非基础类型

当使用语法糖来创建重复元素的数组时,如果元素类型为非基础类型,会出现编译错误:

#![allow(unused)]
fn main() {
let array = [String::from("rust is good!"); 8]; // 编译错误的

println!("{:#?}", array);
}

究其原因,基本类型在 Rust 中赋值是具有 [Copy 特性的](rust-baisc-3# 浅拷贝),而 String 类型没有,所以不能用上述语法创建。

数组切片

数组切片允许我们 引用 数组的一部分:


#![allow(unused)]
fn main() {
let a: [i32; 5] = [1, 2, 3, 4, 5];

let slice: &[i32] = &a[1..3];

assert_eq!(slice, &[2, 3]);
}

数组切片有如下特点:

  1. 切片的长度可以与数组不同,并不是固定的,而是取决于你使用时指定的起始和结束位置
  2. 创建切片的代价非常小,因为切片只是针对底层数组的一个引用
  3. 切片类型 [T] 不固定, 但是切片引用类型 &[T]是固定大小,Rust 喜欢这种类型

参考资料