五、结构体与枚举
1. 结构体
1.1 结构体定义
1.1.1 一般结构体
-
在 Rust 中,使用
struct关键字来定义结构体-
使用
.运算符来访问属性,与 JavaScript 访问对象属性的方式相同 -
与 C/C++, Go 等编程语言中的结构体类似
- 使用方式也与这些语言类似,但有一些自己的特性
-
个人认为:结构体的作用类似于 Typescript 中的
type的作用,是为了自定义一个数据类型
-
-
示例代码
// 定义了一个结构体 pub struct Student { age: u16, name: String, class_number: u8, student_number: String, } // 创建了一个结构体变量 pub fn create_student(name: String, age: u16) -> Student { return Student { age: age, name: name, class_number: 12, student_number: get_student_number() }; } fn get_student_number() -> String { return String::from("abc"); }在 Rust 中,不能为结构体指定某一个属性可变,要想修改某个结构体变量的属性,必须将这个结构体变量定义为可变变量
-
结构体变量初始化
-
属性简写
-
类似于 JavaScript ES6 的属性简写
-
示例代码
// 上述的 create_student 函数可以简写成下面的形式 pub fn create_student(name: String, age: u16) -> Student { return Student { age, name, class_number: 12, student_number: get_student_number() }; }
-
-
剩余赋值
-
注意:这里的剩余赋值与 JavaScript ES6 里的解构赋值不同!
-
这里的剩余赋值,仅仅指定了剩余未显式设置值的字段的赋值,而 JavaScript ES6 里的解构赋值会覆盖掉解构之前的值
-
示例代码
// 续上一个示例 fn main() { let jelly = create_student(String::from("Jelly"), 15); let cherry = Student { name: String::from("Cherry"), student_number: get_student_number(), // cherry 的 class_number 和 age 没有手动设定,但是会通过这个剩余赋值,来将 cherry 的这两个属性设置为与 jelly 的这两个属性相同的值 // 注意:如果剩余赋值中包含了非基本数据类型的值,会发生值的 `移动`,原结构体变量的对应属性将不可访问 ..jelly // 注意:这里的结尾是没有逗号的 }; println!("cherry.age = {}, cherry.class_number = {}", cherry.age, cherry.class_number); // 打印结果:cherry.age = 15, cherry.class_number = 12 }
-
-
1.1.2 元组结构体
-
有结构体名称提供的含义,但没有具体的字段名,只有字段的类型
-
注意 :每一个结构体有其自己的类型,即使结构体中的 字段有着相同的类型
-
示例代码
struct Color(u8, u8, u8); struct Point(u8, u8, u8); // 注意:即便 Point 和 Color 有着同样的字段类型,但它们仍然属于不同的结构体类型,且互相是不兼容的 let red = Color(255, 0, 0); let center = Point(0, 0, 0);
1.1.3 类单元结构体
-
类单元结构体 (unit-like struts):一个没有任何字段的结构体,类似于
(),即 unit 类型 -
常见的使用场景:
- 想要在某个类型上实现
trait但不需要在类型中存储数据
- 想要在某个类型上实现
-
示例代码
struct UnitStruct;
1.2 结构体方法
1.2.1 背景
我们可以通过定义一个结构体来作为自定义类型,结构体中包含一系列属性的类型定义。但是,如果我想以 OOP 的方式,为这个结构体类型添加方法怎么办呢?Rust 为结构体提供了一种特殊的特性,用来实现属于结构体的方法:
// 来自官方教程的一个示例
// src/main.rs
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
}
通过这种方式,就为结构体 Rectangle 添加了一个 area 方法。
- 个人理解:通过这样的方式,就相当于把 OOP 概念里的属性和方法放到了不同的位置去定义。
- 属性定义在了
struct里,方法定义在了impl里
- 属性定义在了
1.2.2 特性
-
关联函数
- 在
impl块中定义的 不 以self作为参数的函数 - 经常被用作返回一个对应结构体新实例的 构造函数
- 比如:
String::from,String::new等,以后会接触到更多
- 在
-
每个结构体都允许拥有多个
impl块,下面是一个官方教程里的示例,续接上一个代码片段// 来自官方教程的一个示例 // 续上一个示例 // src/main.rs impl Rectangle { fn area(&self) -> u32 { self.width * self.height } } impl Rectangle { fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height } }
2. 枚举
- 枚举类型也是用于定义自定义类型的一种方式
- 查阅资料后发现,Rust 的枚举类型和 Java, C/C++, Typescript 等编程语言的枚举类型完全不同
2.1 枚举定义
-
基本方式
// 用这种方式定义出来的枚举,每一个枚举属性都是没有值的 enum Gender { boy, girl, } -
带类型的方式
// 有人习惯于用 1 代表男性,2 代表女性,这时就可以用这种类型定义 enum Gender { boy(u8), girl(u8), } // 官方教程里提供的一个示例 enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } -
用途
- 配合
match语句 - 配合
if..let语句
- 配合
2.2 枚举方法
-
注意:这是与其他语言中的枚举区别最大的地方
-
对于一个枚举,可以像结构体那样,通过
impl来定义方法 -
示例代码
// 来自官方教程的一个示例 // 官方教程里提供的一个示例 enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } impl Message { fn call(&self) { // 在这里定义方法体 println!("called"); } } fn main() { let msg = Message::Write(String::from("hello")); msg.call(); // msg 将被作为 call 方法中 &self 的值 }
2.3 Option 枚举
-
作用:用于编码存在或不存在的值
-
为了拥有一个可能为空的值,你必须要显式的将其放入对应类型的
Option中 -
Option枚举提供了两个值:Some(a: T)None