• Rust中的结构体


    专栏简介:本专栏作为Rust语言的入门级的文章,目的是为了分享关于Rust语言的编程技巧和知识。对于Rust语言,虽然历史没有C++、和python历史悠远,但是它的优点可以说是非常的多,既继承了C++运行速度,还拥有了Java的内存管理,就我个人来说,还有一个优点就是集成化的编译工具cargo,语句风格和C++极其相似,所以说我本人还是比较喜欢这个语言,特此建立这个专栏,作为学习的记录分享。

    日常分享:每天努力一点,不为别的,只是为了日后,能够多一些选择,选择舒心的日子,选择自己喜欢的人!


    结构体的定义和实例化

    和c++/c语言一样,Rust语言也有结构体,和元组一样,结构体中的变量数据类型是可以不同的,而且只需要引用符就可以得到其中的数据。

    1. struct Buffer{
    2. length:usize,
    3. width:usize,
    4. flag:bool,
    5. name:String,
    6. }
    7. fn main()
    8. {
    9. let mut buffer = Buffer{
    10. length:90,
    11. width:100,
    12. flag:true,
    13. name:String::from("test"),
    14. };
    15. println!("{}", buffer.length);
    16. }

    在数据结构中,c/c++语言均可以用结构体数据类型定义函数,作为函数的返回值类型,在Rust中,也可以作为函数的返回类型。

    1. fn return_Buffer(length:usize,flag:bool)->Buffer {
    2. Buffer{
    3. length:length,
    4. width:100,
    5. flag:flag,
    6. name:String::from("test"),
    7. }
    8. }

     这里的函数就是返回结构体类型,但是重复写了length,flag等字段,可以采用字段初始化简写语法,也就是不用写具体的变量值,只需要写字段名,但前提是参数和字段名是一样的。

    1. fn return_Buffer(length:usize,flag:bool)->Buffer {
    2. Buffer{
    3. length,
    4. width:100,
    5. flag,
    6. name:String::from("test"),
    7. }
    8. }

    结构体更新语法

    1. struct Buffer{
    2. length:usize,
    3. width:usize,
    4. flag:bool,
    5. name:String,
    6. }
    7. fn main()
    8. {
    9. let mut buffer = Buffer{
    10. length:90,
    11. width:100,
    12. flag:true,
    13. name:String::from("test"),
    14. };
    15. let mut buffer_two=Buffer{
    16. length:buffer.length,
    17. width:50,
    18. flag:false,
    19. name:buffer.name,
    20. };
    21. let mut buffer_three = Buffer{
    22. flag:true,
    23. ..buffer_two
    24. };
    25. println!("{}", buffer.length);
    26. }
    27. fn return_Buffer(length:usize,flag:bool)->Buffer {
    28. Buffer{
    29. length,
    30. width:100,
    31. flag,
    32. name:String::from("test"),
    33. }
    34. }

    上面这段例子就是指的是 利用其他的结构体变量来创建一个新的结构体变量。虽然代码看起来比较简单,但是这其中却有很多的知识。比如,在Buffer结构体中定义了一个String数据类型的变量,那么我们在利用buffer创建buffer_two后,buffer就失去了作用,这一点在前面的数据类型章节中已经讲过了。同样的道理,在创建buffer_three变量的时候,我们先是定义了flag字段的值,然后剩于字段全部使用buffer_two变量,由于也使用了name字段,所以buffer_two变量失去作用,但是如果只是使用其他的字段,则依然有效。如下:

    1. let mut buffer_three = Buffer{
    2. name:String::from("alice"),
    3. ..buffer_two
    4. };
    5. println!("{}",buffer_two.name);

    这里声明一下,虽然buffer已经丧失作用,这只是代表这个变量不再有作用,但是他当中的非string类型字段还有作用,因为他们本身就已经是变量了。

    使用没有命名字段的元组结构体来创建不同的类型

    元组结构体,利用元组来对结构体进行定义,或者说是以元组的方式来进行定义结构体。

    1. struct Bufferlines(i32,i32,i32);
    2. struct Bufferline(u32,u32,u32);
    3. let mut buffer_fine=Bufferline(32,34,45);
    4. let mut buffer_fine_two = Bufferlines(32,34,45);
    5. println!("{}",buffer_fine.1);

    没有任何字段的类单元结构体

    在Rust语言中,有一种结构体叫做类单元结构体,其实这个大概意思就是说定义一个结构体,但是不包含任何的数据类型,也就相当于一个空壳。

    1. struct Bufferclong; //定义一个类单元结构体
    2. let mut buffer_four=Bufferclong; //定义一个类单元结构体变量。

    结构体示例程序

    结构体的使用在很多的方面都可以使用,在c语言中的数据结构的学习中就用到了结构体,结构体的实用性在于它可以存储很多数据类型的变量,使得代码简介,增强代码的完整性。

    1. //定义一个结构体
    2. struct Rectangle
    3. {
    4. width: usize,
    5. height: usize,
    6. }
    7. fn main(){
    8. let mut rect = Rectangle{
    9. width: 30,
    10. height: 50,
    11. };
    12. println!("The area is {}",area(&rect));
    13. }
    14. fn area(rect: &Rectangle) ->usize {
    15. return rect.width * rect.height; //如果不使用return关键字,则不需要分号
    16. }

    这里我们定义了一个结构体,定义了width和height两个字段 ,类型定义的时usize(无论是32位的还是64位的电脑都可以跑),然后就是创建了一个实体,并对字段赋值。然后调用已经定义好的求面积函数。在这里要特别说明一下,定义函数的时候,参数是结构体的引用类型了,这个原因和前面讲解的所有权属性有关。如果我们不使用引用,则在传参后原本定义的结构体实例对象就无法再使用了。所以这里使用引用也是为了后面可以继续使用该对象。

    通过派生trait增加实用功能

    我们在使用println!()宏的时候,虽然可以输出绝大多数的数据内容,但是却无法直接输出结构体实体对象。那么有什么方法嘛?

    我们先来看一下使用println!()会报什么错误。

    `Rectangle` doesn't implement `std::fmt::Display`
      --> src/main.rs:14:17
       |
    14 |   println!("{}",rect);
       |                 ^^^^ `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)

    上面的这段报错,我们可以看到报错给出了解决方法,我们尝试使用报错给出的解决方法试试:

    println!("{:#?}",rect);
       |                    ^^^^ `Rectangle` cannot be formatted using `{:?}`

    我们发现还是报错,不过报错给出了一个信息,增加一个Debug。

    1. //定义一个结构体
    2. #[derive(Debug)]
    3. struct Rectangle
    4. {
    5. width: usize,
    6. height: usize,
    7. }
    8. fn main(){
    9. let mut rect = Rectangle{
    10. width: 30,
    11. height: 50,
    12. };
    13. println!("The area is {}",area(&rect));
    14. println!("{:#?}",rect);
    15. }
    16. fn area(rect: &Rectangle) ->usize {
    17. return rect.width * rect.height; //如果不使用return关键字,则不需要分号
    18. }

    通过 上面增加的显示代码,我们已经可以打印出结构体了。那么还有没有其他的简单的方法?

    dbg!宏

    dbg!宏和println!宏有点不一样的是,dbg!宏接受的是表达式的所有权,而println!接受的是引用。这个如果不理解的话,下面看一个例子。

    1. //定义一个结构体
    2. #[derive(Debug)]
    3. struct Rectangle
    4. {
    5. width: usize,
    6. height: usize,
    7. }
    8. fn main(){
    9. let mut rect = Rectangle{
    10. width: 30,
    11. height: 50,
    12. };
    13. println!("The area is {}",area(&rect));
    14. println!("{:#?}",rect); //第一次打印
    15. println!("{:?}",rect);//第二次使用println!打印
    16. //使用dbg!打印
    17. dbg!(rect); //第一次打印
    18. dbg!(rect);//第二次打印,此时运行错误,rect已经便不再具有所有权。
    19. }
    20. fn area(rect: &Rectangle) ->usize {
    21. return rect.width * rect.height; //如果不使用return关键字,则不需要分号
    22. }

    上面使用println!宏可以连续打印,但是使用dbg!宏只能打印一次,此后这个变量就不再具有所有权。所有权已经转移。所以运行上述代码就会出现报错。

    let mut rect = Rectangle{
       |       -------- move occurs because `rect` has type `Rectangle`, which does not implement the `Copy` trait
    ...
    18 |   dbg!(rect); //第一次打印
       |   ---------- value moved here
    19 |   dbg!(rect);//第二次打印,此时运行错误,rect已经便不再具有所有权。
       |        ^^^^ value used here after move

     如果不想让dbg!获取表达式的所有权,那么就在调用dbg!的时候传递引用参数。诸如dbg!(&rect);

    impl语法

    前面我们定义的area函数,如果需要使用则需要进行传参,重要的是,我们定义的很多函数可能不会调用结构体变量,但是我们在与结构体有关的函数时,就会显得非常麻烦。为了解决这种麻烦,Rust推出了impl语法。

    定义方法

    我们先来看一个实例:

    1. //定义一个结构体
    2. #[derive(Debug)]
    3. struct Rectangle
    4. {
    5. width: usize,
    6. height: usize,
    7. }
    8. impl Rectangle {
    9. fn area(&self) ->usize {
    10. return self.width * self.height; //如果不使用return关键字,则不需要分号
    11. }
    12. }
    13. fn main(){
    14. let mut rect = Rectangle{
    15. width: 30,
    16. height: 50,
    17. };
    18. println!("{}", rect.area());
    19. }

     上面的代码中,我们用impl定义了一个快,这个块中的所有内容都将与Rectangle类型相关联。参数使用的是&self,这是用来替代rectangle:&Rectangle。这就表示我们可以使用结构体实例中的属性。每个与结构体关联的函数,第一个参数都必须是&self,后面的参数根据自己需要来添加。

    Rust中的自动引用和解引用:

    C/C++中有解引用和引用,这两个概念相信很多熟悉c++的小伙伴都知道这个概念,这里就不过多介绍了。

    Rust中取消了这种复杂的机制,也就是说我们不需要考虑是否需要解引用,直接用属性操作符就可以了。

     多个参数

    1. //定义一个结构体
    2. #[derive(Debug)]
    3. struct Rectangle
    4. {
    5. width: usize,
    6. height: usize,
    7. }
    8. impl Rectangle {
    9. fn area(&self) ->usize {
    10. return self.width * self.height; //如果不使用return关键字,则不需要分号
    11. }
    12. fn hold(&self,other: &Rectangle) -> bool {
    13. self.width > other.width && self.height > other.height
    14. }
    15. }
    16. fn main(){
    17. let mut rect = Rectangle{
    18. width: 30,
    19. height: 50,
    20. };
    21. let mut rect2 = Rectangle{
    22. width:40,
    23. height:50,
    24. };
    25. let mut rect3 = Rectangle{
    26. width:20,
    27. height:30,
    28. };
    29. println!("{}", rect.hold(&rect2));
    30. println!("{}", rect2.hold(&rect3));
    31. }

    上面这段代码可能很多人没看懂是什么意思,其实简单的看就是,在impl块中又定义了一个比较长宽大小的函数,而比较明显的是,多了一个参数(other:&Rectangle),这是表示我们这里使用另一个Rectangle实例,不再是使用一个结构体实例。那么如果我们要多几个不是结构体实例的参数又该如何?

    1. fn get_v(&self,length:usize) -> usize {
    2. self.width * self.height*length
    3. }

    在impl块中增加上述代码,便可以得到长方体的体积,这里新增加的一个参数,使用的是usize类型。为什么不使用other喃?

    impl块中函数的多个参数的设定规则:

    1、如果要使用多个参数,那么第一个参数必须是&self。

    2、如果要使用统一个结构体类型的不同实例,则需要添加other来区分。

    3、如果只是增加普通的参数,则直接在后面添加即可。

    关联函数

    在官方的定义中,所有在impl块中的函数均被称作关联函数,我们前面说,如果要定义一个结构体的关联函数,那么第一个参数必须是&self,但是我们也可以不使用&self作为第一个参数,但是这就不再算是方法,并不能作用于结构体的实例。

    不是方法的关联函数经常被用作返回一个结构体新实例的构造函数。这些函数的名称通常为 new ,但 new 并不是一个关键字。

    例如:

    1. impl Rectangle{
    2. fn new(size:usize,length:usize)->Self{
    3. Self{
    4. width:size,
    5. height:length,
    6. }
    7. }
    8. }

    然后在主函数中调用:

    let mut rects = Rectangle::new(34,45);

    println!("{}", rects.area());

    这里的调用方式不再是使用实例进行调用,而是使用::进行调用,这个是由于该函数只是被建立在结构体的命名空间中,而未获得实例,所以需要使用::进行调用,后面会细讲。

    多个impl块

    在Rust语言中,一个结构体是允许存在多个impl块的,就像上述的代码一样,关于这个性能,后面需要的时候再慢慢讲解,初学者只需要指导就好,下面是本节内容的所有代码,如果相对本节知识有所了解的话,可以仔细看一下。

    1. //定义一个结构体
    2. #[derive(Debug)]
    3. struct Rectangle
    4. {
    5. width: usize,
    6. height: usize,
    7. }
    8. impl Rectangle {
    9. fn area(&self) ->usize {
    10. return self.width * self.height; //如果不使用return关键字,则不需要分号
    11. }
    12. fn get_v(&self,length:usize) -> usize {
    13. self.width * self.height*length
    14. }
    15. fn hold(&self,other: &Rectangle) -> bool {
    16. self.width > other.width && self.height > other.height
    17. }
    18. }
    19. impl Rectangle{
    20. fn new(size:usize,length:usize)->Self{ //相当于是结构体的构造函数
    21. Self{
    22. width:size,
    23. height:length,
    24. }
    25. }
    26. }
    27. fn main(){
    28. let mut rects = Rectangle::new(34,45);
    29. println!("{}", rects.area());
    30. let mut rect = Rectangle{
    31. width: 30,
    32. height: 50,
    33. };
    34. let mut rect2 = Rectangle{
    35. width:40,
    36. height:50,
    37. };
    38. let mut rect3 = Rectangle{
    39. width:20,
    40. height:30,
    41. };
    42. println!("{}", rect.hold(&rect2));
    43. println!("{}", rect2.hold(&rect3));
    44. println!("The rect2 体积是:{}",rect2.get_v(20));
    45. }

    总结

    本节的内容主要就是讲解结构体中的相关知识,通过结构体,我们可以将相关联的数据片段联系起来并命名它们,这样可以使得代码更加清晰。在 impl 块中,你可以定义与你的类型相关联的函数,而方法是一种相关联的函数,让你指定结构体的实例所具有的行为。

     

  • 相关阅读:
    硅芯思见:【145】问芯吾跬(IC设计验证常见问题汇总三)
    【wp】2023第七届HECTF信息安全挑战赛 Web
    信钰证券:股票抵押是好还是坏?
    (不是Al创作助手、AiLink、开放猫、AiTab新标签、Official Index)分享好用的ChatGPT
    ScrollView的OnValueChanged
    竞赛 题目:基于python的验证码识别 - 机器视觉 验证码识别
    字节前端面试被问到的react问题
    1-07 React配置postcss-px-to-viewport
    java计算机毕业设计ssm养老院敬老院系统 elementui
    java常用集合及常用类
  • 原文地址:https://blog.csdn.net/qq_59931372/article/details/133135998