05结构体

struct 是一个自定义数据类型

在Rust 中通过 struct 来定义类型,就组织数据属性这点,有点类似于 Java 中的 Class,但又有很大不同。

struct 可以通过定义方法和关联函数的方式来指定与结构体数据相关的行为。

在Rust中可以通过在程序中基于结构体和枚举(enum)创建新类型。这样可以充分利用 Rust 的编译时类型检查。

定义并实例化结构体

定义结构体,需要使用struct 关键字并为整个结构体提供一个名字。

在大括号中,需要定义每一部分数据的名字和类型,即字段(field)。

struct 中仅定义字段,方法在外面创建。

struct 结构体命名规范,使用驼峰格式。

例如:

1
2
3
4
5
6
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}

实例化结构体

  1. 实例化需要结构体的名字开头,

  2. 在大括号中使用key:value 的形式提供字段值,key 为字段名字,value为存储在字段中的数据值。

    实例中字段的顺序不需要和它们在结构体中声明的顺序一致。

  3. 注意:如果结构体中有字段为私有的,如果推外提供访问,那么需要提供一个关联函数,否则外部无法通过直接实例化的方式创建结构体实例。参考第7章包、crate和模块

1
2
3
4
5
6
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};

使用结构体

访问结构体中的字段

  • 使用.点号,例如,user1.email

如果结构体实例是可变的,那么我们就可以修改实例中的字段值

  • 注意,Rust 中并不允许某个字段标记为可变,必须整个实例是可变的;
1
2
3
4
5
6
7
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"); // user1 实例是可变的,所以可以通过.来访问并修改email字段的值。

在函数中,我们可以在函数体的最后一个表达式中构造一个结构体的新实例,来隐式的返回整个实例。

1
2
3
4
5
6
7
8
fn build_user(email: String, username: String) -> User {
User {
email: email,
username: username,
active: true,
sign_in_count: 1,
}
}

变量与字段同名时的字段初始化简写语法

当参数名与结构体字段名完全相同时,实例化时,可以使用省略value的写法

1
2
3
4
5
6
7
8
fn build_user(email: String, username: String) -> User {
User {
email, // 参数名 email 与字段名相同
username, // 一样
active: true,
sign_in_count: 1,
}
}

用结构体更新语法从其他实例创建实例

如果一个实例从另一个实例中创建,可以仅设置不同的新值,相同的旧值字段不变。

这种结构体更新语法(struct update syntax),有两种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};

// user2 使用 user1 的部分字段
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
active: user1.active,
sign_in_count: user1.sign_in_count,
};

// user3 使用 .. 更新 user1 的部分字段
let user3 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
..user1
};

元组结构体

普通元组没有名称,

元组结构体:有名字,本质还是元组

  • 有着结构体的名称(这样整个元组有更明确的含义),
  • 只有字段的类型,但是没有具体的字段名,

特点:

  • 既可以像struct一样实例化,
  • 也可以像元组一样使用;
    • 解构为单独的部分,
    • 或者使用.后索引来访问单独的值。

使用场景:

  • 给整个元组取个名字,并使元组成为与其他元组不同的类型。
1
2
3
4
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 ,但是不需要在类型中存储数据的时候发挥作用。

结构体数据的所有权

结构体中除了可以存储自身拥有所有权的数据类型,还可以存储数据的引用(即所有权被其他对象拥有),

  • 存储引用,需要引入第10章 生命周期(lifetimes)
    • TODO回去找一下把同目录下的生命周期的文章链接进来;
  • 生命周期确保结构体引用的数据有效性跟结构体本身保持一致。
  • 如果尝试在结构体中存储一个引用而不指定生命周期,是无效的。

一个使用结构体的示例程序

通过派生 trait 增加实用功能

  1. println!() 宏能处理很多类型的格式,不过要使用{}格式输出,需要类型实现 Display trait。
    • std::fmt::Display
    • 也可以在类型上添加#[derive(Debug)],或实现 std::fmt::Debug,然后使用println!("{:?}", rect)的写法,让 Rust 打印出调试信息
      • 注:rect是一个类型变量
1
2
3
4
5
6
7
8
9
10
11
12
13
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}

fn main() {
let rect1 = Rectangle { width: 30, height: 50 };

println!("rect1 is {:?}", rect1);
}

//println!("rect1 is {:#?}", rect1); 使用{:#?} 打印可以更好看一些。

方法语法

方法为结构体实例指定行为。

定义方法

结构体的方法写在impl 块中(impl 是 implementation 缩写)

1
2
3
4
5
6
7
8
struct 结构体名称 {
...
}
impl 结构体名称 {
// 定义该结构体的方法
e.g. fn xx() {}
...
}
  • implstruct 是分离的,这点与 Java 不一样。

方法参数

方法与函数类似,

最大的不同是,**方法的第一个参数总是self**,它代表调用该方法的结构体实例。

  • self,mut self,&self,&mut self

  • 方法签名中,可以在self 后增加多个参数,这些参数就像函数中的参数一样工作;

  • 方法与实例关联,实例化后才能调用方法;

  • 函数可以独立存在,也可以与结构体关联(关联函数,不需要实例化可以直接通过::符号调用);

直接将self 作为第一个参数来使用,获取实例的所有权是很少见的;

  • 通常用在当方法将self转换成别的实例的时候,这时我们想要防止调用者在转换之后使用原始的实例。(通过移动所有权来做防范)

  • 大部分情况是使用 &self 或者&mut self ,即我们只希望能够使用(读取/修改)结构体中的数据,并不想获取所有权。

自动引用和解引用(automatic referencing and dereferencing)

  • 当在 Rust 中使用 object.something()调用方法时,Rust 会自动为 object 添加 &&mut* 以便使object 与方法签名匹配。

样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

#[derive(Debug,Copy,Clone)]
struct Point {
x: f64,
y: f64,
}

impl Point {
fn distance(&self, other: &Point) -> f64 {
let x_squared = f64::powi(other.x - self.x, 2);
let y_squared = f64::powi(other.y - self.y, 2);

f64::sqrt(x_squared + y_squared)
}
}
let p1 = Point { x: 0.0, y: 0.0 };
let p2 = Point { x: 5.0, y: 6.5 };
// 计算p1,p2 两点的距离
// 下面两种方法调用的写法是等价的。
p1.distance(&p2); // rust 自动处理了
(&p1).distance(&p2);

关联函数

关联函数将特定功能置于结构体的命名空间中并且无需一个实例。

impl 块中,可以定义self 作为参数的函数,称为关联函数(associated functions)。

  • 关联函数与结构体相关联;(它们仍然是函数而不是方法)
    • 从与结构体的关系上来看,有点类似于 Java 中的静态方法;
    • 例如 String::from 关联函数。

调用方式:

  • 使用结构体名和 :: 语法来调用关联函数;

多个 impl

每个结构体都允许拥有多个 impl 块。每个方法都属于其中一个 impl 块。

总结

  1. 结构体允许你创建出在你的领域中有意义的自定义类型。通过结构体,我们可以将相关联的数据片段联系起来并命名。
  2. 方法,允许为结构体实例指定行为;
  3. 关联函数,将特定功能置于结构体的命名空间中,并且无需一个实例;
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2023 ligongzhao
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信