Rust 结构体
结构体(struct)把相关的值组合成一个有名字的类型。元组是一种快速、没有名字的打包方式;结构体则给每个字段都起了名字,用到它的代码读起来也就清楚得多。
定义和创建结构体
Section titled “定义和创建结构体”struct Person { name: String, age: u32,}
fn main() { let alice = Person { name: String::from("Alice"), age: 30, };
println!("{} 今年 {} 岁", alice.name, alice.age);}输出:
Alice 今年 30 岁和元组 ("Alice", 30) 比一比:用结构体时,alice.name 和 alice.age 一看就懂,你不用去记第 0 个字段是名字、第 1 个字段是年龄。
让结构体可变
Section titled “让结构体可变”和变量一样,结构体实例默认是不可变的。想在创建之后修改某个字段,就加上 mut:
struct Person { name: String, age: u32,}
fn main() { let mut alice = Person { name: String::from("Alice"), age: 30, };
alice.age += 1; println!("{} 现在 {} 岁了", alice.name, alice.age);}用方法添加行为
Section titled “用方法添加行为”用 impl 块可以给结构体加上函数。在里面,&self 指的就是调用这个方法的那个实例:
struct Person { name: String, age: u32,}
impl Person { fn greet(&self) { println!("你好,我是 {},今年 {} 岁", self.name, self.age); }}
fn main() { let alice = Person { name: String::from("Alice"), age: 30 }; alice.greet();}输出:
你好,我是 Alice,今年 30 岁构造函数风格的函数
Section titled “构造函数风格的函数”一种常见的写法是在 impl 里放一个 new 函数,由它来创建实例,调用的人就不用把每个字段都亲手写一遍:
struct Person { name: String, age: u32,}
impl Person { fn new(name: &str, age: u32) -> Person { Person { name: name.to_string(), age } }}
fn main() { let bob = Person::new("Bob", 25); println!("{} 今年 {} 岁", bob.name, bob.age);}注意 Person { name: name.to_string(), age } 里单独出现的 age:当变量名和字段名完全相同时,可以省掉 age: age,只写 age。Rust 把这叫作字段初始化简写(field init shorthand)。
用 derive 打印结构体
Section titled “用 derive 打印结构体”对上面写的结构体试试 println!("{:?}", alice),会编译不过:除非你告诉 Rust,否则它不知道怎样用调试格式打印一个自定义类型。在结构体上方加上 #[derive(Debug)],就能免费获得这个功能:
#[derive(Debug)]struct Person { name: String, age: u32,}
fn main() { let alice = Person { name: String::from("Alice"), age: 30 }; println!("{:?}", alice);}输出:
Person { name: "Alice", age: 30 }#[derive(...)] 是 Rust 为类型自动生成常用功能的方式。你会经常看到用这种方式加上 Debug,以后还有 Clone 等等。
1. 矩形的方法
Section titled “1. 矩形的方法”创建一个 Rectangle 结构体,带有 width 和 height 两个字段(u32)。给它两个方法:area 返回面积,is_square 在两条边相等时返回 true。提示:这两个方法都只读取字段,所以参数是 &self。
查看答案
struct Rectangle { width: u32, height: u32,}
impl Rectangle { fn area(&self) -> u32 { self.width * self.height }
fn is_square(&self) -> bool { self.width == self.height }}
fn main() { let wide = Rectangle { width: 8, height: 3 }; let square = Rectangle { width: 5, height: 5 };
println!("宽矩形:面积 {},是正方形吗?{}", wide.area(), wide.is_square()); println!("正方形:面积 {},是正方形吗?{}", square.area(), square.is_square());}输出:
宽矩形:面积 24,是正方形吗?false正方形:面积 25,是正方形吗?true2. 银行账户
Section titled “2. 银行账户”创建一个 Account 结构体,带有 owner(String)和以分为单位的 balance(u32)。写一个 Account::new("Ana"),让余额从 0 开始。然后通过修改字段存入 50 分和 25 分,再用两种方式打印账户:一句普通的话,以及 {:?}。提示:变量必须是 let mut,才能修改 account.balance;而 {:?} 需要 #[derive(Debug)]。
查看答案
#[derive(Debug)]struct Account { owner: String, balance: u32,}
impl Account { fn new(owner: &str) -> Account { Account { owner: owner.to_string(), balance: 0 } }}
fn main() { let mut account = Account::new("Ana"); account.balance += 50; account.balance += 25;
println!("{} 有 {} 分钱", account.owner, account.balance); println!("{:?}", account);}输出:
Ana 有 75 分钱Account { owner: "Ana", balance: 75 }new 自己把 balance 设成了 0,调用的人就不会忘记这一步。构造函数之所以这么常见,很大程度上就是因为这一点。
3. 一组人
Section titled “3. 一组人”把三个 Person 值放进一个 vector:Ana(34 岁)、Ben(16 岁)和 Chen(52 岁)。打印所有 18 岁及以上的人的名字,再打印谁年龄最大。提示:用 for person in &people 循环,并用 let mut oldest = &people[0]; 记住目前为止年龄最大的人。
查看答案
struct Person { name: String, age: u32,}
fn main() { let people = vec![ Person { name: String::from("Ana"), age: 34 }, Person { name: String::from("Ben"), age: 16 }, Person { name: String::from("Chen"), age: 52 }, ];
let mut oldest = &people[0]; for person in &people { if person.age >= 18 { println!("{} 已经成年", person.name); } if person.age > oldest.age { oldest = person; } }
println!("年龄最大:{}({} 岁)", oldest.name, oldest.age);}输出:
Ana 已经成年Chen 已经成年年龄最大:Chen(52 岁)oldest 是指向 vector 内部的引用(&Person),不是副本,所以什么都没有被克隆。每次发现年龄更大的人,只要让 oldest 改为指向那个人就行。
- 结构体把有名字的字段组合成一个类型:
struct Person { name: String, age: u32 }。 - 用
Person { name: ..., age: ... }创建实例;以后要修改字段,就把它标记为mut。 - 用
impl块添加行为;&self指的是调用方法的那个实例。 - 加上
#[derive(Debug)],结构体就能用{:?}打印。
小测验
1. 和元组相比,结构体的主要优点是什么?
2. 给结构体加上 #[derive(Debug)] 后,你可以做什么?
3. 在 Person { name: name.to_string(), age } 里,单独的 age 起什么作用?
得分: 0 / 3
- 结构体的定义和实例化 —《Rust 程序设计语言》:还讲了结构体更新语法(
..other)、元组结构体和类单元结构体。 - 结构体示例程序 —《Rust 程序设计语言》:把一个计算矩形的程序从零散的变量改成元组、再改成结构体,让你看到结构体好在哪里。
- 方法语法 —《Rust 程序设计语言》:
&self和&mut self的区别、带额外参数的方法,以及new这样的关联函数。 - 结构体 — 通过例子学 Rust:各种结构体的可运行示例,最后还有一个小练习。
- 派生 — 通过例子学 Rust:
#[derive(...)]能为你生成哪些功能,比如Debug、Clone和PartialEq。