Rust 探索(七)—— 使用结构体来组织关联的数据

1. 定义并实例化结构体

Rust当中的结构体是一种自定义的数据类型,可以将多个值打包在一起,组成一个整体,听起来和面向对象编程语言当中类的概念似乎差不多

结构体的成员也是可以有不同的类型,并且其成员有对应的名称,相较于元组,它们更加灵活

使用struct关键字可以定义结构体

1
2
3
4
5
6
struct UserInfo {
username: String,
password: String,
gender: bool,
balance: f64
}

这就是一个UserInfo结构体,其中有4个字段,这些字段都是有对应名称的,有特定的含义,与平常所见的其他语言的类的定义十分相似

1
2
3
4
5
6
let user = UserInfo {
username: String::from("杰洛特"),
password: String::from("123456"),
gender: true,
balance: 50.0
};

如果进行实例化,可以使用这样键值对的方式,user就是我们创建的新实例,给对应的字段赋了有意义的值

1
print!("名称={}", user.username);

通过.可以根据字段名称访问实例中的字段

默认情况下,只能够访问实例的字段,而不能够进行修改,只有将实例声明为mut可变类型,才可以对实例中的字段值进行修改

1
2
3
4
5
6
7
8
9
10
let mut user = UserInfo {   // 声明为可变
username: String::from("杰洛特"),
password: String::from("123456"),
gender: true,
balance: 50.0
};

user.balance += 100.0; // 修改字段

print!("余额={}", user.balance)

实例声明为mut表明其中所有字段均可变,Rust不允许声明部分字段可变

2. 字段初始化简化

使用与字段名相同的变量进行初始化时,可以简化结构体的赋值写法

1
2
3
4
5
6
7
8
9
let username = String::from("杰洛特");
let password = String::from("123456");

let mut user = UserInfo {
username, // 简化写法
password,
gender: true,
balance: 50.0
};

当变量名称与结构体的字段名相同时,可以不用将键值分开书写,可以合并成一个,免去重复

3. 使用更新语法根据其他实例创建新实例

许多情况中,我们的新实例可能与原来已有的实例只是有细微的差别,重新新建一个从头开始初始化会比较啰嗦,此时就可以使用到Rust提供的更新语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let username = String::from("杰洛特");
let password = String::from("123456");

let user = UserInfo {
username,
password,
gender: true,
balance: 50.0
};

let user2 = UserInfo {
gender: false,
username: String::from("叶奈法"),
..user // 更新语法
};

使用..表明剩下没有显式指明的内容均与给定的实例相同

4. 使用元组结构体

1
2
struct Color(u8, u8, u8);
struct Point(f64, f64);

元组结构体的形式类似于元组,其只需要在声明的时候指定类型就可以了

1
let blue = Color(0, 0, 255);   // 创建实例

直接对应赋值创建实例

访问其中的字段可以使用元组的索引方式

1
println!("R={0}, G={1}, B={2}", blue.0, blue.1, blue.2);

5. 空结构体

还有一种特殊的结构体,没有任何的字段,这种结构体被称为空结构体

1
struct Thing;

通常用来在这种类型上实现trait,这与其他语言中的接口相似

6. 打印输出结构体

image-20230528163103621

直接使用println!()输出结构体代码无法通过编译

image-20230528163223131

主要还是Rust没有为结构体提供默认的Display实现,但是Rust也给出了解决方法,可以使用{:?}{:#?}

image-20230528163434666

修改后又有新的问题

image-20230528163525913

Debug没有实现,但是同样给出了解决方法,我们再搭配上#[derive(Debug)]试一下

1
2
3
4
5
6
7
#[derive(Debug)]   // 定义结构体的地方添加Debug标注
struct UserInfo {
username: String,
password: String,
gender: bool,
balance: f64
}

image-20230528163823353

现在可以输出结构体的内容了,如果想要格式更好看些,可以替换{:#?}

image-20230528163934725

7. 方法

如果把Rust的结构体看做是C++或是Java等语言的类,那么是不是还有成员方法的概念没有出现?其实Rust的结构体也是可以添加方法的

7.1. 定义方法

方法和函数相似但也有区别,主要就是方法是被定义在结构体、枚举类型或者trait对象内部的,类似于C++的成员函数,并且第一个参数永远是self,指代调用该方法的结构体实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#[derive(Debug)]
struct UserInfo {
username: String,
password: String,
gender: bool,
balance: f64
}

impl UserInfo { // 在对应的impl块中声明方法
fn earn(&mut self) {
self.balance += 100.0;
}

fn printInfo(&self) {
println!("用户信息={:#?}", self);
}
}

7.2. 多个参数

直接在&self后附加其他参数即可

1
2
3
4
5
6
7
8
9
impl UserInfo {
fn earn(&mut self, money: f64) { // 添加其他参数
self.balance += 100.0;
}

fn printInfo(&self) {
println!("用户信息={:#?}", self);
}
}
1
user.earn(50.0);

7.3. 关联函数

关联函数被称为函数而非方法,主要原因在于其不会作用于某个具体的实例,即没有&self参数

关联函数一种常用的场景是通过一些参数、条件返回一个新的实例

1
2
3
4
5
6
7
8
9
10
impl UserInfo {
fn magicBuild() -> UserInfo {
UserInfo {
username: String::from("召唤物"),
password: String::from(""),
gender: true,
balance: 0.0
}
}
}
1
2
let user = UserInfo::magicBuild();
println!("召唤={:#?}", user);

使用结构体类型加上::调用,这就类似于其他语言中的静态函数,与具体的实例无关,而只与类型相关

7.4. 多个impl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
impl UserInfo {
fn magicBuild() -> UserInfo {
UserInfo {
username: String::from("召唤物"),
password: String::from(""),
gender: true,
balance: 0.0
}
}
}

impl UserInfo {
fn earn(&mut self, money: f64) {
self.balance += 100.0;
}

fn printInfo(&self) {
println!("用户信息={:#?}", self);
}
}

一个结构体可以对应于多个impl块,但通常没有特殊的必要还是推荐写在一起