04.结构体

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

一、结构体的定义和实例化

结构体于元素类型类似,它们都包含多个相关的值。和元组一样,结构体的每一部分可以是不同类型。但不同于元组,结构体需要命名各部门数据以便能清楚的表明其值的意义,因此结构体比元组更灵活:不需要以来顺序来指定或访问实例中的值
定义结构体,需要使用struct关键字并为整个结构体提供一个名字(结构体的名字需要描述它所组合的数据意义)。在大括号中,定义每一部分数据的名字和类型,我们称为字段(field)。

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

一旦定义了结构体后,为了使用它,通过为每个字段指定具体值来创建这个结构体的实例。创建一个实例需要以结构体的名字开头,接着在打括号中使用key:value(键-值对)的形式提供字段,其中key是字段的名字,value是需要存储在字段中的数据值。

fn main() {
    let user1 = User{
        email: String::from("111@qq.com"),
        username: String::from("time"),
        active: true,
        sign_in_count: 1,
    };
}

为了从结构体中获取某个特定的值,可以使用点号。举个例子,想要用户的邮箱地址,可以用 user1.email。如果结构体的实例是可变的,我们可以使用点号并为对应的字段赋值。

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

fn main() {
    let mut user1 = User{
        email: String::from("111@qq.com"),
        username: String::from("time"),
        active: true,
        sign_in_count: 1,
    };

    println!{"{}", user1.email};

    user1.email = String::from("anotheremail@example.com");
    println!{"{}", user1.email};
}

注意整个实例必须是可变的;Rust不允许只将某个字段标记为可变。另外需要注意同其他任何表达式一样,我们可以在函数体的最后一个表达式中构造一个结构体的新实例,来隐式地返回这个实例。
build_user函数返回一个带有给定的email和用户名的User结构体实例。

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

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

fn main() {
    let user1 = build_user(
        String::from("someone@example.com"),
        String::from("someusername123"),
    );
}

1、结构体更新

Rust通过结构体更新语法来实现,使用旧实例的大部分值但改变其部分值来构建一个新的结构体实例。
首先,如下展示了不使用更新语法时,如何在 user2 中创建一个新 User 实例。我们为 email 设置了新的值,其他值则使用了实例 5-2 中创建的 user1 中的同名值:

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

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

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

使用结构体更新语法,我们可以通过更少的代码来达到相同的效果。

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

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

    let user2 = User {
        email: String::from("another@example.com"),
        ..user1
    };
}

请注意,结构更新语法就像带有=的赋值,因为它移动了数据。

2、元组结构体

元组结构体有着结构体名称提供的含义,但没有具体的字段名,只有字段的类型。
要定义元组结构体,以struct关键字和结构体名开头并后跟元组中的类型。

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

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}

注意blackorigin值得类型不同,因为它们是不同的元组结构体的实例。

3、类单元结构体

类单元结构体(Unit-Like Struct)是没有任何字段的结构体,它是在某个类型上实现trait但不需要在类中存储数据的时候发挥作用。

struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
}

二、通过派生trait增加使用功能

当我们想要使用Debug的输出格式时,可以在{}中加入:?从而告诉println!我们将输出DebugDebug 是一个 trait,它允许我们以一种对开发者有帮助的方式打印结构体,以便当我们调试代码时能看到它的值

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

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };
    println!("rect1 is {:?}", rect1);
}

但是我还需要在Rust结构体定义之前加上外部属性#[derive(Debug)]从而外部现实调试信息。

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

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };
    println!("rect1 is {:?}", rect1);
}


当我们想要更为易懂的代码时,可以将{:?}替换为{:#?}

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

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };
    println!("rect1 is {:#?}", rect1);
}

三、方法/语法

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

1、定义方法

#[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的上下文中,我们开始了一个impl块,这个impl块中的所有内容都将与Rectangle类型关联。接着将 area 函数移动到 impl 大括号中,并将签名中的第一个(在这里也是唯一一个)参数和函数体中其他地方的对应参数改成 self。然后在 main 中将我们先前调用 area 方法并传递 rect1 作为参数的地方,改成使用 方法语法method syntax)在 Rectangle 实例上调用 area 方法。方法语法获取一个实例并加上一个点号,后跟方法名、圆括号以及任何参数。
在 area 的签名中,使用 &self 来替代 rectangle: &Rectangle&self 实际上是 self: &Self 的缩写。在一个 impl 块中,Self 类型是 impl 块的类型的别名。方法的第一个参数必须有一个名为 self 的Self 类型的参数,所以 Rust 让你在第一个参数位置上只用 self 这个名字来缩写。注意,我们仍然需要在 self 前面使用 & 来表示这个方法借用了 Self 实例,就像我们在 rectangle: &Rectangle 中做的那样。方法可以选择获得 self 的所有权,或者像我们这里一样不可变地借用 self,或者可变地借用 self,就跟其他参数一样。

2、关联函数

所有在impl块中定义的函数被称为关联函数,因为它们与impl后面命名的类型相关。

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

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

fn main() {
    let sq = Rectangle::square(3);
}

使用结构体名和 :: 语法来调用这个关联函数:比如 let sq = Rectangle::square(3);。这个函数位于结构体的命名空间中::: 语法用于关联函数和模块创建的命名空间。

posted @ 2022-11-21 16:11  Diligent_Maple  阅读(89)  评论(0编辑  收藏  举报