创建一个最简单的结构体:
struct info{
name:String,
age:u32,
address:String
}
要点:
结构体的对象创建:
let stu1=info{
name:String::from("ylh"),
age:20,
address:String::from("China")
};
我们创建了一个不可变的结构体变量,叫做stu1,并且给他传递了基本信息。
要点:
访问结构体变量:
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定义的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!(&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
}
}
要点:
试着调用这个方法:
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 的枚举功能,为你的工具箱再添一个工具。