The Rust Programming Language - 第5章 使用结构体组织相关数据

5 使用结构体组织相关数据

struct 是一个自定义的数据类型,允许你命名和包装多个相关的值,从而形成一个更有意义的组合

5.1 定义并实例化结构体

同元组一样,结构体中每个成员的数据类型可以是不同,但是结构体命名更加的清楚和明确,数据的名字和类型称为字段

结构体的定义:

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

结构体的实例化:

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

改变User 实例email字段值,注意;整个结构体必须是可变的

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

user1.email=String::from("anotheremail@example.com");

在函数中使用结构体

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

变量与字段同命时字段初始化简写语法

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

结构体更新

let user2 = User{
    active:user1.active,
    username:user1.username,
    sign_in_count:user1.sign_in_count,
    email:String::from("anotherone@example.com")
}//不使用更新语法
let user2 = User {
    email:String::from("anotherone@example.com"),
    ..user1
}//使用更新语法..user1

注意,解构体更新语法类似 = 的赋值,因为它移动了数据,这个概念我们在前面变量与数据的交换方式:移动和克隆中讲过,知识点有模糊的朋友看前面啊

元组结构体

元组结构体没有具体的字段名,只有字段的类型

定义元组结构体,如下结构体尽管字段类型相同,但是结构体类型业不同

struct Color (i32,i32,i32);
struct Point(i32,i32,i32);

let black = Color(0,0,0);
let origin = Point(0,0,0);

类单元结构体

类单元结构体中没有任何字段,类似于(),即“元组类型”一节 中提到的unit类型

类单元结构体常常在你想要某个类型上实现trait但不需要在类型中存储数据的时候发挥作用

struct AlwaysEqual;
let subject = AlwaysEqual;

结构体数据的所有权

我们现在使用了自身拥有所有权的String类型而不是&str字符串slice类型

5.2 一个使用结构体的示例程序

我们来写一个计算长方形面积的小程序

fn main() {
    let width1 = 30;
    let heigth1 = 50;

    println!("The area of the retangle is {} square pixels.",
        area(width1,heigth1)
    );
}
fn area (width:u32,heigth:u32) -> u32{
    width*heigth
}

长方形的长和宽是有关系的,我们来重构一下,我们将两个变量放入了一个元组

fn main() {
   let rect1 = (30,50);

   println!("The area if rectangle is {} square pixels",
        area(rect1)
    );
}
fn area (dimensions:(u32,u32)) -> u32{
    dimensions.0*dimensions.1
}

虽然使用元组可以将元素关联起来,但是我们仍然无法判断它们到底代表什么,我接下来用结构体重构,赋予他们更多意义

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

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

   println!("The area if rectangle is {} square pixels",
        area(&rect1)
    );
}
fn area (rectangle:&Rectangle) -> u32{
    rectangle.width*rectangle.height
}

参数形式的不同,处理其的函数形式也要跟着变化

通过派生trait来增加其他功能;增加属性来派生 Debug trait,并使用调试格式打印 Rectangle 实例

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

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

   println!("The rect1 is: {:?}",rect1);//Rectangle { width: 30, height: 50 }
}

另一种使用debug格式打印数值的方法是使用dbg!宏。dbg!宏接收一个表达式的所有权,打印出你代码中dbg!宏调用的文件或行号,以及该表达式的结果值,并返回该值所有权.调用dbg!宏会打印到标准错误控制台流(stderr)和(stdout)

#[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 trait,Rust还为我们提供了很多可以通过derive属性来使用的trait,他们可以为我们的自定义类型增加实用的行为

5.3 方法语法

area函数其实非常特殊,只能用来计算长方形的面积,让我们来重构代码,将area函数协调进Rectangle类型定义阿area方法中

方法与函数类似,使用fn关键字声明,可以拥有参数和返回值,同时包含在某处调用该方法时会执行的代码。不过方法和函数是不同的,方法在结构体上下文中被定义,第一个参数总是self,它代表调用该方法的结构体实例

定义方法

让我们把之前的area函数改为定义于Rectangle结构体上的area方法

#[derive(Debug)]
struct Rectangle {
    width: u32,
    heigth: u32,
}
impl Rectangle {// 我们开始了一个impl块,后面接块名称(Rectangle),因此块中所有内容都与Rectangle类型相关
    fn area(&self) -> u32 {//定义了一个方法,参数是&self(也可是self或者可变借用),代替了rectangle: &Rectangle
        self.width*self.heigth
    }
}
fn main() {
    let rect1 = Rectangle {width:30,heigth:50};

    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()
    );
}

通过使用self作为第一参数来让方法获取实例的所有权是非常少见的,这种做法一般是用方法将self转换成别的示例的时候,这个时候我们需要防止调用者在转换后仍然使用原始实例

使用方法的好处:我们可以将某个类型实例能做的事情都放入impl 块,而不是让将来的用户在我们的库中到处寻找Rectangle的功能

我们在某个类型的块中定义方法,这个方法名可以和类型中的字段相同,如下:

#[derive(Debug)]
struct Rectangle {
    width: u32,
    heigth: u32,
}
impl Rectangle {
    fn width(&self) -> bool {
        self.width>0
    }
}
fn main() {

    let rect1 = Rectangle {
        width:30,
        heigth:50,
    };

    if rect1.width() {
        println!("The retangle has a nonezero with; it is {}",rect1.width);
    }
}

运算符去哪里了

Rust 有一个叫 自动引用和解引用automatic referencing and dereferencing)的功能

工作原理:当使用 object.something() 调用方法时,Rust 会自动为 object 添加 &&mut* 以便使 object 与方法签名匹配

p1.distance(&p2);
(&p1).distance(&p2);

带有多个参数的方法

#[derive(Debug)]

struct Rectangle {
    width: u32,
    height: u32,
}
impl Rectangle {
    fn area(&self)-> u32 {
        self.width*self.height
    }
    fn can_hold(&self,other:&Rectangle)->bool {
        self.width>other.width && self.height>other.height
    }    
}
fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    let rect2 = Rectangle { width: 10, height: 40 };
    let rect3 = Rectangle { width: 60, height: 45 };

    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}

关联函数

在impl块中定义的函数称为关联函数,因为它们与impl后面的类型相关。String::from函数就是在String类型上定义的函数

关联函数经常被用作返回一个结构体新实例的构造函数

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

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

虽然这里没必要编写多个impl语法,但这是有效的语法

总结:结构体让我们可以创建出在我们的领域中有意义的自定义类型。通过结构体,我们可以将相关联的数据片段联系起来并命名它们,这样可以使得代码更加清晰。在 impl 块中,我们可以定义与我们的类型相关联的函数,而方法是一种相关联的函数,让我们指定结构体的实例所具有的行为

但结构体并不是创建自定义类型的唯一方法,下一章,我们将会了解枚举的功能

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值