【Rust自学】5.1. 定义并实例化struct
喜欢的话别忘了点赞、收藏加关注哦,对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
5.1.1. 什么是struct
struct的中文意思为结构体,它是一种自定义的数据类型,它允许程序为相关联的值命名和打包,形成有意义的组合。它类似于其他编程语言中的“类”或“结构”,但它只提供数据存储功能,不包含方法。
学过C/C++的人可能对struct这个关键字很熟悉,但它们有区别:
-
C:struct 是一种用来组织数据的简单聚合类型。它只能包含数据,没有方法。
-
C++:struct 与 class 非常相似,可以包含数据和方法,唯一的语法区别是在 struct 中,默认的访问权限是 public;在 class 中,默认的访问权限是 private。
-
Rust:struct 仅用于定义数据结构,不包含方法,方法需要通过 impl 块为结构体定义。Rust 提供了更严格的所有权、生命周期和内存管理机制。
5.1.2. 定义struct
- 使用
struct
这个关键字,为整个struct命名(驼峰命名法) - 在花括号内,为所有字段(Field) 定义 名称 和 类型
例子:
为HLTV上的CS职业选手定制存储各项数据的struct(补充信息:CS职业选手的数据一般由Rating评分、DPR每回合死亡数、KAST不白给率、Impact影响力、ADR平均每回合伤害、KPR每回合击杀数组成)
struct Stats{ rating: f32, dpr: f32, kast: f32, impact: f32, adr: f32, kpr: f32,
}
5.1.3. 实例化struct
想要使用struct
,需要创建struct
的实例:
- 为每个字段指定具体值,不能少赋字段的值
- 无需按声明的顺序进行指定
就以donk为例创建他的数据库:
fn main() { let donk = Stats { rating: 1.27, impact: 1.4, dpr: 0.67, adr: 88.8, kast: 74.1, kpr: 0.85, };
}
5.1.4. 取得struct里某个字段的值
使用点标记法可以取得struct里字段的值:
fn main() { let mut donk = Stats { rating: 1.27, impact: 1.4, dpr: 0.67, adr: 88.8, kast: 74.1, kpr: 0.85, }; donk.rating = 2.59;
}
如果要更改struct的值,记得在实例化时使用可变变量关键字mut
。
在struct中,可变性的最小单位就是单个实例,不能控制单个字段的可变性。一旦struct实例被声明为可变的,那么这个实例下的所有字段都是可变的。
5.1.5. 使用struct作为函数返回值
函数里的最后一个表达式就是它的返回值,所以使用struct作为返回值就只需要确保构建struct是这个函数的最后一个表达式(不带分号)就行:
fn change_stats(rating: f32, impact:f32, dpr:f32, adr:f32, kast:f32, kpr:f32) -> Stats{ Stats { rating: rating, impact: impact, dpr: dpr, adr: adr, kast: kast, kpr: kpr, }
}
5.1.6. 字段初始化的简写
Rust与JS和C#一样在某些情况下它的字段初始化是可以简写的
当字段名与字段值对应变量名相同时,就可以简写。比如在上一个代码例中,所有的字段名都和字段值对应的变量名相同,所以可以将其简写为:
fn change_stats(rating: f32, impact:f32, dpr:f32, adr:f32, kast:f32, kpr:f32) -> Stats{ Stats { rating, impact, dpr, adr, kast, kpr, }
}
当然不只是全部对应才能这么写,只要有一个字段符合简写条件就可以,其他的保持正常写法就行。
5.1.7. struct的更新语法
当你基于某个struct实例来创建一个新的实例的时候,如果新实例的字段有与先前实例里的字段相同的,就可以使用更新语法。
比如我要给存储sh1ro的数据,他的rating是1.25,impact是1.2,其余与donk一样,这是基础的写法:
fn main() { let donk = Stats { rating: 1.27, impact: 1.4, dpr: 0.67, adr: 88.8, kast: 74.1, kpr: 0.85, }; let sh1ro = Stats { rating: 1.25, impact: 1.2, dpr: donk.dpr, adr: donk.adr, kast: donk.kast, kpr: donk.kpr,};
}
这样写比较麻烦,所以Rust提供了这样的语法糖:
fn main() { let donk = Stats { rating: 1.27, impact: 1.4, dpr: 0.67, adr: 88.8, kast: 74.1, kpr: 0.85, }; let sh1ro = Stats { rating: 1.25, impact: 1.2, ..donk };
}
只需要写有变化的部分,其余一样的部分只需要写..
加上另一个struct实例的名字即可,表示剩下的没有赋值的字段的值都与另一个实例对应字段的值相同
5.1.8. 元组结构体Tuple struct
其中文名叫做元组结构体,指的是类似元组的结构体。元组结构体整体有名字,但里面的元素没有。适用于想给整个tuple起名,并让它不同于其他tuple,而且又不需要给每个元素起名。
定义tuple struct:使用struct关键字,后边是名字,以及里面元素的类型。
例子:
struct Color(u8, u8, u8);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
有的人戏谑地说:tuple struct在传统编程语言没有类似物,这是来自Haskell的高贵血统。这是因为在许多传统的面向对象语言(如 Java、C++)中,结构体或类是具名且字段命名的,而元组则是匿名且仅基于顺序的。没有中间形式来融合两者的优点。Rust的 tuple struct 概念与Haskell的新类型(Newtype Pattern) 有直接关系,Haskell中可以通过newtype
来定义类似的模式。
需要注意的是,即使两个元组结构体有相同数量的元素并且对应元素的数据类型都一样,它们也不该被称为相同的类型,因为它们是不同的struct。
5.1.9. 类单元结构体Unit-Like Struct
unit-like struct被称为类单元结构体,因为它们的行为类似于单元类型()
。当需要类型标记或是在某种类型上实现trait(可以理解为接口)但不想要在类型本身中存储任何数据时。类似于Go语言中的interface{}
。
struct ReadOnly;
struct WriteOnly;fn process_data<T>(_mode: T) {// 仅用于类型标记
}fn main() {process_data(ReadOnly);process_data(WriteOnly);
}
这个例子实现了类型标记
5.1.10. struct数据的所有权
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
在这个例子中,username
和email
都使用的是String
类型而不是&str
,因为String
类型是自有类型(owned type),拥有自身全部数据的所有权。在这种情况下,只要它的实例是有效的,那么里面的字段数据也肯定是有效的。
像&str
这样的引用类型也可以存放进struct
里,但这需要生命周期(以后讲)。在这里先简单地来说,生命周期保证只要struct
实例是有效的,那么里面的引用也是有效的。如果struct里面存储引用,而不使用生命周期,就会报错(missing lifetime specifier
)。