Rust的结构体类型的使用

结构体的创建

创建结构体

创建一个最简单的结构体

struct info{
    name:String,
    age:u32,
    address:String
}

要点:

  • 使用struct类型名+结构体名称创建结构体。
  • 结构体中包含字段变量与数据类型说明,必须要以:分隔
  • 每一个字段之间要用,分隔,注意不要写成了 ;

结构体的对象创建:

let stu1=info{
        name:String::from("ylh"),
        age:20,
        address:String::from("China")
    };

我们创建了一个不可变的结构体变量,叫做stu1,并且给他传递了基本信息。

要点:

  • 可以规定此变量是只读还是可写可以使用mut。
  • 注意指明你创建的对象是哪一个结构体? 使用info结构体花括号来包含起来。
  • 数据字段进行初始化时也要加上 :之后赋予初始值String类型则使用from赋予初始字面值常量。
  • 在结构体每个字段变量中我们无法使用mut关键字。

访问结构体变量:

println!("name:{},age:{},address:{}",stu1.name,stu1.age,stu1.address);

在这里插入图片描述
注意:我们使用 . 点运算符来访问每一个属性字段。

改变结构体变量的属性字段值:

 let mut stu1=info{
     name:String::from("ylh"),
     age:20,
     address:String::from("China")
 };
 stu1.age=33;
 stu1.name=String::from("hhhhh");
 
 println!("name:{},age:{},address:{}",stu1.name,stu1.age,stu1.address);

在这里插入图片描述

当然我们也可以通过函数来修改变量内容

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

此函数通过接受两个String的值,来修改User结构体的变量,并且返回一个结构体。
注意:如果函数形参和实际结构体变量字段名字相同,则无需使用 : ,直接写上函数的形参名(或者你认为是结构体的字段名)即可。

创建结构体变量初始化赋值时字段顺序随便!!!

结构体的更新

使用结构体更新,从其他结构体实例创建一个新的实例

let stu2=info{
        name:stu1.name, 
        address:stu1.address,
        age:55
    };

根据结构体stu1的实例属性来创建另一个属性,同样,顺序无关紧要。

使用 ..实例名.来实例属性:

let stu3=info{
        age:88,
        ..stu2
    };

把刚刚初始化的stu2再次作为实例更新 ,注意: …stu2 一定要在最后面定义,否则编译器无法知道你要修改哪一个变量。

注意!!!!
实例化更新会产生所有权的更换
stu2借用了stu1 :导致stu1的两个String类型变量借用到了stu2里面,成为了stu2的属性,此时如果访问stu1的String对象会出错:(被借用的所有权产生了移动
在这里插入图片描述
但是,u32类型的变量却可以访问,因为对于整数的借用,不会产生所有权的更换。这涉及到深浅拷贝的问题。

不明白可以看我这篇博文:
Rust的所有权与引用详解

同样,实例化stu3,同样stu2会产生所有权更换,导致无法访问。

结构体使用示例

矩形求体积

我们创建一个矩形结构体用来求面积:

如果我们不会使用结构体?我们可以使用元组

fn main()
{
    let rect:(u32,u32)=(10,20);
    println!("Area: {}",tuple_test(&rect));

}

fn tuple_test(rect:&(u32,u32))->u32
{
    rect.0*rect.1
}

在这里插入图片描述
这样写有点太low ,而且我们只知道数据类型,不知道属性名,对于我们来说太模糊了

我们使用结构体:

struct Rect{
    width:u32,
    height:u32    
}

fn main()
{
    let rect1=Rect{
        width:20,
        height:30
    };
    println!("Area: {}",area_struct(&rect1));
   
}

fn area_struct(rect:&Rect)->u32
{
    rect.width*rect.height
}

要点:

  • 结构体函数参数使用 &的类型是为了防止借用所有权,以便于以后还能在主函数中使用其所有权。
  • 我们可以很清晰的看到rect的width和rect的height相乘。

方便调试,显示结构体的信息:

在这里插入图片描述
能否直接显示rect1的信息? 显然,不能

在这里插入图片描述
我们没有对Rect定义的Display显示函数,但是编译器提示我们可以使用 {:?} 运算符,或者 {:#?} 运算符

#[derive(Debug)]
struct Rect{
    width:u32,
    height:u32    
}
fn main()
{
    let rect1=Rect{
        width:20,
        height:30
    };
    println!("Area: {:?}",rect1);
}

这样我们就可以直接在println!宏函数中。使用{:?} 来显示结构体的信息,便于我们调试。
注意:要在结构体的定义处加上: #[derive(Debug)]
在这里插入图片描述
能否更加直观的显示,比如换行? 可以,使用 {:#?}

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

在这里插入图片描述

使用dbg!宏调试

使用方法:

dbg!(&rect1);

这样我们就可以使用这一个宏直接进行显示:
在这里插入图片描述
可以看到,这个宏不仅显示了结构体的结构,还在开头指出了所在的行数
使用在结构体内部:

let num=100;
    let rect1=Rect{
        width:dbg!(20*num),
        height:30
    };

在这里插入图片描述
注意: dbg! 宏接受的是引用,同理,我们不希望在进入宏之后就丢失了对于此变量的所有权,所以我们使用&引用,来保留我们的结构体变量所有权。

结构体方法与关联函数

为结构体单独写一个特定的独立的函数太麻烦了,而且维护性不高,我们可以使用 impl 来指定为这个结构体定义一些方法

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

要点:

  • impl 结构体名字: 表明我们要为这个结构体写一个方法,所有的Rect变量都可以直接调用此方法。
  • fn 表示这是一个函数。
  • self:使用 &self 来替代 rectangle: &Rectangle,&self 实际上是 self: &Self 的缩写。在一个 impl 块中,Self 类型是 impl 块的类型的别名。相当于C++的this指针,在调用属性时,要指定是谁的属性,即是自己的。方法的第一个参数必须是一个指向自身的self。

试着调用这个方法:

fn main()
{
    let num=100;
    let rect1=Rect{
        width:dbg!(20*num),
        height:30
    };
    dbg!(&rect1);
    
    println!("Area: {}",rect1.area());

}

在这里插入图片描述
成功了,这样做很简单,使用rect1直接调用这个结构的方法。

我们对于任何的结构体变量都可以直接调用此方法:

fn main()
{
    let rect:(Rect,Rect)=(
        Rect{
            width:50,
            height:10
        },
        Rect{
            width:400,
            height:200
        }
    );
    dbg!(&rect);

    println!("Area1: {},Area2: {}",rect.0.area(),rect.1.area());

}

在这里插入图片描述
使用元组,包含两个Rect的结构体变量,然后依次调用结构体方法。

多参数的方法

例如,我们想比较两个结构体变量的面积,看看谁大,我们可以定义一个这样的方法:

impl Rect {
    fn area(&self)->u32
    {
        self.width*self.height
    }
    fn comp(&self,rect:&Rect)->bool
    {
        self.area() > rect.area()
    }
}
 ......
println!("Area1: {},Area2: {}\n Area1 > Area2 ?{}",rect.0.area(),rect.1.area(),
        rect.0.comp(&rect.1));

在这里插入图片描述
注意我们的方法调用方式: rect.0.comp(rect.1) ,由于我们在上面定义了一个嵌套的元组结构体,会稍微显得有点复杂,其实,这个调用就是这样的: 变量1.方法(变量2) 注意传入的参数要是引用的形式,保证其所有权不会转移。

关联函数

所有在 impl 块中定义的函数被称为 关联函数(associated functions),因为它们与 impl 后面命名的类型相>关。我们可以定义不以 self 为第一参数的关联函数(因此不是方法),因为它们并不作用于一个结构体的实例。我们已经使用了一个这样的函数:在 String 类型上定义的 String::from 函数。

只不过加上self的,对自身进行操作是方法,不加self的方法是关联函数。

impl Rect {
    fn area(&self)->u32
    {
        self.width*self.height
    }
    fn comp(&self,rect:&Rect)->bool
    {
        self.area() > rect.area()
    }
    fn square(i:u32)->Self
    {
        Self{
            width:i,
            height:i
        }
    }
}

我们定义了一个:square 接受 i u32类型的参数作为构造的新的结构体的变量,返回一个结构体。
这里我们就指定了一个参数构造矩形,相当于是一个正方形
不是方法的关联函数经常被用作返回一个结构体新实例的构造函数。类似于C++类构造函数。
调用与运行:

let rect2=Rect::square(5);
println!("Area1: {}",rect2.area());

使用结构体名和 :: 来指定调用此方法,:: 表示此方法位于结构体的命名空间内部。
在这里插入图片描述

总结

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

但结构体并不是创建自定义类型的唯一方法:让我们转向 Rust 的枚举功能,为你的工具箱再添一个工具。

posted @ 2022-09-17 17:20  hugeYlh  阅读(33)  评论(0编辑  收藏  举报  来源