五、结构体与枚举

166 阅读5分钟

五、结构体与枚举

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