当前位置: 首页 > news >正文

Rust学习笔记(四)|结构体与枚举(面向对象、模式匹配)

本篇文章包含的内容

  • 1 结构体
    • 1.1 定义和初始化结构体
    • 1.2 Tuple Struct
    • 1.3 结构体方法(Rust 面向对象)
    • 1.4 关联函数
  • 2 枚举
    • 2.1 定义和使用枚举
    • 2.2 将数据附加到枚举的变体中
    • 2.3 Option 枚举
    • 2.4 模式匹配
      • 2.4.1 match语句
      • 2.4.2 if let语句


1 结构体

1.1 定义和初始化结构体

定义结构体必须显式指定结构体成员变量的类型。实例化结构体时需要对结构体的每个成员变量赋值,使用下面的写法定义和初始化结构体:

fn main() {let user1 = User {email: String::from("example@example.com"),username: String::from("Nikky"),active: true,sign_in_count: 556,};
}struct User {username: String,email: String,sign_in_count: u64,active: bool,
}

获取或者修改结构体实例中某个字段的值的方式和其他语言类似,使用点标记法即可,例如user1.email。上面例子中的user1默认也是不可变的,如果要修改某个字段,需要将这个结构体声明为可变的,即let mut user1 = User {...}。需要注意,如果结构体实例是可变的,那么其中的每个字段都是可变的,Rust不允许存在一部分字段不变,另一部分字段可变的结构体。

理所当然的,结构体也可以是函数的返回值:

fn build_user(email: String, username: String) -> User {User {email: emailusername: usernameactive: true,sign_in_count: 556,}
}

上面的例子中,初始化变量名和字段的变量名相同,那么初始化时可以用下面的方法简写(又是一个语法糖):

fn build_user(email: String, username: String) -> User {User {email,username,active: true,sign_in_count: 556,}
}

当你想通过基于某个结构体实例来初始化另一个结构体实例时,可以使用struct更新语法:

	let user1 = User {email: String::from("example@example.com"),username: String::from("Nikky"),active: true,sign_in_count: 556,};let user2 = User {email: String::from("another_example@example.com"),username: String::from("Tom"),..user1};

上面的例子中定义emailusername的类型为String而不是&str,使得该struct实例拥有其所有的数据(所有权),并且只要结构体实例是有效的,那么其中的数据也一定有效。struct中也可以存放引用,但是必须使用生命周期。生命周期保证只要struct实例是有效的,那么其中的引用也一定有效。生命周期的概念之后才会涉及。

自定义的struct往往没有Display方法,所以无法被println!()直接输出,如果要使用println!()输出,可以采用下面的写法:

#[derive(Debug)]	//
struct Rectangle {width: u32,length: u32,
}fn main() {let rect = Rectangle {width: 30,length: 50,};println!("{}", area(&rect));println!("{:?}", rect);     // 多行打印使用{:#?}
}fn area(rect: &Rectangle) -> u32 {rect.width * rect.length
}

需要注意,打印结构体时必须使用#[derive(Debug)]注解,使得Rectangle派生于Debug这个trait。

1.2 Tuple Struct

Rust中允许我们定一个类似tuple的struct,它就是Tuple Struct,它有一个整体的结构体名,但是其中的元素可以没有名字。适用于想给整个tuple起名,让其不同于其他的Tuple,但是又不需要给其中的每个元素起名的情况。

fn main() {struct Color(i32, i32, i32);struct Point(i32, i32, i32);let black = Color(0, 0, 0);let origin = Point(0, 0, 0);     // black和origin数据类似,但是是不同的类型
}

1.3 结构体方法(Rust 面向对象)

在C/C++中,class说白了就是拥有很多方法的struct,那么Rust中是否可以为结构体添加方法呢?当然可以。看下面这个例子:

struct Rectangle {width: u32,length: u32,
}impl Rectangle {fn area(&self) -> u32 {		// 借用self,不需要其所有权self.width * self.length}
}fn main() {let rect = Rectangle {width: 30,length: 50,};println!("{}", rect.area());
}

Rust的方法是在struct(或者enumtrait对象)的上下文中使用impl关键字(implementation)定义的。方法的第一个参数总是self

Rust会自动引用或者自动解引用,在调用方法时,rect.area()就相当于(&rect).area(),Rust会自动在变量前添加&&mut或者*。以便于实例匹配方法的签名。

一个impl里可以定义多个方法,也可以在多个impl块中定义,方法除了自身以外也可以有其他参数:

struct Rectangle {width: u32,length: u32,
}impl Rectangle {fn area(&self) -> u32 {self.width * self.length}fn can_hold(&self, other: &Rectangle) -> bool {self.width > other.width && self.length >= other.length}
}fn main() {let rect1 = Rectangle {width: 30,length: 50,};let rect2 = Rectangle {width: 10,length: 20,};println!("{}", rect1.area());println!("{}", rect1.can_hold(&rect2));
}

1.4 关联函数

impl块中也可以定义函数,这个函数不是对象的方法,但是它又与struct有一定的关联,我们把这种函数称为关联函数。通常使用关联函数作为对象的构造器,例如常用的String::from("")函数构造一个String。

#[derive(Debug)]
struct Rectangle {width: u32,length: u32,
}impl Rectangle {fn area(&self) -> u32 {self.width * self.length}fn can_hold(&self, other: &Rectangle) -> bool {self.width >= other.width && self.length >= other.length}fn square(size: u32) -> Rectangle {Rectangle {width: size,length: size,}}
}fn main() {let rect1 = Rectangle {width: 30,length: 50,};let rect2 = Rectangle {width: 10,length: 20,};let s = Rectangle::square(20);println!("{}", rect1.area());println!("{}", rect1.can_hold(&rect2));println!("{:?}", s);
}

2 枚举

2.1 定义和使用枚举

枚举(Enum)允许我们使用确定的可能值定义一个类型。枚举的可能的值称为变体。枚举的最基础作用其实就是提高代码的可读性,这和C/C++的枚举没有本质的区别。

enum IpAddrKind {V4,V6,
}fn main() {let four_ip = IpAddrKind::V4;let six_ip = IpAddrKind::V6;route(four_ip);route(six_ip);route(IpAddrKind::V4);
}fn route(ip_kind:IpAddrKind) {// some code...
}

自然地,枚举可以是结构体的成员变量:

enum IpAddrKind {V4,V6,
}struct IpAddr {ip_addr_kind: IpAddrKind,ip_addr: String,
}fn main() {let home = IpAddr {ip_addr: String::from("127.0.0.1"),ip_addr_kind: IpAddrKind::V4,};let loopback = IpAddr {ip_addr: String::from("::1"),ip_addr_kind: IpAddrKind::V6,};
}

2.2 将数据附加到枚举的变体中

枚举的变体中可以添加一些附加数据,这样可以让我们不用定义额外的结构体存储其他的信息:

enum IpAddrKind {V4(u8, u8, u8, u8),V6(String),
}fn main() {let home = IpAddrKind::V4(192, 168, 0, 1);let loopback = IpAddrKind::V6(String::from("::1"));
}

标准库的IpAddr就使用了类似的设计,不过标准库中枚举IpAddr中嵌入的是结构体struct。说明我么可以在枚举中嵌入任意的数据类型,甚至嵌入另外的枚举。枚举也可以定义方法,枚举方法的第一个参数也是self,调用依然采用.进行调用。看下面这个例子:

enum Message {Quit,Move {x: i32, y: i32},      // 关联一个匿名结构体Write(String),ChangeColor(i32, i32, i32),
}impl Message {fn call(&self) {// some code ...}
}fn main() {let q = Message::Quit;let m = Message::Move {x: 1, y: 2};let w = Message::Write(String::from("hello"));let c = Message::ChangeColor(0, 128, 0);q.call();
}

2.3 Option 枚举

Option枚举位于标准库中,它是预导入(Prelude)的。它主要是为了解决其他语言中一个值可能存在,并且可能是空值null的情况。Rust为了解决null的弊端,直接摒弃了null,取而代之的是Option枚举,使得开发者要想使用null,则必须同时处理值存在和不存在两种情况。

Option在标准库中的定义如下所示,T是泛型参数。Option枚举是预导入的,它的两个变体也是预导入的,所以程序中可以直接使用。

// 标准库中的定义
enum Option<T> {Some(T),None,
}
let some_number = Some(1);
let some_string = Some("hello");let absent_number: Option<i32> = None;

2.4 模式匹配

2.4.1 match语句

match是Rust中的一个强大的控制流运算符,它允许一个值与一系列模式进行依次匹配,这个“模式”可以是子面值,变量名或者是通配符,匹配成功后执行对应的代码块。

enum Coin {Penny,Nickel,Dime,Quarter,
}fn value_in_cents(coin: Coin) -> u8 {match coin {Coin::Penny => 1,Coin::Nickel => 5,Coin::Dime => 10,Coin::Quarter => 25,}
}fn main() {}

匹配到的模式可以关联被匹配对象的部分值,利用这个特性(语法糖?),我么可以方便地提取枚举中的值:

#[derive(Debug)]
enum UsState {Alabama,Alaska,
}enum Coin {Penny,Nickel,Dime,Quarter(UsState),       // 关联枚举数据
}fn value_in_cents(coin: Coin) -> u8 {match coin {Coin::Penny => 1,Coin::Nickel => 5,Coin::Dime => 10,Coin::Quarter(state) => {println!("Quarter from: {:?}", state);25},}
}fn main() {value_in_cents(Coin::Quarter(UsState::Alabama));value_in_cents(Coin::Quarter(UsState::Alaska));
}

在这里插入图片描述

Option枚举可以和match语句结合处理当数据为空的情况:

fn plus_one(number: Option<i32>) -> Option<i32> {match number {None => None,Some(i) => Some(i + 1)	// 返回也必须是Option<i32>}
}fn main() {let five = Some(5);let six = plus_one(five);let none = plus_one(None);
}

需要注意,match必须穷举所有的可能。如果没有穷举所有的可能编译器就会报错,但是有时候确实只需要处理其中一部分数据,我么需要一个default,Rust使用下划线通配符表示未提及的情况:

fn main() {let v = 1u8;match v {1 => println!("one!"),3 => println!("three!"),_ => {},    // 或者 _ => () }
}

2.4.2 if let语句

if let语句相当于match语句只需要处理一种情况时的语法糖,后面可以加else,写法如下:

fn main() {let v = Some(3);if let Some(i) = v {      // 注意这里是 =,并且被匹配变量必须写在后面println!("the number is {}", i);} else {println!("others");}
}

虽然看起来直接写if更简单,但是if let本质是模式匹配,除了控制流,它还有另一个重要的功能:提取枚举携带的值。所以它和普通的控制流语句if还是不同的。

在这里插入图片描述


  原创笔记,码字不易,欢迎点赞,收藏~ 如有谬误敬请在评论区不吝告知,感激不尽!博主将持续更新有关嵌入式开发、FPGA方面的学习笔记。


http://www.dtcms.com/a/334065.html

相关文章:

  • C++基础——内存管理
  • 基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
  • 零知开源——基于STM32F407VET6的TCS230颜色识别器设计与实现
  • 开源数据发现平台:Amundsen Frontend Service 推荐实践
  • Camx-Tuning参数加载流程分析
  • 【时时三省】(C语言基础)共用体类型数据的特点
  • 她的热情为何突然冷却?—— 解析 Kafka 吞吐量下降之谜
  • 智能合约:区块链时代的“数字契约革命”
  • 外出业务员手机自动添加报价单​——仙盟创梦IDE
  • 多商户商城系统源码选型指南:开源框架 vs 定制开发的优劣对比
  • Android RxJava 组合操作符实战:优雅处理多数据源
  • 12分区南排烟机,多线模块没电
  • Linux上管理Java的JDK版本
  • LeetCode 刷题【43. 字符串相乘】
  • 34 HTB Cat 机器 - 中等难度
  • 完整设计 之 智能合约系统:主题约定、代理协议和智能合约 (临时命名)----PromptPilot (助手)答问之2
  • Three.js三大组件:场景(Scene)、相机(Camera)、渲染器(Renderer)
  • 线性代数之两个宇宙文明关于距离的对话
  • 图像相似度算法汇总及Python实现
  • 01数据结构-关键路径
  • Unity 游戏提升 Android TargetVersion 相关记录
  • Docker小游戏 | 使用Docker部署人生重开模拟器
  • MySQL的三大范式:
  • 机器学习--决策树
  • Rust 语法基础教程
  • sqli-labs通关笔记-第52关 GET数值型order by堆叠注入(手工注入+脚本注入两种方法)
  • Ubuntu 25.04 安装并使用 MySQL 8.4.5 的步骤
  • 使用 npm-run-all2 简化你的 npm 脚本工作流
  • Linux中的restore
  • PHP域名授权系统网站源码/授权管理工单系统/精美UI/附教程