1、定义并实例化struct
什么是 struct
struct,结构体一自定义的数据类型
为相关联的值命名,打包=>有意义的组合
定义struct
使用struct关键字,并为整个struct命名
在花括号内,为所有字段(Field)定义名称和类型
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
实例化 struct
想要使用struct,需要创建struct的实例:
为每个字段指定具体值
无需按声明的顺序进行指定
fn main() {
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
}
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
注意我们在赋值的时候需要把每一个值都进行赋值,不然就会报错的
取得struct 里面的某个值
使用点标记法
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");
一旦 struct的实例是可变的,那么实例中所有的字段都是可变的,rust不允许struct中的字段部分可变部分不可变
struct作为函数的返回值
fn build_user(email: String, username: String) -> User {
User {
email: email,
username: username,
active: true,
sign_in_count: 1,
}
}
字段初始化简写
当字段名与字段值对应变量名相同时,就可以使用字段初始化简写的方式:
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
struct 更新语法
当你想基于某个struct实例来创建一个新实例的时候,可以使用struct更新语法:
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
active: user1.active,
sign_in_count: user1.sign_in_count,
};
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
..user1
};
这种写法就表示user2中还没有被赋值的值就和user1的值
Tuple struct(元祖结构体)
可定义类似tuple 的 struct,叫做tuple struct
Tuple struct整体有个名,但里面的元素没有名
适用:想给整个tuple起名,并让它不同于其它tuple,而且又不需要给每个元素起名
定义tuple struct:使用struct关键字,后边是名字,以及里面元素的类型
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
black和 origin是不同的类型,是不同 tuple struct的实例。
Unit-Like Struct(没有任何字段)
可以定义没有任何字段的struct,叫做Unit-Like struct(因为与(),单元类型类似)
适用于需要在某个类型上实现某个trait,但是在里面又没有想要存储的数据
struct 数据的所有权
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
这里的字段使用了string 而不是&str:
该struct实例拥有其所有的数据
只要struct实例是有效的,那么里面的字段数据也是有效的
struct 里也可以存放引用,但这需要使用生命周期(以后讲)。
生命周期保证只要struct实例是有效的,那么里面的引用也是有效的。
如果struct里面存储引用,而不使用生命周期,就会报错(例子)。
struct User {
username: &str,
email: &str,
sign_in_count: u64,
active: bool,
}
上面的例子中username和email就会报错,提示缺少生命周期
2、struct的例子
计算长方形面积
创建项目
cargo new demo 创建项目
code demo 使用vscode打开
fn main() {
let w = 5;
let l = 6;
println!("{}",area(w, l));
}
fn area(width:usize,length:usize)->usize {
width * length
}
这就是我们的第一个版本的计算长方形的面积,看简单吧
但是还是有问题的,因为长方形的长和宽是有关联的,只有它们组合在一起才是有意义的
现在我们在来写第二版
fn main() {
let rect = (30,50);
println!("{}",area(rect));
}
fn area(dim:(usize,usize))->usize {
dim.0 * dim.1
}
现在我们把这两个参数组合在一起了,但是它的可读性却变差了,因为我们不知道哪一个是长哪一个是宽了。
现在我们在来写第三版
fn main() {
let rect = Rectangle{
width:50,
length:30
};
println!("{}",area(&rect));
println!("{:#?}",rect)
}
#[derive(Debug)]
struct Rectangle {
width:usize,
length:usize
}
fn area(dim:&Rectangle)->usize {
dim.width * dim.length
}
上面就完成了它可读性的要求又有组合关联的含义
涉及到的内容
std:fmt:Display // 实现调试trait
std::fmt::Debug // 实现调试trait
#[derive(Debug)] // 实现调试trait
{:?} // 打印输出
{:#?} // 打印格式化输出
3、struct 的方法
方法和函数类似: fn关键字、名称、参数、返回值
方法与函数不同之处:
方法是在struct(或enum、trait对象)的上下文中定义
第一个参数是self,表示方法被调用的struct实例
定义方法
fn main() {
let rect = Rectangle{
width:50,
length:30
};
println!("{}",rect.area());
println!("{:#?}",rect)
}
impl Rectangle {
fn area(&self)->usize {
self.width * self.length
}
}
#[derive(Debug)]
struct Rectangle {
width:usize,
length:usize
}
在impl块里定义方法
方法的第一个参数可以是&self,也可以获得其所有权或可变借用。和其他参数一样。
更良好的代码组织。
方法调用的运算符
C/C++: object->something()和(*object).something()一样
Rust没有->运算符
Rust会自动引用或解引用
在调用方法时就会发生这种行为
在调用方法时,Rust根据情况自动添加&、&mut或*,以便object可以匹配方法的签名。
下面两行代码效果相同:
p1.distance(&p2);
(&p1).distance(&p2);
方法的调用
fn main() {
let rect1 = Rectangle{
width:50,
length:30
};
let rect2 = Rectangle{
width:40,
length:20
};
let rect3 = Rectangle{
width:55,
length:35
};
println!("{}",rect1.can_hold(rect2));
println!("{}",rect1.can_hold(rect3));
}
impl Rectangle {
fn area(&self)->usize {
self.width * self.length
}
fn can_hold(&self,other:Rectangle)->bool {
self.width > other.width && self.length > other.length
}
}
#[derive(Debug)]
struct Rectangle {
width:usize,
length:usize
}
关联函数
可以在 impl块里定义不把self 作为第一个参数的函数,它们叫关联函数(不是方法)
例如: String:from()
关联函数通常用于构造器(例子)
fn main() {
// 调用关联函数
let s = Rectangle::square(20);
println!("{:#?}",s);
let rect1 = Rectangle{
width:50,
length:30
};
let rect2 = Rectangle{
width:40,
length:20
};
let rect3 = Rectangle{
width:55,
length:35
};
println!("{}",rect1.can_hold(rect2));
println!("{}",rect1.can_hold(rect3));
}
impl Rectangle {
fn area(&self)->usize {
self.width * self.length
}
fn can_hold(&self,other:Rectangle)->bool {
self.width > other.width && self.length > other.length
}
// 关联函数
fn square(size:usize)->Rectangle{
Rectangle{
width:size,
length:size
}
}
}
#[derive(Debug)]
struct Rectangle {
width:usize,
length:usize
}
符号::
关联函数
模块创建的命名空间
多个impl块
每个struct允许拥有多个impl块(例子)
fn main() {
// 调用关联函数
let s = Rectangle::square(20);
println!("{:#?}",s);
let rect1 = Rectangle{
width:50,
length:30
};
let rect2 = Rectangle{
width:40,
length:20
};
let rect3 = Rectangle{
width:55,
length:35
};
println!("{}",rect1.can_hold(rect2));
println!("{}",rect1.can_hold(rect3));
}
impl Rectangle {
fn area(&self)->usize {
self.width * self.length
}
}
impl Rectangle {
fn can_hold(&self,other:Rectangle)->bool {
self.width > other.width && self.length > other.length
}
// 关联函数
fn square(size:usize)->Rectangle{
Rectangle{
width:size,
length:size
}
}
}
#[derive(Debug)]
struct Rectangle {
width:usize,
length:usize
}
struct的内容我们就先搞这么多。