【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据

系列文章目录

【跟小嘉学 Rust 编程】一、Rust 编程基础
【跟小嘉学 Rust 编程】二、Rust 包管理工具使用
【跟小嘉学 Rust 编程】三、Rust 的基本程序概念
【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念
【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据


前言

本章节讲解一种自定义数据类型:结构体,它允许将多个相关的值打包在一起命名,如果你熟悉面向对象语言,那么结构体就像类一样。

主要教材参考 《The Rust Programming Language》


一、定义和实例化结构

1.1、定义结构

要定义结构体,使用关键字 struct 并为结构命名。结构体的名称应该描述组合在一起的数据的重要性。另外打括号包括起来的数据名称和类型,我们称之为字段。

例子:user 结构定义

struct  User {
  active: bool,
  username: String,
  email: String,
  sign_in_count: u64
}

1.2、实例化结构

我们在定义结构后要使用它,需要为每个字段指定具体的值,从而创建该结构的实例,然后添加包含键值对的花括号,其中键就是字段的名称,值就是我们希望存储在这些字段中的数据。

我们不必按照在结构体中声明字段的顺序指定字段。结构定义就像该类型的通用模板,实例用特定的数据填充该模板,以创建该类型的值。

范例:结构的实例化

struct  User {
  active: bool,
  username: String,
  email: String,
  sign_in_count: u64
}

fn main() {
  let user = User{
    active: true,
    username: String::from("someusername123"),
    email: String::from("someone@example.com"),
    sign_in_count: 1,
  };
}

1.3、访问和修改结构体变量的成员

我们要读或写对象的成员,可以使用 变量.字段 的形式来来问。如果要想修改结构体成员的值,我们必须 使用mut 标记结构体。

struct  User {
  active: bool,
  username: String,
  email: String,
  sign_in_count: u64
}

fn main() {
  let mut user = User{
    active: true,
    username: String::from("someusername123"),
    email: String::from("someone@example.com"),
    sign_in_count: 1,
  };

  println!(" user's email is {}", user.email);
  user.email = String::from("anotheremail@example.com");

  println!(" user's active is {}", user.active);
  println!(" user's username is {}", user.username);
  println!(" user's email is {}", user.email);
  println!(" user's sign_in_count is {}", user.sign_in_count);
}

1.4、函数与结构

1.4.1、使用函数实例化用户


struct  User {
  active: bool,
  username: String,
  email: String,
  sign_in_count: u64
}

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

fn main() {
  let email:String = String::from("xiaojia@example.com");
  let username:String = String::from("xiaojia");
  let mut user:User = build_user(email, username);
  
  println!(" user's email is {}", user.email);
  user.email = String::from("anotheremail@example.com");

  println!(" user's active is {}", user.active);
  println!(" user's username is {}", user.username);
  println!(" user's email is {}", user.email);
  println!(" user's sign_in_count is {}", user.sign_in_count);
}

1.4.2、结构体支持精简的实例化

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

1.5、用一个结构体去创建另一个结构体

1.5.1、用结构体成员实例化结构

  let email:String = String::from("xiaojia@example.com");
  let username:String = String::from("xiaojia");
  let user01:User = build_user(email, username);

  let user02:User = User { 
    active: user01.active, 
    username: user01.username, 
    email: String::from("another@example.com"),
    sign_in_count: user01.sign_in_count
  };

1.5.2、结构体更新(struct update)的语法

  let email:String = String::from("xiaojia@example.com");
  let username:String = String::from("xiaojia");
  let user01:User = build_user(email, username);
  let user02:User = User { 
    email: String::from("another@example.com"),
    ..user01
  };

结构体更新语法使用 = 就像赋值,这是因为它移动数据,在创建 user02 之后,我们不能再将 user01 作为一个整体使用,因为 user01 的 username 被移到了 user02 中了。

但是 active 和 sign_in_count 值,在创建 user02 之后 在 user01 之中仍然有效。

1.6、使用未命名字段的元组创建不同的类型(Tuple struct)

tuple- struct 是 Rust 对 struct 关键字的一个创新,可以将 Tuple Struct 理解为非匿名的tuple。

#[allow(dead_code)]
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
	
	// 使用 模式解构 方式来获取值,我们称之为 newtype parttern
    let Point(x, y, z) = origin;  
    
    // 使用索引方式来获取值
    let length = origin.0;
    let width = origin.1;
    let heigth =  origin.2;
}

1.7、Unit-Like Struct

在 Rust 中可以定义没有任何字段的 struct,叫做 Unit-Like Struct。 适用于需要在某个类型上实现某个 trait,但是在里面又没有想要存储的数据。

struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
}

1.8、Struct 数据的所有权

Struct 里面如果不是存放的引用,那么该 struct 实例拥有其所有的数据,只要 struct 实例是有效的,那么里面的字段数据也是有效的;

struct 里面也可以存放引用,但这需要使用生命周期

  • 生命周期保证只要 struct 实例是有效的,那么里面的引用也是有效的;
  • 如果 struct 里面存储引用,而不是用生命周期,就会报错;
error[E0106]: missing lifetime specifier
 --> src/main.rs:3:13
  |
3 |   username: &str,
  |             ^ expected named lifetime parameter
  |
help: consider introducing a named lifetime parameter
  |
1 ~ struct User<'a> {
2 |   active: bool,
3 ~   username: &'a str,
  |

二、使用结构的例子

2.1、简单的长方形面积的例子

struct Rectangle{
  width: u32,
  length: u32,
}

fn main() {
  let rect = Rectangle{
    width: 30,
    length: 50,
  };
  println!("{}", area(&rect));
}

fn area(rect : & Rectangle) -> u32 {
  rect.width * rect.length
}

2.2、用 派生的Trait 添加功能

2.2.1、使用 println! 宏来打印结构信息

struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!("rect1 is {}", rect1);
}

此时结构体不能直接打印 结构体的内容。

error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`
  --> src/main.rs:12:27
   |
12 |   println!("rect1 is {}", rect1);
   |                           ^^^^^ `Rectangle` cannot be formatted with the default formatter
   |
   = help: the trait `std::fmt::Display` is not implemented for `Rectangle`
   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
   = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

根据提示我们用 {:?}{:#?} 格式来替代或者 实现 std::fmt::Display

struct Rectangle {
  width: u32,
  height: u32,
}

fn main() {
  let rect1 = Rectangle {
      width: 30,
      height: 50,
  };

  println!("rect1 is {:?}", rect1);
}

此时,还是会有如下的错误提示:

error[E0277]: `Rectangle` doesn't implement `Debug`
  --> src/main.rs:12:29
   |
12 |   println!("rect1 is {:?}", rect1);
   |                             ^^^^^ `Rectangle` cannot be formatted using `{:?}`
   |
   = help: the trait `Debug` is not implemented for `Rectangle`
   = note: add `#[derive(Debug)]` to `Rectangle` or manually `impl Debug for Rectangle`
   = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider annotating `Rectangle` with `#[derive(Debug)]`
   |
1  + #[derive(Debug)]
2  | struct Rectangle {

上述提示我们使用 #[derive(Debug)] 来派生或者手动实现 Debug。

#[allow(unused)] // 允许变量不使用 抑制警告
#[derive(Debug)]
struct Rectangle {
  width: u32,
  height: u32,
}

fn main() {
  let rect1 = Rectangle {
      width: 30,
      height: 50,
  };

  println!("rect1 is {:?}", rect1);
}

如果你的struct 很复杂,可以是用 {:#?} 可以格式化显示结构体

println!("rect1 is {:#?}", rect1);

2.2.2、使用 dbg! 宏来打印结构信息

我们使用 println! 宏来打印调试信息的时候,并不会打印文件名,所在行号,如果要调试的时候方便可以使用 dbg! 打印 结构体的信息,需要注意的是 该宏需要实现 Debug 接口。

#[allow(unused)]
#[derive(Debug)]
struct Rectangle {
  width: u32,
  height: u32,
}

fn main() {
  let scale = 2;
  let rect1 = Rectangle {
      width: dbg!(30 * scale),
      height: 50,
  };

  dbg!(&rect1);
}

三、 方法语法

3.1、结构普通方法

使用 impl 关键字可以为结构定义方法,方法的第一个参数是 &self 或者 self 相当于别的语言的 this 指针。

#[allow(unused)]
#[derive(Debug)]
struct Rectangle {
  width: u32,
  height: u32,
}

impl Rectangle{
  fn area(&self)-> u32 {
    self.width * self.height
  }
}

fn main() {
  let scale = 2;
  let rect1 = Rectangle {
      width: dbg!(30 * scale),
      height: 50,
  };

  dbg!(&rect1.area());
}

在调用方法时, Rust 会根据情况自动添加 & 、&mut 或者* ,以便 object 可以匹配方法的签名。

3.2、关联函数(Associated Functions)

例如我们可以在 impl 代码块里面定义不把 self 作为第一个参数的函数,它们叫做关联函数:例如 String:from();

#[allow(unused)]
#[derive(Debug)]
struct Rectangle {
  width: u32,
  height: u32,
}

impl Rectangle{
  fn area(&self)-> u32 {
    self.width * self.height
  }

  fn square(size: u32) -> Self{
    Self { 
      width: size, 
      height: size,
     }
  }
}

fn main() {
  let s = Rectangle::square(20);
  dbg!(s.area());
  dbg!(s);
}

3.3、Multiple impl Blocks

一个结构可以有多个 impl 块。

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
    }
}

总结

以上就是今天要讲的内容

  • 本章我们讲解了如何定义结构、如何使用结构、如何定义方法、关联函数等。
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小嘉丶学长

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值