结构体用来组合不同类型的数据,类似于 C 语言当中的结构体。定义结构体时,需要声明变量名以及变量的类型。
结构体定义与返回结构体
使用花括号,然后指明每一个字段的值来初始化结构体变量。
rust
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let user: User = User {
username: String::new("Jack"),
email: String::new("1649412@qq.com"),
active: true,
sign_in_count: 125554,
};
}
为了访问结构体中的某个成员变量,可以使用 .
操作符:
println("username is {user.username}");
注意整个结构体的每个字段都必须是可变的,Rust 不允许只将结构体的某个字段标记为可变。
另外,当函数的返回类型是一个结构体类型时,我们可以在函数的最后一个表达式中构造一个新的结构体变量。下面是一些返回结构体类型的例子。
rust
fn build_user(email: String, username: String) -> User {
User {
active: true,
username: username,
email: email,
sign_in_count: 1,
}
}
如果 build_user
函数的参数名与结构体的某个字段名相同,可以使用字段初始化简写语法,这样就可以不用显示指出这个字段的值了:
rust
fn build_user(email: String, username: String) -> User {
User {
active: true,
username: ,
email: ,
sign_in_count: 1,
}
}
有时候,一个结构体中许多字段的值是来自于同类型另一个结构体变量的字段,这个时候可以使用结构体更新语法(struct update syntax)来简化结构体变量的初始化过程:
rust
fn main() {
// --snip--
let user2 = User {
active: user1.active,
username: user1.username,
email: String::from("another@example.com"),
sign_in_count: user1.sign_in_count,
};
// 可以简化为
let user3 = User {
email: String::from("another@example.com"),
..user1
};
}
但是,..user1
必须放到最后。此外,在使用一个结构体变量的成员初始化另一个结构体变量的成员时,对于没有实现 copy trait
的数据类型,会发生所有权的转移,例如上例当中的 username
。
特殊形式的结构体
元组结构体(tuple structs)
元组结构体没有具体的字段名,只有字段的类型。当你想给整个元组取一个名字,并使元组的类型区别于其它元组类型时,元组结构体会比较有用。
rust
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
println!("Point = {origin.0, origin.1, origin.2}");
}
Color
和 Point
是两种不同的类型,尽管它们每个字段的类型都相同。
没有任何字段的类单元结构体
没有任何字段的结构体称为类单元结构体(unit-like structs)。类单元结构体常常在你想要在某个类型上实现 trait 但不需要在类型中存储数据的时候发挥作用。
rust
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
}
定义类单元结构体不需要 {}
,定义一个类结构体变量也不需要 {}
。
结构体的方法和关联函数
方法
方法和函数的不同之处在于:方法在结构体、枚举类型或者 trait 的上下文中定义,并且它们的第一个参数总是 self
,它代表调用该方法的结构体实例。
方法第一个参数的类型:
&self
实际上是self:&Self
的缩写;在一个impl
块当中,Self
是impl
块的类型的别名。&mut self
:如果想要改变实例当中的成员,参数的类型就需要写成&mut self
;self
:通过仅仅使用self
作为第一个参数来使方法获取实例的所有权是很少见的;这种技术通常用在当方法将self
转换成别的实例的时候,这时我们想要防止调用者在转换之后使用原始的实例。
rust
// 用于获得debug trait
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
// 方法的实现。可以分段写。
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn perimeter(&self) -> u32 {
2 * (self.height + self.width)
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
// 关联函数。属于整个结构体,不需要创建出实例就能使用
fn square(size: u32) -> Rectangle {
Rectangle {width:size, height:size}
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
let rect2 = Rectangle { width: 10, height: 40 };
let rect3 = Rectangle { width: 60, height: 45 };
let square1: Rectangle = Rectangle::square(32);
println!("rect1 is {:#?}", rect1); // 格式化输出debug
println!("square1 is {:?}", square1);
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}
{:?}
用于格式化输出实现了Debug
特征的结构体;例如:
square1 is Rectangle { width: 32, height: 32 }
{:#?}
以一种更易读的方式输出实现了Debug
特征的结构体;
rust
rect1 is Rectangle {
width: 30,
height: 50,
}
关联函数
所有不在 impl
块中定义的函数被称为关联函数(associated functions)。关联函数第一个参数的类型不必是 self
,因为它并不作用于一个结构体的实例。关联函数通常用来创建并返回一个结构体新实例,类似于其它编程语言当中的构造函数。通常这个函数命名为 new
,但不是必须的,因为 new
并不是 Rust 语言的关键字。例如,我们不使用 new
也可以用来创建对象:
rust
impl Rectangle {
fn square(size: u32) -> Self {
Self {
width: size,
height: size,
}
}
}
fn main()
{
let s = Rectangle::squre(20);
}
小结:本篇博客介绍了 Rust 语言当中结构体的定义,初始化结构的三种方式,两种特殊的结构体,以及方法和关联函数的定义。
文章主要参考资料:《The Rust Programming Language》
各位道友,码字不易,如有收获,记得一键三连啊。