10_rust的结构体struct
rust的struct
定义和实例化struct
使用struct关键字,并对整个struct命名。
在花括号内,对所有字段(Field)定义名称和类型。
创建struct实例:为每个字段指定具体值,无需按声明顺序进行指定。
struct User {
name: String,
id: u64,
is_act: bool,
}
fn main() {
let u1 = User {
name: String::from("test"),
is_act: true,
id: 12,
}
}
操作字段
使用点标记法操作字段,一旦struct实例是可变的,那么实例中所有字段都是可变的。
struct User {
name: String,
id: u64,
is_act: bool,
}
fn main() {
let mut u1 = User {
name: String::from("test"),
is_act: true,
id: 12,
}
u1.name = String::from("t2");
}
struct作为函数的返回值
struct User {
name: String,
id: u64,
is_act: bool,
}
fn return_struct(nm: String, is_act: bool) -> User {
User {
name: nm,
is_act: is_act,
id: 0,
}
}
当字段名与字段值对应变量名相同时,可使用字段初始化简写的方式。
struct User {
name: String,
id: u64,
is_act: bool,
}
fn return_struct(name: String, is_act: bool) -> User {
User {
name,
is_act,
id: 0,
}
}
struct的更新语法
当基于某个struct实例创建一个新实例时,可用更新语法:
fn main() {
let mut u1 = User {
name: String::from("test"),
is_act: true,
id: 12,
}
u1.name = String::from("t2");
let u2 = User {
name: String::from("test2"),
..u1 // 更新语法,表示剩余字段用u1的值来填充
}
}
tuple struct
tuple struct:定义类似于tuple的struct,tuple struct整体有个命名,但里边元素没有名称。
适应:想给整个tuple起名,使其不同于其他tuple,且无需给每个元素命名。
定义tuple struct:使用struct关键字 名字(元素类型列表)
struct Color(i32, i32, i32);
struct P(i32, i32, i32);
let b = Color(0, 0, 0);
let s = P(0, 0, 0); // s和b是两个不同类型数据
可用模式匹配和点索引的方式访问字段。
Unit-Like struct(没有任何字段)
可定义没有任何字段的struct,叫Unit-Like struct(因为与(),单元类型类似).
适于需要在某个类型上实现某个trait,但没有需要存储的数据。
struct数据的所有权
struct User {
name: String,
id: u64,
is_act: bool,
}
这里字段使用了String而不是&str,该struct实例拥有其所有的数据,只要struct实例有效,其字段也有效。
struct里也可放引用,需使用生命周期的知识。
看个实践:
struct Rect {
w: u32,
l: u32,
}
fn main() {
let rect = (30, 40);
println!("{}", get_area(rect));
let rect_struct = Rect {
w: 30,
l: 40,
}
// 传入不可变的引用,传入后为不可变借用,不会改变变量的所有权,所有权依然归属于main函数
println!("{}", get_area_struct(&rect_struct));
}
fn get_area(rect: (u32, u32)) -> u32 { // 使用tuple实现
rect.0 * rect.1 // 无法区分谁是长和宽
}
fn get_area_struct(rect: &Rect) -> u32 { // 使用结构体的不可变的借用即可,无需获得所有权
rect.w * rect.l
}
rust的打印与调试信息
首先看下直接打印
struct Rect {
w: u32,
l: u32,
}
fn main() {
let rect = Rect {
w: 30,
l: 40,
};
// 传入不可变的引用,传入后为不可变借用,不会改变变量的所有权,所有权依然归属于main函数
println!("{}", get_area_struct(&rect));
println!("{}", rect);
/* 直接打印,编译报错信息(没有实现display):
error[E0277]: `Rect` doesn't implement `std::fmt::Display`
--> src\main.rs:114:20
|
114 | println!("{}", rect);
| ^^^^ `Rect` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `Rect`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
*/
}
fn get_area_struct(rect: &Rect) -> u32 { // 使用结构体的不可变的借用即可,无需获得所有权
rect.w * rect.l
}
注释中附带的编译报错信息,是指Rect结构体没有实现打印trait std::fmt::Display
,不清楚如何打印。其他基础类型能答应是因rust默认实现了这些类型的display trait,而自定义类型无默认实现。
但下一行提示可使用{:?}
(or {:#?} for pretty-print)来代替。
struct Rect {
w: u32,
l: u32,
}
fn main() {
let rect = Rect {
w: 30,
l: 40,
};
// 传入不可变的引用,传入后为不可变借用,不会改变变量的所有权,所有权依然归属于main函数
println!("{}", get_area_struct(&rect));
println!("{:?}", rect);
/* 加上{:?}后打印,编译报错信息(变成了没有实现debug):
error[E0277]: `Rect` doesn't implement `Debug`
--> src\main.rs:114:22
|
114 | println!("{:?}", rect);
| ^^^^ `Rect` cannot be formatted using `{:?}`
|
= help: the trait `Debug` is not implemented for `Rect`
= note: add `#[derive(Debug)]` to `Rect` or manually `impl Debug for Rect`
*/
}
fn get_area_struct(rect: &Rect) -> u32 { // 使用结构体的不可变的借用即可,无需获得所有权
rect.w * rect.l
}
错误信息变成了没有实现debug,debug和display一样,也是一种格式化的方法。下一行提示可加上#[derive(Debug)]
或者手动实现debug。这是因为rust默认包含了打印调试信息的功能,但需要显示添加,能直接打印。
#[derive(Debug)] // 需要显示加上打印调试信息
struct Rect {
w: u32,
l: u32,
}
fn main() {
let rect = Rect {
w: 30,
l: 40,
};
// 传入不可变的引用,传入后为不可变借用,不会改变变量的所有权,所有权依然归属于main函数
println!("{}", get_area_struct(&rect));
println!("{:?}", rect); // 一行打印
println!("\n{:#?}", rect); // 加上#后换行打印
}
fn get_area_struct(rect: &Rect) -> u32 { // 使用结构体的不可变的借用即可,无需获得所有权
rect.w * rect.l
}
/* 打印输出:
Rect { w: 30, l: 40 }
Rect {
w: 30,
l: 40,
}
*/
这是因为rust提供了很多默认的trait(已实现好的函数方法),可用于derive,#[derive(Debug)]中的derive是派生的意思,可利用现有trait,给自定义类型添加很多功能。#[derive(Debug)]整行的意思是让Rect派生于Debug trait,所以才可直接用debug模式打印信息。
rust的方法
方法与函数类似:fn关键字、名称、参数、返回值。
方法与函数不同之处:
- 方法是在struct(或enum、trait对象)的上下文中定义
- 第一个参数是self,表示方法被调用的struct实例
例子:
#[derive(Debug)] // 加上打印调试信息
struct Rect {
w: u32,
l: u32,
}
impl Rect {
fn get_area_struct(&self) -> u32 { // 这里使用借用(引用),不需要所有权,也可获得所有权或可变的借用
self.w * self.l
}
// fn get_area_struct(self) -> u32 {} // 获得所有权(move)
// fn get_area_struct(&mut self) -> u32 {} // 可变借用
}
fn main() {
let rect = Rect {
w: 30,
l: 40,
};
println!("{}", rect.get_area_struct());
println!("{:?}", rect); // 一行打印
println!("\n{:#?}", rect); // 加上#后换行打印
}
定义方法:在impl块里定义方法,第一个参数是&self,也可获得所有权/可变借用,和其他参数一样,能更好组织代码。
方法调用的运算符
rust会在调用方法时自动引用或解引用,根据情况自动添加&、&mut或*,以便object可匹配方法签名。
如 p1.distance(&p2) 与 (&p1).distance(&p2)效果是等价的。
方法也可增加其他参数.
关联函数
还可在impl块里定义不把self作为第一个参数的函数,叫关联函数(不是方法),不是在实例中调用,但跟类型有关联,如之前的String::from()函数,关联函数通常用于构造器。
#[derive(Debug)] // 加上打印调试信息
struct Rect {
w: u32,
l: u32,
}
impl Rect {
fn get_area_struct(&self) -> u32 {
self.w * self.l
}
fn square(size: u32) -> Rect { // 关联函数,创建一个正方形
Rect {
w: size,
l: size,
}
}
}
fn main() {
let s = Rect::square(10); // 使用类直接调用,相当于函数位于类型的命名空间里
let rect = Rect {
w: 30,
l: 40,
};
println!("{}", rect.get_area_struct());
}
- ::符号作用:调用关联函数,模块创建的命名空间。
多个impl块
每个struct允许拥有多个impl块。
如:
#[derive(Debug)] // 加上打印调试信息
struct Rect {
w: u32,
l: u32,
}
impl Rect {
fn get_area_struct(&self) -> u32 { // 这里使用借用(引用),不需要所有权,也可获得所有权或可变的借用
self.w * self.l
}
// fn get_area_struct(self) -> u32 {} // 获得所有权(move)
// fn get_area_struct(&mut self) -> u32 {} // 可变借用
}
impl Rect {
fn square(size: u32) -> Rect { // 关联函数,创建一个正方形
Rect {
w: size,
l: size,
}
}
}
fn main() {
let s = Rect::square(10); // 使用类直接调用,相当于函数位于类型的命名空间里
let rect = Rect {
w: 30,
l: 40,
};
println!("{}", rect.get_area_struct());
println!("{:?}", rect); // 一行打印
println!("\n{:#?}", rect); // 加上#后换行打印
}