欢迎来到飞鸟慕鱼博客,开始您的技术之旅!
当前位置: 首页知识笔记正文

rust学习——复合类型结构体、复合类型枚举、复合类型元组

终极管理员 知识笔记 47阅读

文章目录 复合类型元组用模式匹配解构元组用点来访问元组元组的使用示例 复合类型结构体结构体定义结构体语法创建结构体实例访问结构体字段简化结构体创建结构体更新语法 结构体的内存排列元组结构体(Tuple Struct)单元结构体(Unit-like Struct)结构体数据的所有权打印结构体 复合类型枚举

复合类型元组

元组是由多种类型组合到一起形成的因此它是复合类型元组的长度是固定的元组中元素的顺序也是固定的。

可以通过以下语法创建一个元组

fn main() {    let tup: (i32, f64, u8)  (500, 6.4, 1);}

变量 tup 被绑定了一个元组值 (500, 6.4, 1)该元组的类型是 (i32, f64, u8)看到没元组是用括号将多个类型组合到一起简单吧

可以使用模式匹配或者 . 操作符来获取元组中的值。

用模式匹配解构元组
fn main() {    let tup  (500, 6.4, 1);    let (x, y, z)  tup;    println!(The value of y is: {}, y);}

上述代码首先创建一个元组然后将其绑定到 tup 上接着使用 let (x, y, z) tup; 来完成一次模式匹配因为元组是 (n1, n2, n3) 形式的因此我们用一模一样的 (x, y, z) 形式来进行匹配元组中对应的值会绑定到变量 x y z上。

这就是解构用同样的形式把一个复杂对象中的值匹配出来。

用点来访问元组

模式匹配可以让我们一次性把元组中的值全部或者部分获取出来如果只想要访问某个特定元素那模式匹配就略显繁琐对此Rust 提供了 . 的访问方式

fn main() {    let x: (i32, f64, u8)  (500, 6.4, 1);    let five_hundred  x.0;    let six_point_four  x.1;    let one  x.2;}

和其它语言的数组、字符串一样元组的索引从 0 开始。

元组的使用示例

元组在函数返回值场景很常用例如下面的代码可以使用元组返回多个值

fn main() {    let s1  String::from(hello);    let (s2, len)  calculate_length(s1);    println!(The length of {} is {}., s2, len);}fn calculate_length(s: String) -> (String, usize) {    let length  s.len(); // len() 返回字符串的长度    (s, length)}

calculate_length 函数接收 s1 字符串的所有权然后计算字符串的长度接着把字符串所有权和字符串长度再返回给 s2len 变量。

在其他语言中可以用结构体来声明一个三维空间中的点例如 Point(10, 20, 30)虽然使用 Rust 元组也可以做到(10, 20, 30)但是这样写有个非常重大的缺陷

不具备任何清晰的含义在下一章节中会提到一种与元组类似的结构体元组结构体可以解决这个问题。

复合类型结构体

Rust 中的结构体Struct与元组Tuple都可以将若干个类型不一定相同的数据捆绑在一起形成整体但结构体的每个成员和其本身都有一个名字这样访问它成员的时候就不用记住下标了。元组常用于非定义的多值传递而结构体用于规范常用的数据结构。结构体的每个成员叫做字段。

结构体定义

Rust 中的结构体Struct与元组Tuple都可以将若干个类型不一定相同的数据捆绑在一起形成整体但结构体的每个成员和其本身都有一个名字这样访问它成员的时候就不用记住下标了。元组常用于非定义的多值传递而结构体用于规范常用的数据结构。结构体的每个成员叫做字段。

这是一个结构体定义

struct Site {    domain: String,    name: String,    nation: String,    found: u32,}

注意如果你常用 C/C请记住在 Rust 里 struct 语句仅用来定义不能声明实例结尾不需要 ; 符号而且每个字段定义之后用 , 分隔。

结构体语法 创建结构体实例

为了使用上述结构体我们需要创建 User 结构体的实例

    let user1  User {        email: String::from(someoneexample.com),        username: String::from(someusername123),        active: true,        sign_in_count: 1,    };

有几点值得注意:

初始化实例时每个字段都需要进行初始化初始化时的字段顺序不需要和结构体定义时的顺序一致 访问结构体字段

通过 . 操作符即可访问结构体实例内部的字段值也可以修改它们

    let mut user1  User {        email: String::from(someoneexample.com),        username: String::from(someusername123),        active: true,        sign_in_count: 1,    };    user1.email  String::from(anotheremailexample.com);

需要注意的是必须要将结构体实例声明为可变的才能修改其中的字段Rust 不支持将某个结构体某个字段标记为可变。

简化结构体创建

下面的函数类似一个构建函数返回了 User 结构体的实例

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

它接收两个字符串参数 emailusername然后使用它们来创建一个 User 结构体并且返回。可以注意到这两行 email: emailusername: username非常的扎眼因为实在有些啰嗦如果你从 TypeScript 过来肯定会鄙视 Rust 一番不过好在它也不是无可救药

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

如上所示当函数参数和结构体字段同名时可以直接使用缩略的方式进行初始化跟 TypeScript 中一模一样。

结构体更新语法

在实际场景中有一种情况很常见根据已有的结构体实例创建新的结构体实例例如根据已有的 user1 实例来构建 user2

  let user2  User {        active: user1.active,        username: user1.username,        email: String::from(anotherexample.com),        sign_in_count: user1.sign_in_count,    };

老话重提如果你从 TypeScript 过来肯定觉得啰嗦爆了竟然手动把 user1 的三个字段逐个赋值给 user2好在 Rust 为我们提供了 结构体更新语法

  let user2  User {        email: String::from(anotherexample.com),        ..user1    };

因为 user2 仅仅在 email 上与 user1 不同因此我们只需要对 email 进行赋值剩下的通过结构体更新语法 ..user1 即可完成。

.. 语法表明凡是我们没有显式声明的字段全部从 user1 中自动获取。需要注意的是 ..user1 必须在结构体的尾部使用。

结构体更新语法跟赋值语句 非常相像因此在上面代码中user1 的部分字段所有权被转移到 user2username 字段发生了所有权转移作为结果user1 无法再被使用。

聪明的读者肯定要发问了明明有三个字段进行了自动赋值为何只有 username 发生了所有权转移

仔细回想一下所有权那一节的内容我们提到了 Copy 特征实现了 Copy 特征的类型无需所有权转移可以直接在赋值时进行 数据拷贝其中 boolu64 类型就实现了 Copy 特征因此 activesign_in_count 字段在赋值给 user2 时仅仅发生了拷贝而不是所有权转移。

值得注意的是username 所有权被转移给了 user2导致了 user1 无法再被使用但是并不代表 user1 内部的其它字段不能被继续使用例如

let user1  User {    email: String::from(someoneexample.com),    username: String::from(someusername123),    active: true,    sign_in_count: 1,};let user2  User {    active: user1.active,    username: user1.username,    email: String::from(anotherexample.com),    sign_in_count: user1.sign_in_count,};println!({}, user1.active);// 下面这行会报错println!({:?}, user1);
结构体的内存排列

先来看以下代码

#[derive(Debug)] struct File {   name: String,   data: Vec<u8>, } fn main() {   let f1  File {     name: String::from(f1.txt),     data: Vec::new(),   };   let f1_name  &f1.name;   let f1_length  &f1.data.len();   println!({:?}, f1);   println!({} is {} bytes long, f1_name, f1_length); }

上面定义的 File 结构体在内存中的排列如下图所示

从图中可以清晰地看出 File 结构体两个字段 namedata 分别拥有底层两个 [u8] 数组的所有权(String 类型的底层也是 [u8] 数组)通过 ptr 指针指向底层数组的内存地址这里你可以把 ptr 指针理解为 Rust 中的引用类型。

该图片也侧面印证了把结构体中具有所有权的字段转移出去后将无法再访问该字段但是可以正常访问其它的字段

元组结构体(Tuple Struct)

有一种更简单的定义和使用结构体的方式元组结构体

元组结构体是一种形式是元组的结构体。

与元组的区别是它有名字和固定的类型格式。它存在的意义是为了处理那些需要定义类型经常使用又不想太复杂的简单数据

struct Color(u8, u8, u8);struct Point(f64, f64);let black  Color(0, 0, 0);let origin  Point(0.0, 0.0);

颜色和点坐标是常用的两种数据类型但如果实例化时写个大括号再写上两个名字就为了可读性牺牲了便捷性Rust 不会遗留这个问题。元组结构体对象的使用方式和元组一样通过 . 和下标来进行访问

实例

fn main() {    struct Color(u8, u8, u8);    struct Point(f64, f64);    let black  Color(0, 0, 0);    let origin  Point(0.0, 0.0);    println!(black  ({}, {}, {}), black.0, black.1, black.2);    println!(origin  ({}, {}), origin.0, origin.1);}

运行结果

black  (0, 0, 0)origin  (0, 0)
单元结构体(Unit-like Struct)

结构体可以只作为一种象征而无需任何成员

struct UnitStruct;

我们称这种没有身体的结构体为单元结构体Unit Struct。

结构体数据的所有权

在之前的 User 结构体的定义中有一处细节我们使用了自身拥有所有权的 String 类型而不是基于引用的 &str 字符串切片类型。这是一个有意而为之的选择因为我们想要这个结构体拥有它所有的数据而不是从其它地方借用数据。

打印结构体

使用#[derive(Debug)]

#[derive(Debug)]struct Rectangle {    width: u32,    height: u32,}fn main() {    let rect1  Rectangle {        width: 30,        height: 50,    };    println!(rect1 is {:?}, rect1);    println!(rect1 is {:#?}, rect1);}

使用{:?}{:#?}打印结构体的区别

rect1 is Rectangle { width: 30, height: 50 }rect1 is Rectangle {    width: 30,    height: 50,}

使用dbg!打印调试信息

下面的例子中清晰的展示了 dbg! 如何在打印出信息的同时还把表达式的值赋给了 width:

#[derive(Debug)]struct Rectangle {    width: u32,    height: u32,}fn main() {    let scale  2;    let rect1  Rectangle {        width: dbg!(30 * scale),        height: 50,    };    dbg!(&rect1);}

最终的 debug 输出如下:

$ cargo run[src/main.rs:10] 30 * scale  60[src/main.rs:14] &rect1  Rectangle {    width: 60,    height: 50,}

可以看到我们想要的 debug 信息几乎都有了代码所在的文件名、行号、表达式以及表达式的值简直完美

复合类型枚举

枚举类在 Rust 中并不像其他编程语言中的概念那样简单但依然可以十分简单的使用

#[derive(Debug)]enum Book {    Papery, Electronic}fn main() {    let book  Book::Papery;    println!({:?}, book);}

运行结果

Papery

书分为纸质书Papery book和电子书Electronic book。

如果你现在正在开发一个图书管理系统你需要描述两种书的不同属性纸质书有索书号电子书只有 URL你可以为枚举类成员添加元组属性描述

enum Book {    Papery(u32),    Electronic(String),}let book  Book::Papery(1001);let ebook  Book::Electronic(String::from(url://...));

如果你想为属性命名可以用结构体语法

enum Book {    Papery { index: u32 },    Electronic { url: String },}let book  Book::Papery{index: 1001};

虽然可以如此命名但请注意并不能像访问结构体字段一样访问枚举类绑定的属性。访问的方法在 match 语法中。

标签:
声明:无特别说明,转载请标明本文来源!