第八章 rust中的struct
什么是struct? 结构体,自定义的数据类型,为相关联的值命名,打包->有意义的组合
定义struct
struct User{ // 使用struct关键字
username: String, // 字段名称和类型
email: String,
sign_in_count: u64,
active: bool, // 最后的字段也要有逗号
}
实例化struct
想要使用必须先实例化.
- 为每个字段指定具体的值
- 无需按声明顺序进行指定
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
fn main() {
let user1 = User {
email: String::from("abc@123.com"),
username: String::from("Nikky"),
active: true,
sign_in_count: 556, // 初始化时要给每个成员都赋值
};
}
实例化字段的简写
当字段名与字段值对应的变量名相同时,就可以使用字段初始化简写的方式:
struct User{
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
fn build_user(email: String, username: String)->User{
User {
email, // 使用了简写的方式
username, // 使用了简写的方式
active: true,
sign_in_count: 0,
}
}
struct 中的更新语法
当想要基于一个struct实例创建一个新实例的时候,可以使用struct更新语法:
let user2 = User{
email: String::from("anotheremail@123.com")
username: String::from("anothername"),
active: user1.active, // 如果不使用更新的语法,需要从之前的struct指定值
sign_in_count: user1.sign_in_count,
};
let user2 = User{
email: String::from("anotheremail@123.com")
username: String::from("anothername"),
..user1 // struct更新语法,使用两个...剩下的值使用user1的值
};
获取struct里面的某个值
使用点 标记法
let mut user1 = User { // 如果使用mut标记,标记struct是可变的,那么整个struct的字段都是可变的
email: String::from("abc@123.com"),
usernameL: String::from("Nikky"),
active: true,
sign_in_count: 556,
};
user1.email = String::from("anotheremail@123.com");
struct作为函数返回值
将struct作为函数最后的表达式, 就可以将struct作为函数的返回值使用.
tuple struct
定义类似tuple的struct.需要:
- tuple struct整体有个名字,但里面的元素没有名字
- 适用范围:想给整个tuple起名,并让它不同于其他tuple,而且又不需要给每个元素起名.
定义tuple struct: struct + 名称(元素类型)
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0,0,0);
let origin = Point(0,0,0);
没有任何字段的struct (unit-Like Struct)
可以定义没有任何字段的struct,叫做unit-like struct
适用于需要在某个类型上实现某个trait,但是在里面又没有想要存储的数据.
struct Empty;
let x = Empty;
println!("{:p}", &x);
struct数据的所有权
struct User{
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
上述结构体的字段使用了String而不是&str,并且u64等基础类型
- 所以该struct实例拥有其所有的数据的所有权
- 只要struct的实例是有效的,那么里面的字段数据也是有效的.
struct里面也可以存放引用,但这需要使用生命周期(以后再讲)
-
生命周期保证只要struct实例是有效的,那么里面的引用也是有效的
-
如果struct里面存储引用,而不使用生命周期,就会报错.
struct ImportantExcerpt<'a> { // 声明周期标注 part: &'a str, }
使用struct的例子
假如要计算一个矩形的面积,我们需要长宽进行计算,也就是我们需要两个参数.
fn main()
{
let w = 30;
let l = 50;
prinfln!("{}", area(w, l));
}
fn area(wigth: u32, length: u32) -> u32{
wigth * length
}
上面的例子的问题是矩形的长宽是独立的,没有将他们联系起来,也就是不统一.
使用元组可以将两个参数组合起来,但是它的问题的是哪个是长,哪个是宽,容易混淆
fn main()
{
let rect = (30, 50);
println!("{}", area(rect));
}
fn area(dim: (u32, u32)) ->u32
{
dim.0 * dim.1
}
使用struct对每个参数起名字
struct Rectangle {
width: u32,
length: u32,
} // 使用结构体来保存长宽的数据
fn main() {
let rect = Rectangle {
width: 30,
length: 50,
};
println!("{}", area(&rect)); // 保留了所有权
println!("{}", rect); // 这里打印会报错, 需要实现 Display trait
}
fn area(rect: &Rectangle) -> u32 {
rect.width * rect.length
}
struct的调试模式
如上面例子所示
println!("{}", rect);
这里直接打印一个struct是不行的,因为这个struct并没有实现std::fmt::Display这个接口,类似于python中没有实现__str__这个方法,无法打印出struct中具体的数据.
修改提示可以使用{:?}进行格式化
println!("{:?}", rect);
println!("{:#?}", rect);
但是这样也会报错,因为没有开启struct的debug模式,我们需要显示的开启这个功能
#[derive(Debug)]
struct Rectangle
{
width: u32,
length: u32,
}
其中derive是一种派生,这是实现一种trait的方式.
struct的方法
方法和函数类似:fn关键字、名称、参数、返回值相似
不同之处:
- 方法是在struct(或者
enum、trait对象)的上下文中定义 - 第一个参数是self,表示方法被调用的struct实例
方法的定义
- 使用
impl关键字,定义在impl块中, - 方法的第一个参数可以是&self,也可以获得其所有权或可变借用,和其他参数一样.
#[derive(Debug)]
struct Rectangle {
width: u32,
length: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.length
}
}
fn main() {
let rect = Rectangle {
width: 30,
length: 30,
};
println!("{}", rect.area());
println!("{:#?}", rect);
}
方法调用的运算符
rust没有->运算符
rust会自动解引用或者自动引用:在调用方法时就会发生这种行为
在调用方法时,rust根据情况自动添加& &mut *,以便object可以匹配方法的签名.
下面两行代码效果相同:
- p1.distance(&p2);
- (&p1).distance(&p2);
impl Rectangle{
fn area(&self) -> u32
{
self.width * self.length
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.length > other.length
}
}
如上我们添加一个方法之后.试着调用函数
fn main()
{
let rect1 = Rectangle{
width: 30,
length: 50,
};
let rect2 = Rectangle{
width: 20,
length: 10,
};
println!("{}", rect1.can_hold(&rect2)); // rect1 会自动 添加&rect1
}
关联函数
可以在impl块里定义不把self作为第一个参数的函数,他们叫关联函数例如 String::from()
关联函数通常用于构造器,用来生成关联类型的实例.
impl Rectangle{
fn area(&self) ->u32
{
self.width * self.length
}
fn can_hold(&self, other: &Rectangle)->bool{
self.width > other.width && self.length > other.length
}
fn square(size: u32) -> Rectangle{
Rectangle {
width: size,
length: size,
}
}
}
fn main(){
let s = Rectangle::square(20);
}
每个struct可以有多个impl块