【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 }