【Rust】自定义数据类型—结构体——Rust语言基础13

1. 结构体——struct

结构体是包含一组不同类型元素的集合,这一点和之前介绍的元组 (Tuple) 类型很像,不过不同于元组的是,在元组中的每个元素只需表明其元素类型即可不需要为其命名,而在结构体中则需要明确的为每个元素命名并表明其数据类型。

不过也因此结构体变得更加灵活,可以直接使用名字来访问结构体中指定的成员,并不像 Tuple 中需要依赖循序来访问成员。

2. 定义并初始化一个结构体

2.1. 定义一个结构体

这里我们定义了一个学生信息的结构体:

    struct Student {
    
    
        name: String,
        class: String,
        score:  u64,
        pass:   bool,
    }

2.2. 定义变量时实例化结构体

下面来看如何实例化一个结构体:

    let lacia = Student {
    
    
        name: String::from("Lacia"),
        class: String::from("10-01"),
        pass: true,
        score:  89,
    };

通过为结构体中每个字段绑定具体值来创建并实例化一个结构体变量。创建一个结构体需要以结构体的名字开头,然后接着在大括号中用 key : value 的方式来为每个字段绑定具体的值。在实例化的过程中并不要求顺序。

2.3. 用函数的方式初始化结构体

还可以通过构建一个函数来实现结构体的实例化:

struct Student {
    
    
    name: String,
    class: String,
    score:  u64,
    pass:   bool,
}


fn main() {
    
    


    let mut lacia = Student {
    
    
        name: String::from("Lacia"),
        class: String::from("10-01"),
        pass: true,
        score:  89,
    };


    lacia.class = String::from("10-03");
    println!("Student:\nName: {}\nClass: {}", lacia.name, lacia.class);


    let mut yinoli = build_student(String::from("YiNoLi"), String::from("10-03"), 86);

    println!("Student:\nName: {}\nClass: {}", yinoli.name, yinoli.class);


}

fn build_student(name: String, class: String, score: u64) -> Student {
    
    

    Student {
    
    
        name,
        class,
        score,
        pass: {
    
    
            if score >= 60 {
    
     true }
            else {
    
     false }
        },
    }
}

可以将一个函数体的最后部分构造一个结构体的实例,可以隐式的返回该实例。若函数中的参数名与结构体成员名相同则可以简化写法,无需重复的写 name: name 这样,而是可以简单的直接写 name 即可。

2.4. 利用已有结构体构造其它

在构造结构体时,经常会遇到这种情况,创建的结构体的大部分信息与之前的相同,将会直接用之前结构体中的实例计算出的结果来初始化新的结构体:

    let mut yinoli = build_student(String::from("YiNoLi"), String::from("10-03"), 86);

    println!("Student:\nName: {}\nClass: {}", yinoli.name, yinoli.class);

    let mut misaka = Student {
    
    
        name: String::from("MiSaKaMiKoTo"),
        class: yinoli.class,
        score: yinoli.score,
        pass:  yinoli.pass,
    };

    println!("Student:\nName: {}\nClass: {}", misaka.name, misaka.class);

Rust 对这种情况提供了我们更加方便的初始化方式:

    let mut yinoli = build_student(String::from("YiNoLi"), String::from("10-03"), 86);

    println!("Student:\nName: {}\nClass: {}", yinoli.name, yinoli.class);

    let mut misaka = Student {
    
    
        name: String::from("MiSaKaMiKoTo"),
        ..yinoli
    };

    println!("Student:\nName: {}\nClass: {}", misaka.name, misaka.class);

.. 语法表示结构体中剩余的成员全部用 .. 后的实例中的值初始化。上述两段代码效果相同。

3. 元组结构体

在此前我们已经接触过元组类型 Tuple,笔者将之前文章中的示例截图过来,看到应该很快就回忆起来了。
在这里插入图片描述
看起来元组有一点类似结构体,但这是不同的,在本文开始就已经说明了,因此结构体支持定义为元组类型的结构体,又名元组结构体(Tuple Structs)。其结合了元组类型的特点,即结构体中的各字段没有具体的名字,只有类型。

为什么要这么做呢??似乎是多此一举的动作但其有大用处。当遇到描述两种类型完全相同的事物,但不可将其混为一谈,即描述的数据类型都相同,但绝非等价关系或相互赋值和运算的关系。

如,我们需要描述人类、汽车、树木(仅限乔木)这三个分类,且只关心这几个分类中每个的信息如下:

  • 人类: (1) 年龄,(2) 身高,(3) 体重;
  • 汽车: (1) 最高马力,(2) 重量,(3) 价格;
  • 树木: (1) 年龄,(2) 胸径,(3) 高度

可以看出来这几个描述信息完全可以全部使用 u32 类型:

struct Human(u32, u32, u32);
struct Car(u32, u32, u32);
struct Tree(u32, u32, u32);

fn main() {
    
    
    let bizhe = Human(123, 176, 61);
    let porsche_tancan = Car(321, 2100, 86700);
    let wu_tong = Tree(12, 38, 430);

    println!("BiZhe: {} age, {} cm, {} kg", bizhe.0, bizhe.1, bizhe.2);
    println!("Porsche_Tancan: {} hp, {} kg, {} $", porsche_tancan.0, porsche_tancan.1, porsche_tancan.2);
    println!("WuTong: {} age, {} cm, {} cm", wu_tong.0, wu_tong.1, wu_tong.2);
}

执行结果如下:

imaginemiracle@im-Linux:tuple$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/tuple`
BiZhe: 123 age, 176 cm, 61 kg
Porsche_Tancan: 321 hp, 2100 kg, 86700 $
WuTong: 12 age, 38 cm, 430 cm

4. 结构体的使用

4.1. 访问结构体的成员

可以通过结构体加 . 的形式来直接访问指定的结构体中的成员。

    let lacia = Student {
    
    
        name: String::from("Lacia"),
        class: String::from("10-01"),
        pass: true,
        score:  89,
    };


    println!("Student:\nName: {}\nClass: {}", lacia.name, lacia.class);

当需要修改结构体中的某个成员可用同样的方式。

    let mut lacia = Student {
    
    
        name: String::from("Lacia"),
        class: String::from("10-01"),
        pass: true,
        score:  89,
    };

    lacia.class = String::from("10-03");
    println!("Student:\nName: {}\nClass: {}", lacia.name, lacia.class);

[注]:结构体不允许 mut 只修饰其中某个字段,即不可将结构体与其成员视为不同变量。

4.2. 打印结构体信息

上文中均使用访问结构体中的字段将其逐个打印出来,有没有办法直接一次性输出结构体信息的呢?没错,是有的,Rust 提供了 Debug 模式的调试打印方法,来看看如何使用。

#[derive(Debug)]

struct Student {
    
    
    name: String,
    class: String,
    score:  u64,
    pass:   bool,
}


fn main() {
    
    


    let mut lacia = Student {
    
    
        name: String::from("Lacia"),
        class: String::from("10-01"),
        pass: true,
        score:  89,
    };


    lacia.class = String::from("10-03");
    println!("Student:\nName: {}\nClass: {}", lacia.name, lacia.class);


    let mut yinoli = build_student(String::from("YiNoLi"), String::from("10-03"), 86);

    println!("Student:\nName: {}\nClass: {}", yinoli.name, yinoli.class);

    let mut misaka = Student {
    
    
        name: String::from("MiSaKaMiKoTo"),
        ..yinoli
    };

    println!("Student:\nName: {}\nClass: {}", misaka.name, misaka.class);
    println!("Student:\n {:?}", misaka);
}

fn build_student(name: String, class: String, score: u64) -> Student {
    
    

    Student {
    
    
        name,
        class,
        score,
        pass: {
    
    
            if score >= 60 {
    
     true }
            else {
    
     false }
        },
    }
}

这段代码和之前的基本一样,不同的是,在代码开始的地方添加了 #[derive(Debug)],表示引入调试方法,另外还添加了一行输出代码:

println!("Student:\n {:?}", misaka);

可以看到这里的 { } 和之前的不一样了,中间添加了 :?,这个符号告诉 println! 将使用 Debug 的输出格式,来看看输出的效果:

imaginemiracle@im-Linux:structs$ cargo run
warning: `structs` (bin "structs") generated 3 warnings
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/structs`
Student:
Name: Lacia
Class: 10-03
Student:
Name: YiNoLi
Class: 10-03
Student:
Name: MiSaKaMiKoTo
Class: 10-03
Student:
 Student {
    
     name: "MiSaKaMiKoTo", class: "10-03", score: 86, pass: true }

还有另一个符号也表示 Debug 模式输出 :#?,即将 { } 中的符号填充为 :#?,看看这样的输出效果:

imaginemiracle@im-Linux:structs$ cargo run
   Compiling structs v0.1.0 
warning: `structs` (bin "structs") generated 3 warnings
    Finished dev [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/structs`
Student:
Name: Lacia
Class: 10-03
Student:
Name: YiNoLi
Class: 10-03
Student:
Name: MiSaKaMiKoTo
Class: 10-03
Student:
 Student {
    
    
    name: "MiSaKaMiKoTo",
    class: "10-03",
    score: 86,
    pass: true,
}

在之后我们还可以自己实现类似这样的调试方法。

4.3. 方法语法(Method Syntax)

Rust 中对于方法和函数是两个不同的概念,方法和函数同样都是使用 fn 来修饰声明的,同时可以拥有参数返回值。但与函数不同的是,方法是特指在特定结构的上下文中定义的,且方法中的第一个参数是 self,表示当前的结构对象。

来看一个定义方法的例子:

struct Student {
    
    
    name: String,
    age: u32,
    height: u32,
    score: u32,
}

impl Student {
    
    
    fn pass(&self) -> bool {
    
    
        if self.score >= 60 {
    
     true }
        else {
    
     false }
    }
}

fn main() {
    
    
    let li_bai = Student {
    
    
        name: String::from("LiBai"),
        age: 18,
        height: 73,
        score: 86,
    };

    if li_bai.pass() {
    
    
        println!("{} is pass.", li_bai.name);
    } else {
    
    
        println!("{} is no pass.", li_bai.name);
    }
}

可以看到与之前的函数定义方法不同,这里使用 impl 关键字来修饰(implementation 的缩写),并在其后跟上结构的名称,然后在 impl 大括号内则基本与常规函数定义相同,但函数的第一个参数一定是 self,这里使用的是 &self 引用,即不拥有 self 的所有权。由于方法被定义在指定的结构对象中,因此第一个变量默认为该结构的对象本身,就可以直接通过 self 调用访问结构内部成员而无需传参。

来看上述代码的执行效果:

imaginemiracle@im-Linux:method$ cargo run
warning: `method` (bin "method") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/method`
LiBai is pass.

4.4. 关联函数

impl 定义的结构块还有另一个重要的功能,其中定义的函数允许不以 self 作为第一个参数,而使用其它参数或者没有参数,这类函数则被称为关联函数 (Associated Functions),需要注意的是这样的定义则被称为函数不是方法了,因为它并不只作用于一个结构体对象。我们来改造一下上文代码,看看有什么不同:

struct Student {
    
    
    name: String,
    age: u32,
    height: u32,
    score: u32,
}

impl Student {
    
    
    fn new(name: String, age: u32, height: u32, score: u32) -> Student {
    
    
        Student {
    
    
            name,
            age,
            height,
            score,
        }
    }
}

fn main() {
    
    
    let li_bai = Student::new(String::from("LiBai"), 18, 73, 86);

    if li_bai.score >= 60 {
    
    
        println!("{} is pass.", li_bai.name);
    } else {
    
    
        println!("{} is no pass.", li_bai.name);
    }
}

这里已经看到代码和之前的有所改变了,尤其是在调用时,这里使用的是 :: 来调用关联函数,而关联函数的第一个参数也不再是 self,而是其它参数,此关联函数的作用是来构造一个结构体变量并返回。这里代码的运行效果和上一个代码相同就不在此展示。

每个结构体理所应当的可以拥有多个 impl 块。Ok~ 看到这里相信你已经学会了如何在 Rust 中使用结构体,至少基本的使用方法已经有所掌握。


Boys and Girls!!!
准备好了吗?下一节我们要还有一个小练习要做哦!

不!我还没准备好,让我先回顾一下之前的。
上一篇《【Rust】引用和借用,字符串切片 (slice) 类型 (&str)——Rust语言基础12》

我准备好了,掛かって来い(放马过来)!
下一篇《Rust语言基础14》


觉得这篇文章对你有帮助的话,就留下一个赞吧v*
请尊重作者,转载还请注明出处!感谢配合~
[作者]: Imagine Miracle
[版权]: 本作品采用知识共享署名-非商业性-相同方式共享 4.0 国际许可协议进行许可。
[本文链接]: https://blog.csdn.net/qq_36393978/article/details/126720509

猜你喜欢

转载自blog.csdn.net/qq_36393978/article/details/126720509