Rust基础知识系列三:数据基础类型
数组和切片
数组(array)是一组拥有相同类型 T 的对象的集合,在内存中是连续存储的。数组使用中括号 []
来创建,且它们的大小在编译时会被确定。数组的类型标记为 [T; length]
(译注:T
为元素类型,length
表示数组大小)。
切片(slice)类型和数组类似,但其大小在编译时是不确定的。相反,切片是一个双字对象(two-word object),第一个字是一个指向数据的指针,第二个字是切片的长度。这个 “字” 的宽度和 usize 相同,由处理器架构决定,比如在 x86-64
平台上就是 64 位。slice
可以用来借用数组的一部分。slice
的类型标记为 &[T]
。
use std::mem;
// 此函数借用一个 slice
fn analyze_slice(slice: &[i32]) {
println!("first element of the slice: {}", slice[0]);
println!("the slice has {} elements", slice.len());
}
fn main() {
// 定长数组(类型标记是多余的)
let xs: [i32; 5] = [1, 2, 3, 4, 5];
// 所有元素可以初始化成相同的值
let ys: [i32; 500] = [0; 500];
// 下标从 0 开始
println!("first element of the array: {}", xs[0]);
println!("second element of the array: {}", xs[1]);
// `len` 返回数组的大小
println!("array size: {}", xs.len());
// 数组是在栈中分配的
println!("array occupies {} bytes", mem::size_of_val(&xs));
// 数组可以自动被借用成为 slice
println!("borrow the whole array as a slice");
analyze_slice(&xs);
// slice 可以指向数组的一部分
println!("borrow a section of the array as a slice");
analyze_slice(&ys[1 .. 4]);
// 越界的下标会引发致命错误(panic)
println!("{}", xs[5]);
}
结构体
结构体(structure,缩写成 struct
)有 3 种类型,使用 struct
关键字来创建:
- 元组结构体(tuple struct),事实上就是具名元组而已。
- 经典的 C 语言风格结构体(C struct)。
- 单元结构体(unit struct),不带字段,在泛型中很有用。
#[derive(Debug)]
struct Person {
name: String,
age: u8,
}
// 单元结构体
struct Unit;
// 元组结构体
struct Pair(i32, f32);
// 带有两个字段的结构体
struct Point {
x: f32,
y: f32,
}
// 结构体可以作为另一个结构体的字段
#[allow(dead_code)]
struct Rectangle {
// 可以在空间中给定左上角和右下角在空间中的位置来指定矩形。
top_left: Point,
bottom_right: Point,
}
fn main() {
// 使用简单的写法初始化字段,并创建结构体
let name = String::from("Peter");
let age = 27;
let peter = Person { name, age };
// 以 Debug 方式打印结构体
println!("{:?}", peter);
// 实例化结构体 `Point`
let point: Point = Point { x: 10.3, y: 0.4 };
// 访问 point 的字段
println!("point coordinates: ({}, {})", point.x, point.y);
// 使用结构体更新语法创建新的 point,
// 这样可以用到之前的 point 的字段
let bottom_right = Point { x: 5.2, ..point };
// `bottom_right.y` 与 `point.y` 一样,因为这个字段就是从 `point` 中来的
println!("second point: ({}, {})", bottom_right.x, bottom_right.y);
// 使用 `let` 绑定来解构 point
let Point { x: left_edge, y: top_edge } = point;
let _rectangle = Rectangle {
// 结构体的实例化也是一个表达式
top_left: Point { x: left_edge, y: top_edge },
bottom_right: bottom_right,
};
// 实例化一个单元结构体
let _unit = Unit;
// 实例化一个元组结构体
let pair = Pair(1, 0.1);
// 访问元组结构体的字段
println!("pair contains {:?} and {:?}", pair.0, pair.1);
// 解构一个元组结构体
let Pair(integer, decimal) = pair;
println!("pair contains {:?} and {:?}", integer, decimal);
}
向量 (vector
)
对向量使用loop和map,
fn vec_loop(mut v: Vec<i32>) -> Vec<i32> {
for element in v.iter_mut() {
// TODO: Fill this up so that each element in the Vec `v` is
// multiplied by 2.
*element = *element*2;
}
// At this point, `v` should be equal to [4, 8, 12, 16, 20].
v
}
fn vec_map(v: &Vec<i32>) -> Vec<i32> {
v.iter().map(|element| {
// TODO: Do the same thing as above - but instead of mutating the
// Vec, you can just return the new number!
element*2
}).collect()
}
校验,
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vec_loop() {
let v: Vec<i32> = (1..).filter(|x| x % 2 == 0).take(5).collect();
let ans = vec_loop(v.clone());
assert_eq!(ans, v.iter().map(|x| x * 2).collect::<Vec<i32>>());
}
#[test]
fn test_vec_map() {
let v: Vec<i32> = (1..).filter(|x| x % 2 == 0).take(5).collect();
let ans = vec_map(&v);
assert_eq!(ans, v.iter().map(|x| x * 2).collect::<Vec<i32>>());
}
}
枚举
enum
关键字允许创建一个从数个不同取值中选其一的枚举类型(enumeration)。任何一个在 struct
中合法的取值在 enum
中也合法。
创建一个 enum
(枚举)来对 web 事件分类。注意变量名和类型共同指定了 enum
取值的种类:PageLoad
不等于 PageUnload
,KeyPress(char)
不等于Paste(String)
。各个取值不同,互相独立。
enum WebEvent {
// 一个 `enum` 可以是单元结构体(称为 `unit-like` 或 `unit`),
PageLoad,
PageUnload,
// 或者一个元组结构体,
KeyPress(char),
Paste(String),
// 或者一个普通的结构体。
Click { x: i64, y: i64 }
}
此函数将一个 WebEvent
enum 作为参数,无返回值。
fn inspect(event: WebEvent) {
match event {
WebEvent::PageLoad => println!("page loaded"),
WebEvent::PageUnload => println!("page unloaded"),
// 从 `enum` 里解构出 `c`。
WebEvent::KeyPress(c) => println!("pressed '{}'.", c),
WebEvent::Paste(s) => println!("pasted \"{}\".", s),
// 把 `Click` 解构给 `x` and `y`。
WebEvent::Click { x, y } => {
println!("clicked at x={}, y={}.", x, y);
},
}
}
然后,main
函数实例,
fn main() {
let pressed = WebEvent::KeyPress('x');
// `to_owned()` 从一个字符串切片中创建一个具有所有权的 `String`。
let pasted = WebEvent::Paste("my text".to_owned());
let click = WebEvent::Click { x: 20, y: 80 };
let load = WebEvent::PageLoad;
let unload = WebEvent::PageUnload;
inspect(pressed);
inspect(pasted);
inspect(click);
inspect(load);
inspect(unload);
}
// Output:
// pressed 'x'.
// pasted "my text".
// clicked at x=20, y=80.
// page loaded
// page unloaded
类型别名
若使用类型别名,则可以通过其别名引用每个枚举变量。当枚举的名称太长或者太一般化,且你想要对其重命名,那么这对你会有所帮助。
enum VeryVerboseEnumOfThingsToDoWithNumbers {
Add,
Subtract,
}
// 创建一个类型别名
type Operations = VeryVerboseEnumOfThingsToDoWithNumbers;
fn main() {
// 我们可以通过别名引用每个枚举变量,避免使用又长又不方便的枚举名字
let x = Operations::Add;
}
最常见的情况就是在 impl
块中使用 Self
别名。
enum VeryVerboseEnumOfThingsToDoWithNumbers {
Add,
Subtract,
}
impl VeryVerboseEnumOfThingsToDoWithNumbers {
fn run(&self, x: i32, y: i32) -> i32 {
match self {
Self::Add => x + y,
Self::Subtract => x - y,
}
}
}
显式使用use
使用 use
声明的话,就可以不写出名称的完整路径了:
#![allow(dead_code)]
enum Status {
Rich,
Poor,
}
enum Work {
Civilian,
Soldier,
}
fn main() {
// 显式地 `use` 各个名称使他们直接可用,而不需要指定它们来自 `Status`。
use Status::{Poor, Rich};
// 自动地 `use` `Work` 内部的各个名称。
use Work::*;
// `Poor` 等价于 `Status::Poor`。
let status = Poor;
// `Civilian` 等价于 `Work::Civilian`。
let work = Civilian;
match status {
// 注意这里没有用完整路径,因为上面显式地使用了 `use`。
Rich => println!("The rich have lots of money!"),
Poor => println!("The poor have no money..."),
}
match work {
// 再次注意到没有用完整路径。
Civilian => println!("Civilians work!"),
Soldier => println!("Soldiers fight!"),
}
}
// Output:
// The poor have no money...
// Civilians work!
测试实例:链表
enum
的一个常见用法就是创建链表(linked-list):
use List::*;
enum List {
// Cons:元组结构体,包含链表的一个元素和一个指向下一节点的指针
Cons(u32, Box<List>),
// Nil:末结点,表明链表结束
Nil,
}
定义方法:
impl List {
// 创建一个空的 List 实例
fn new() -> List {
// `Nil` 为 `List` 类型(译注:因 `Nil` 的完整名称是 `List::Nil`)
Nil
}
// 处理一个 List,在其头部插入新元素,并返回该 List
fn prepend(self, elem: u32) -> List {
// `Cons` 同样为 List 类型
Cons(elem, Box::new(self))
}
// 返回 List 的长度
fn len(&self) -> u32 {
// 必须对 `self` 进行匹配(match),因为这个方法的行为取决于 `self` 的
// 取值种类。
// `self` 为 `&List` 类型,`*self` 为 `List` 类型,匹配一个具体的 `T`
// 类型要好过匹配引用 `&T`。
match *self {
// 不能得到 tail 的所有权,因为 `self` 是借用的;
// 因此使用一个对 tail 的引用
Cons(_, ref tail) => 1 + tail.len(),
// (递归的)基准情形(base case):一个长度为 0 的空列表
Nil => 0
}
}
// 返回列表的字符串表示(该字符串是堆分配的)
fn stringify(&self) -> String {
match *self {
Cons(head, ref tail) => {
// `format!` 和 `print!` 类似,但返回的是一个堆分配的字符串,
// 而不是打印结果到控制台上
format!("{}, {}", head, tail.stringify())
},
Nil => {
format!("Nil")
},
}
}
}
main
函数:
fn main() {
// 创建一个空链表
let mut list = List::new();
// 追加一些元素
list = list.prepend(1);
list = list.prepend(2);
list = list.prepend(3);
// 显示链表的最后状态
println!("linked list has length: {}", list.len());
println!("{}", list.stringify());
}
常量
Rust 有两种常量,可以在任意作用域声明,包括全局作用域。它们都需要显式的类型声明:
const
:不可改变的值(通常使用这种)。static
:具有'static
生命周期的,可以是可变的变量(译注:须使用static mut
关键字)。
有个特例就是 "string
" 字面量。它可以不经改动就被赋给一个 static
变量,因为它的类型标记:&'static str
就包含了所要求的生命周期 'static
。其他的引用类型都必须特地声明,使之拥有 'static
生命周期。这两种引用类型的差异似乎也无关紧要,因为无论如何,static
变量都得显式地声明。
// 全局变量是在所有其他作用域之外声明的。
static LANGUAGE: &'static str = "Rust";
const THRESHOLD: i32 = 10;
fn is_big(n: i32) -> bool {
// 在一般函数中访问常量
n > THRESHOLD
}
fn main() {
let n = 16;
// 在 main 函数(主函数)中访问常量
println!("This is {}", LANGUAGE);
println!("The threshold is {}", THRESHOLD);
println!("{} is {}", n, if is_big(n) { "big" } else { "small" });
// 报错!不能修改一个 `const` 常量。
// THRESHOLD = 5;
// 改正 ^ 注释掉此行
}
// Output:
// This is Rust
// The threshold is 10
// 16 is big