Rust 基础入门 - 第三章
整点其他复合类型:枚举、数组
枚举 (enumeration)
枚举,可以通过列举可能的成员来定义一个类型,这些成员称为枚举成员。例如扑克牌的花色,因为花色就只有四种:
enum PokerSuit {
Spades, // 黑桃
Hearts, // 红心
Diamonds, // 方块
Clubs, // 梅花
}
从抽象角度来看,这四种花色尽管各不相同,但都属于扑克牌花色这个概念。
枚举类型 是一个类型,它会包含所有可能的 枚举成员 , 而 枚举值 是该类型中的具体某个成员的实例。
枚举值
可以这样创建枚举类型:
let heart = PokerSuit::Hearts;
let spade = PokerSuit::Spades;
如上所示,通过 ::
操作符来访问枚举成员,这里的 PokerSuit
是枚举类型,Hearts
和 Spades
是枚举值。
可以使用枚举成员:
#[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
,所以我们可以把 heart
和 spade
作为参数传入。这两玩意儿都是 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 中,最常用的有两种数组:
- 固定长度数组
array
:速度快、长度固定 - 可变长度数组
vector
:可动态增长、有性能损耗
所以一般称 array
为 数组 ,称 vector
为 动态数组 。
这两种数组的关系,和 str
和 String
的关系类似。
先重点学习固定长度数组 array
。有如下三要素:
- 长度固定
- 元素类型相同
- 依次线性排列
实际上 vector
和 Python
中的 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 行,创建了三个数组,分别:
- 最基础的语法
- 显式指定数组元素类型和长度,也从侧面反应了数组的特性
- 语法糖:初始化一个某个值重复出现 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]);
}
数组切片有如下特点:
- 切片的长度可以与数组不同,并不是固定的,而是取决于你使用时指定的起始和结束位置
- 创建切片的代价非常小,因为切片只是针对底层数组的一个引用
- 切片类型 [T] 不固定, 但是切片引用类型 &[T]是固定大小,Rust 喜欢这种类型