5.1 定义结构体并实例化结构体
定义结构体,需要使用 struct
关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义每一部分数据的名字和类型,我们称为 字段(field)。
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool, // 最后多了一个,
}
实例化(不可变变量)
fn main() {
// 定义结构体
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
// 实例化
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
}
可变变量
fn main() {
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
// 可变变量,字段都可以修改
let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
// 可以修改
user1.email = String::from("anotheremail@example.com");
}
字段初始化简写语法
fn main() {
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: 1,
}
}
}
通过已经存着的变量初始化新变量
fn main() {
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
active: user1.active, // 通过user1初始化
sign_in_count: user1.sign_in_count, // 通过user1初始化
};
}
简化
fn main() {
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
..user1 // 其余的值都通过user1创建
};
}
也可以定义与元组(在第三章讨论过)类似的结构体,称为 元组结构体(tuple structs)。元组结构体有着结构体名称提供的含义,但没有具体的字段名,只有字段的类型。当你想给整个元组取一个名字,并使元组成为与其他元组不同的类型时,元组结构体是很有用的,这时像常规结构体那样为每个字段命名就显得多余和形式化了。
要定义元组结构体,以 struct
关键字和结构体名开头并后跟元组中的类型。
fn main() {
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}
注意 black
和 origin
值的类型不同,因为它们是不同的元组结构体的实例。你定义的每一个结构体有其自己的类型,即使结构体中的字段有着相同的类型。
也可以定义一个没有任何字段的结构体!它们被称为 类单元结构体(unit-like structs)因为它们类似于 ()
,即 unit 类型。类单元结构体常常在你想要在某个类型上实现 trait 但不需要在类型中存储数据的时候发挥作用。(后面章节介绍)
5.2 一个结构体的示例程序
使用 Cargo 新建一个叫做 rectangles 的二进制程序,它获取以像素为单位的长方形的宽度和高度,并计算出长方形的面积。
直接思维:
fn main() {
let width = 32;
let heigth = 25;
println!("The area of the rectangle is {} square pixels.", area(width, heigth));
}
fn area(width : u32, heigth : u32) -> u32 {
width * heigth
}
抽象成结构体:
struct Rectangle {
width : u32,
height : u32,
}
fn main() {
let rectangle = Rectangle{ width : 30, height : 50}; // 这里width和height都必须写着
println!("The area of the rectangle is {} square pixels.", area(&rectangle));
}
fn area(rectangle :&Rectangle) -> u32 {
rectangle.height * rectangle.width
}
通过派生 trait 增加实用功能
一个很实用的功能,一步步来实现,直接println!打印看看
加上这些试试
接着加
5.3方法语法
方法 与函数类似:它们使用 fn
关键字和名称声明,可以拥有参数和返回值,同时包含在某处调用该方法时会执行的代码。不过方法与函数是不同的,因为它们在结构体的上下文中被定义(或者是枚举或 trait 对象的上下文,将分别在第六章和第十七章讲解),并且它们第一个参数总是 self
,它代表调用该方法的结构体实例。
成员函数?
将上述案例中的函数用方法重写
#[derive(Debug)]
struct Rectangle {
width : u32,
height : u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.height * self.width
}
}
fn main() {
let rectangle = Rectangle{ width : 30, height : 50}; // 这里width和height都必须写着
// println!("The area of the rectangle is {} square pixels.", area(&rectangle));
println!("rectangle is {}", rectangle.area());
}
为了使函数定义于 Rectangle
的上下文中,我们开始了一个 impl
块(impl
是 implementation 的缩写)。接着将 area
函数移动到 impl
大括号中,并将签名中的第一个(在这里也是唯一一个)参数和函数体中其他地方的对应参数改成 self
。然后在 main
中将我们先前调用 area
方法并传递 rect1
作为参数的地方,改成使用 方法语法(method syntax)在 Rectangle
实例上调用 area
方法。方法语法获取一个实例并加上一个点号,后跟方法名、圆括号以及任何参数。
在 area
的签名中,使用 &self
来替代 rectangle: &Rectangle
,因为该方法位于 impl Rectangle
上下文中所以 Rust 知道 self
的类型是 Rectangle
。注意仍然需要在 self
前面加上 &
,就像 &Rectangle
一样。方法可以选择获取 self
的所有权,或者像我们这里一样不可变地借用 self
,或者可变地借用 self
,就跟其他参数一样。
这里选择 &self
的理由跟在函数版本中使用 &Rectangle
是相同的:我们并不想获取所有权,只希望能够读取结构体中的数据,而不是写入。如果想要在方法中改变调用方法的实例,需要将第一个参数改为 &mut self
。通过仅仅使用 self
作为第一个参数来使方法获取实例的所有权是很少见的;这种技术通常用在当方法将 self
转换成别的实例的时候,这时我们想要防止调用者在转换之后使用原始的实例。
带有更多参数的方法
同其它语言
关联函数
impl 块的另一个有用的功能是:允许在 impl 块中定义 不 以 self 作为参数的函数。这被称为 关联函数(associated functions),因为它们与结构体相关联。它们仍是函数而不是方法,因为它们并不作用于一个结构体的实例。已经使用过 String::from 关联函数了。
静态函数?
#![allow(unused_variables)]
fn main() {
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// 关联函数
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}
}
}
使用结构体名和 ::
语法来调用这个关联函数:比如 let sq = Rectangle::square(3);
。这个方法位于结构体的命名空间中:::
语法用于关联函数和模块创建的命名空间。第七章会讲到模块。
多个 impl 块
总结
结构体让你可以创建出在你的领域中有意义的自定义类型。通过结构体,我们可以将相关联的数据片段联系起来并命名它们,这样可以使得代码更加清晰。方法允许为结构体实例指定行为,而关联函数将特定功能置于结构体的命名空间中并且无需一个实例。
参考:使用结构体来组织相关联的数据 - Rust 程序设计语言 简体中文版 (bootcss.com)