Loading

【Rust-book】第五章 使用结构体来组织相关联的数据

第五章 使用结构体来组织相关联的数据

 

结构,或者结构体,是一种自定义数据类型,它允许我们命名多个相关的值并将它们组成一个有机的结合体。

 

可以把结构体视作对象中的数据属性

 

1 对比元组和结构体之间的异同,并演示如何使用结构体

 

2 讨论如何定义方法和关联函数,他们可以指定那些与结构体数据相关的行为

 

结构体和枚举体是用来创建类型的基本工具,在特定领域中的新类型同样可以享受到Rust编译时类型检查系统的所有优势。

 

定义并实例化结构体

 

结构体需要给每个数据赋予名字,可以清楚地表明它们地意义。

 

由于字段命名,不再需要依赖顺序索引来指定或访问实例中的值。

 

关键字struct被用来定义并命名结构体,一个良好的结构体名称应当能反映出自身数据组合的意义。在随后的花括号中声明所有数据的名字及类型,这些数据被称为字段。

 

1 struct User {
2   username: String,
3    email: String, 
4    sign_in_count: u64, 
5    active: bool,
6 }

 

在为每个字段赋予具体的值来创建结构体实例,赋值的顺序不需要严格对应在结构体中声明它们的顺序。

 

结构体的定义就像类型的通用模版一样,当我们将具体的数据填入模板时就创建出了新的实例。

1 let user1 = User {
2    email: String::from("someone@example.con"),
3   username: String::from("someusername123"),
4    active: true,
5    sign_in_count: 1,
6 };

可以通过点号来访问实例中的特定字段。

1 let mut user1 = User {
2   email: String::from("someone@example.con"),
3    username: String::from("someusername123"),
4    active: true,
5    sign_in_count: 1,
6 };
7 
8 user1.email = String::from("anothermail@example.com");

一旦实例可变,那么实例中的所有字段都将是可变的。

 

Rust不允许我们将单独声明某一部分字段的可变性。

1 fn build_user( email: String, username: String) -> User {
2   User { // 在变量名与字段名相同时使用简化版的字段初始化方法
3         // 参数与结构体字段拥有完全一致的名称,所以可以使用名为字段初始化简写的语法(field init shorthand)
4      email,
5       username,
6       active: true,
7       sign_in_count: 1,
8    }
9 }

使用结构体更新语法根据其他实例创建新实例

1 let user2 = User {
2   email: String::from("another@example.com"),
3    username: String::from("anotherusername455"),
4    ..user1 // .. 表明剩下的那些还未被显示赋值字段都与给定实例拥有相同的值
5 };

使用不需要对字段命名的元组结构体来创建不同的类型

 

元组结构体, 元组结构体同样拥有用于表明自身含义的名称,无须在声明它时对其字段进行命名,仅保留字段的类型即可。

 

一般来说,当你想要给元组赋予名字,并使其区别与其他拥有同样定义的元组时,可以使用元祖结构体。

1 struct Color(i32, i32, i32);
2 struct Point(i32, i32, i32);
3 
4 let black = Color(0,0,0);
5 let origin = Point(0,0,0);

这里的 black ,origin 是不同的类型,因为它们两个分别是不同元组结构体的实例,

 

所以定义的每一个结构体都拥有自己的类型,即便结构体中的字段拥有完全相同的类型。 例如,一个以Color类型作为参数的函数不能合法地接收Point类型的参数,即使它们都是由3个i32组成的。

 

可以通过模式匹配解构为单独的部分,也可以通过.及索引来访问特定字段

 

没有任何字段的空结构体

 

单元结构体,没有任何字段的结构体()。也称为空结构体

 

当需要在某些类型实现一个trait,却不需要在该类型中存储任何数据时,空结构体可以发挥作用。

 1 //对于引用来说有生命周期的限制
 2 struct User {
 3   username: &str, // expected lifetime parameter
 4    email: &str,
 5    sign_in_count: u64,
 6    active: bool,
 7 }
 8 
 9 fn main() {
10   let user1 = User {
11        email: "someone@example.com",
12        username: "someusername123",
13        active: true,
14        aign_in_count: 1,
15    };
16 }

一个结构体的示例程序

 1 fn main() {
 2   let width1 = 30;
 3   let height1 = 50;
 4     
 5    println!("The area of the rectangel is {} aquare pixels.", area(width1, height1));
 7 }
 8 fn area(width: u32, height: u32) -> u32 {
 9     width * height
10 }

元组重构

1 fn main() {
2   let rect1 = (30, 50);
3     
4    println!("The area of the rectangel is {} aquare pixels.", area(rect1));
5 }
6 
7 fn area(dimensions: (u32, u32)) -> u32 {
8     dimensions.0 * dimensions.1
9 }

 

使用结构体来重构代码,增加有意义的描述信息

 1 struct Rectangle {
 2     width: u32,
 3     height: u32,
 4 }
 5 
 6 fn main() {
 7     let rect1 = Rectangle { width: 30, height: 50 };
 8     println!("The area of the rectangel is {} aquare pixels.", area(&rect1));
 9 }
10 
11 fn area(rectange: &Rectangle) -> u32 { //不可变借用&Rectangle ,不会获得它的所有权
12     rectangle.width * rectangle.height
13 }

 

通过派生trait增加适用功能

 

 1 #[derive(Debug)]
 2 struct Rectange {
 3     width: u32, 
 4     height: u32,
 5 }
 6 
 7 fn main() {
 8     let rect1 = Rectangle { width: 30, height: 50 };;
 9     println!("rect1 is {:?}", rect1);
10 }

 

方法

 

方法总是被定义在某个结构体、枚举体或者triat对象,并且它们的第一个参数永远都是self,用于指代调用该方法的结构体实例。

 

 1 #[derive(Debug)]
 2 struct Rectangle {
 3     width: u32, 
 4     height: u32,
 5 }
 6 
 7 impl Rectangle {
 8     fn area(&self) -> u32 { // self: &Self ---> Self is Rectangle 
 9         self.width * sellf.height
10     }
11 }
12 
13 fn main() {
14     let rect1 = Rectangle { width: 30, height: 50 };
15     println!("The area of the rectangel is {} aquare pixels.", rect1.area());
16 }

 

为了在Rectangle的上下文环境中定义这个函数,需要将area函数移动到impl 关键字起始的代码块中,并把签名中的第一个参数(也是唯一的那个参数)和函数中使用该参数的地方改为self.

方法调用是通过实例后面加点好,并更上方法名、括号及可能的参数来实现。

 

带有更多参数的方法

 1 fn main() {
 2     let rect1 = Rectangle { width: 30, height: 50 };
 3     let rect2 = Rectangle { width: 10, height: 40 };
 4     let rect3 = Rectangel { width: 60, height: 45 };
 5     
 6     println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
 7     println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
 8 }
 9 
10 #[derive(Debug)]
11 struct Rectangle {
12     width: u32, 
13     height: u32,
14 }
15 
16 impl Rectangle {
17     fn area(&self) -> u32 { // self: &Self ---> Self is Rectangle 
18         self.width * sellf.height
19     }
20     
21     fn can_hold(&self, other: &Rectangle) -> bool {
22         self.width > other.width && self.height > other.height
23     }
24 }
25 
26 // 关联函数
27 impl Rectangle {
28     fn squaer(size: u32) -> Rectange {
29         Rectange { width: size, height: size }
30     }
31 }

 

关联函数常常用作构造器来返回一个结构体的新实例。

 类型名称后面添加::来调用关联函数

 

 :: 语法不仅被用于关联函数,还被用于模块创建的明命名空间。

 

多个imple块

 
 1 impl Rectangle {
 2     fn area(&self) -> u32 { // self: &Self ---> Self is Rectangle 
 3         self.width * sellf.height
 4     }
 5 }
 6 
 7 impl Rectangle {
 8     fn can_hold(&self, other: &Rectangle) -> bool {
 9         self.width > other.width && self.height > other.height
10     }
11 }

 

 
posted @ 2020-08-08 17:14  RainDavi  阅读(408)  评论(0编辑  收藏  举报