以下内容为本人学习过程中的记录笔记,其中可能存在不准确或错误,欢迎勘误及指正

结构体基本操作

定义、实例化

在实例化结构体时,字段内的赋值顺序可以和结构体定义不同,但是字段数量必须和定义一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 定义结构体
struct Person {
name: String,
age: u8,
}

fn main() {
// 实例化
let tom = Person {
name: "tom".to_string(),
age: 10,
};

// 实例化
let jerry = Person {
age: 8,
name: "jerry".to_string(),
};
}

访问、修改内部字段

需要注意的是,当我们将结构体声明为可变对象时,它下属的所有字段就都是可变的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Person {
name: String,
age: u8,
}

fn main() {
// 注意:这个结构体是可变的
let mut tom = Person {
name: "tom".to_string(),
age: 10,
};

// 访问结构体中的字段
println!("name: {}, age: {}", tom.name, tom.age);
// 修改结构体中的字段
tom.name = "jerry".to_string();
println!("name: {}, age: {}", tom.name, tom.age);
}

基于已有实例创建新实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct Person {
name: String,
age: u8,
species: String,
}

fn main() {
let tom = Person {
name: "tom".to_string(),
age: 10,
species: "cat".to_string(),
};

// 基于已有实例创建新实例
let jerry = Person {
name: "jerry".to_string(),
species: "mouse".to_string(),
..tom
};
}

元组结构体(Tuple Struct)

在某些情况下,我们只想给元组整体取名来实现其相对于别的元组的不同。比如下面的例子中的black和origin,虽然它们内部的字段是一样的,但是他们是不同类型的结构体,这样就可以针对其类型定义它们不同的行为

1
2
3
4
5
6
7
8
// 注意:定义元组结构体需要以分号结尾
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}

空结构体(Unit-Like Struct)

当我们想在某个类型上只实现行为而不想在其中储存数据的时候,就可以使用空结构体

1
2
3
4
5
struct Empty;

fn main() {
let a = Empty;
}

结构体数据的所有权

一般类型数据

如果结构中的数据储存在堆内存上的数据或者基础类型数据的话,那么这个结构体实例就拥有其所有的数据。只要实例有效,其内部的数据就有效;反过来说,当其内部数据的所有权发生转移,实例也就失效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#[derive(Debug)]
struct Team {
leader: String,
member: String,
episode: u32,
}

fn main() {
let msm = Team {
leader: "tom".to_string(),
member: "jerry".to_string(),
episode: 201,
};

// 注意:这里传递的是&String
hello(&msm.leader);
// 如果传递的不是引用,下面这句代码会发生错误,实例因其内部数据的所有权转移而失效
println!("{:?}", msm);
}

fn hello(name: &String) {
println!("hello, {}", &name);
}

引用类型数据

在结构体中也可以储存引用,但是需要使用到生命周期标注来保证在实例有效的范围内,其内部引用也是有效的

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Team<'a> {
leader: &'a str,
member: &'a str,
episode: u32,
}

fn main() {
let msm = Team {
leader: "tom",
member: "jerry",
episode: 201,
};
}

结构体的解构

当我们想要获得解构体内部值的时候,除了直接使用.标记法访问结构体数据外,还可以通过模式匹配来解构结构体实现直接获取内部值,但需要注意匹配造成的所有权转移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#[derive(Debug)]
struct Rectangle {
length: String,
width: String,
}

fn main() {
let rec = Rectangle {
length: 50.to_string(),
width: 30.to_string(),
};
// 这里的等号就相当于模式匹配
let Rectangle { length, width } = rec;
println!("length: {}, width: {}", length, width);
// 注意:这句代码会发生错误,因为所有权被转移
println!("{:?}", rec);
}

结构体方法

Rust中方法的作用和面向对象语言(如Python)中的方法有些类似但又有些区别,都是将行为与类型相关联,都可以访问关联类型的所有数据和方法,从而使代码更具可读性和可维护性。这里虽然标题是结构体方法,但其他的一些数据类型,比如枚举(enum)等也可以采用类似语法来实现与其关联的方法

定义方法

定义方法的语法要求

  1. 使用impl关键词进行定义
  2. 方法的定义在struct(或enum、trait对象)的上下文进行
  3. 方法的第一个参数为self,表示被方法调用的实例
  4. 一个类型可拥有多个impl块或多个方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
struct Person {
name: String,
age: u8,
}

impl Person {
// 注意:这里是借用了实例
fn introduce(&self) {
println!("My name is {}, {} years old!", self.name, self.age);
}
// 定义第二个方法
fn hello(&self) {
println!("hello {}", self.name);
}
}

// 再次使用impl
impl Person {
fn sleep(&self) {
println!("{} is sleeping", self.name);
}
}

fn main() {
let tom = Person {
name: "tom".to_string(),
age: 10,
};

// 调用方法
tom.introduce();
}

结构体方法中实例的所有权

在上面的例子中,我们其实是使用借用的模式获得实例中的数据,但是也可以获得实例所有权或者得到实例的可变引用,这取决于用户想要实现的行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#[derive(Debug)]
struct Person {
name: String,
age: u8,
}

impl Person {
// 注意:这里获得了实例的所有权
fn introduce(self) {
println!("My name is {}, {} years old!", self.name, self.age);
}
}

fn main() {
let tom = Person {
name: "tom".to_string(),
age: 10,
};

tom.introduce();
// 下面这句代码会出现错误,因为实例的所有权已经离开这个作用域
println!("{:?}", tom);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#[derive(Debug)]
struct Person {
name: String,
age: u8,
}

impl Person {
// 注意:这里是获得了可变引用
fn friend(&mut self) {
self.name.push_str(" and jerry");
}
}

fn main() {
let mut tom = Person {
name: "tom".to_string(),
age: 10,
};

println!("{:?}", tom);
tom.friend();
println!("{:?}", tom);
}

关联函数

如果学过Python知道,在类中有叫静态方法的特殊类型函数,其不以self作为函数的第一个参数。在Rust中也有类似的函数,即关联函数,关联函数通常被用于构造器,比如下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Rectangle {
length: usize,
width: usize,
}

impl Rectangle {
// new函数即为关联函数,它的作用是返回一个Rectangle实例
fn new(length: usize, width: usize) -> Rectangle {
// 这里使用了简化写法
Rectangle { length, width, }
}

fn area(&self) -> usize {
self.length * self.width
}
}

fn main() {
// 调用关联函数时使用"::"
let rec = Rectangle::new(50, 30);
println!("{}", rec.area());
}

在结构体方法中,self是一个指向当前对象的指针,类似于Python定义类时的self参数,表示当前对象。Self是一个特殊的类型,表示当前类型本身,比如上面的例子可以这样改写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#[derive(Debug)]
struct Rectangle {
length: usize,
width: usize,
}

impl Rectangle {
// 使用Self作为返回类型
fn new(length: usize, width: usize) -> Self {
Self { length, width, }
}
}

fn main() {
let rec = Rectangle::new(50, 30);
println!("{:?}", rec);
}

关联函数和方法的内部调用

在impl块中方法调用其他方法和关联函数的模式是不一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#[derive(Debug)]
struct Rectangle {
length: usize,
width: usize,
}

impl Rectangle {
fn new(length: usize, width: usize) -> Self {
Self { length, width, }
}

fn area(&self) -> usize {
self.length * self.width
}

fn area_compare(&self, length: usize, width: usize) -> bool {
// 调用关联函数
let rec_new = Self::new(length, width);
// 调用方法
self.area() > rec_new.area()
}
}

fn main() {
let rec = Rectangle::new(50, 30);
println!("{:?}", rec);
let e = rec.area_compare(40, 35);
println!("{}", e);
}

如果关联函数想要调用结构体方法,则需要传入结构体实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct Rectangle {
length: usize,
width: usize,
}

impl Rectangle {
fn new(length: usize, width: usize) -> Self {
Self { length, width, }
}

fn area(&self) -> usize {
self.length * self.width
}

fn double_area(rec: &Rectangle) -> usize {
let new_rec = Self::new(rec.length * 2, rec.width * 2);
new_rec.area()
}
}

fn main() {
let rec = Rectangle::new(50, 30);
let double_area = Rectangle::double_area(&rec);
println!("The double area of the rectangle is {} square pixels.", double_area);
}