04.结构体
struct
或structure
是一个自定义数据类型,允许你包装和命名多个相关的值,从而形成一个有意义的组合。
一、结构体的定义和实例化
结构体于元素类型类似,它们都包含多个相关的值。和元组一样,结构体的每一部分可以是不同类型。但不同于元组,结构体需要命名各部门数据以便能清楚的表明其值的意义,因此结构体比元组更灵活:不需要以来顺序来指定或访问实例中的值。
定义结构体,需要使用struct
关键字并为整个结构体提供一个名字(结构体的名字需要描述它所组合的数据意义)。在大括号中,定义每一部分数据的名字和类型,我们称为字段(field)。
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
一旦定义了结构体后,为了使用它,通过为每个字段指定具体值来创建这个结构体的实例。创建一个实例需要以结构体的名字开头,接着在打括号中使用key:value
(键-值对)的形式提供字段,其中key是字段的名字,value是需要存储在字段中的数据值。
fn main() {
let user1 = User{
email: String::from("111@qq.com"),
username: String::from("time"),
active: true,
sign_in_count: 1,
};
}
为了从结构体中获取某个特定的值,可以使用点号。举个例子,想要用户的邮箱地址,可以用 user1.email
。如果结构体的实例是可变的,我们可以使用点号并为对应的字段赋值。
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let mut user1 = User{
email: String::from("111@qq.com"),
username: String::from("time"),
active: true,
sign_in_count: 1,
};
println!{"{}", user1.email};
user1.email = String::from("anotheremail@example.com");
println!{"{}", user1.email};
}
注意整个实例必须是可变的;Rust不允许只将某个字段标记为可变。另外需要注意同其他任何表达式一样,我们可以在函数体的最后一个表达式中构造一个结构体的新实例,来隐式地返回这个实例。
build_user
函数返回一个带有给定的email和用户名的User
结构体实例。
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn build_user(email: String, username: String) -> User {
User {
email: email,
username: username,
active: true,
sign_in_count: 1,
}
}
fn main() {
let user1 = build_user(
String::from("someone@example.com"),
String::from("someusername123"),
);
}
1、结构体更新
Rust通过结构体更新语法来实现,使用旧实例的大部分值但改变其部分值来构建一个新的结构体实例。
首先,如下展示了不使用更新语法时,如何在 user2
中创建一个新 User
实例。我们为 email
设置了新的值,其他值则使用了实例 5-2 中创建的 user1
中的同名值:
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
let user2 = User {
active: user1.active,
username: user1.username,
email: String::from("another@example.com"),
sign_in_count: user1.sign_in_count,
};
}
使用结构体更新语法,我们可以通过更少的代码来达到相同的效果。
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
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"),
..user1
};
}
请注意,结构更新语法就像带有=
的赋值,因为它移动了数据。
2、元组结构体
元组结构体有着结构体名称提供的含义,但没有具体的字段名,只有字段的类型。
要定义元组结构体,以struct
关键字和结构体名开头并后跟元组中的类型。
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}
注意black
和origin
值得类型不同,因为它们是不同的元组结构体的实例。
3、类单元结构体
类单元结构体(Unit-Like Struct)是没有任何字段的结构体,它是在某个类型上实现trait但不需要在类中存储数据的时候发挥作用。
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
}
二、通过派生trait增加使用功能
当我们想要使用Debug
的输出格式时,可以在{}
中加入:?
从而告诉println!
我们将输出Debug
。Debug
是一个 trait,它允许我们以一种对开发者有帮助的方式打印结构体,以便当我们调试代码时能看到它的值
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {:?}", rect1);
}
但是我还需要在Rust结构体定义之前加上外部属性#[derive(Debug)]
从而外部现实调试信息。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {:?}", rect1);
}
当我们想要更为易懂的代码时,可以将{:?}
替换为{:#?}
。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {:#?}", rect1);
}
三、方法/语法
方法(method)与函数类似:它们使用fn
关键字和名称声明,可以拥有参数和返回值,同时包含在某处调用该方法时会执行的代码。不过方法与函数是不同的,因为它们在结构体的上下文被定义,并且它们第一个参数总是self
,它代表调用该方法的结构体实例。
1、定义方法
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
}
为了使函数定义于Rectangle
的上下文中,我们开始了一个impl
块,这个impl
块中的所有内容都将与Rectangle
类型关联。接着将 area
函数移动到 impl
大括号中,并将签名中的第一个(在这里也是唯一一个)参数和函数体中其他地方的对应参数改成 self
。然后在 main
中将我们先前调用 area
方法并传递 rect1
作为参数的地方,改成使用 方法语法(method syntax)在 Rectangle
实例上调用 area
方法。方法语法获取一个实例并加上一个点号,后跟方法名、圆括号以及任何参数。
在 area
的签名中,使用 &self
来替代 rectangle: &Rectangle
,&self
实际上是 self: &Self
的缩写。在一个 impl
块中,Self
类型是 impl
块的类型的别名。方法的第一个参数必须有一个名为 self
的Self
类型的参数,所以 Rust 让你在第一个参数位置上只用 self
这个名字来缩写。注意,我们仍然需要在 self
前面使用 &
来表示这个方法借用了 Self
实例,就像我们在 rectangle: &Rectangle
中做的那样。方法可以选择获得 self
的所有权,或者像我们这里一样不可变地借用 self
,或者可变地借用 self
,就跟其他参数一样。
2、关联函数
所有在impl
块中定义的函数被称为关联函数,因为它们与impl
后面命名的类型相关。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn square(size: u32) -> Self {
Self {
width: size,
height: size,
}
}
}
fn main() {
let sq = Rectangle::square(3);
}
使用结构体名和 ::
语法来调用这个关联函数:比如 let sq = Rectangle::square(3);
。这个函数位于结构体的命名空间中:::
语法用于关联函数和模块创建的命名空间。