第9篇:成员功能------为结构体添加能力
作者: 李金雨
联系方式: wbtm2718@qq.com
目标读者: Rust中文编程
核心理念: AI时代必须使用中文编程,母语编程阅读效率极高
1. 开篇引入
本课目标
- 理解什么是成员功能(方法)
- 掌握为结构体实现方法的方法
- 理解方法中的self参数
- 理解关联函数的概念
- 理解方法与函数的区别
生活场景引入
想象一下,你有一个机器人,它有各种功能:
- 走路
- 说话
- 唱歌
- 跳舞
这些功能是机器人的一部分,只能由机器人来执行。在编程中,方法就像机器人的功能,它是结构体的一部分,只能由结构体的实例来调用。
预期成果展示
通过这节课的学习,你将能够:
- 为结构体实现方法
- 调用结构体的方法
- 理解方法中的self参数
- 定义和使用关联函数
- 区分方法与函数
2. 概念讲解
什么是成员功能(方法)
方法是与结构体关联的函数,它可以访问和修改结构体的字段。方法就像结构体的行为,是结构体的一部分。
为结构体实现方法
在Rust中,我们使用impl块来为结构体实现方法:
rust
fn main() {
impl 结构体名 {
fn 方法名(&self, 参数: 类型) -> 返回类型 {
// 方法体
}
}
}
例如,为学生结构体实现方法:
rust
fn main() {
struct 学生 {
姓名: String,
年龄: i32,
成绩: i32,
科目列表: Vec<String>,
}
impl 学生 {
// 显示信息方法
fn 显示信息(&self) {
println!("姓名:{}", self.姓名);
println!("年龄:{}岁", self.年龄);
println!("成绩:{}分", self.成绩);
println!("学习的科目:{:?}", self.科目列表);
}
// 学习方法
fn 学习(&mut self, 科目: &str) {
self.科目列表.push(String::from(科目));
println!("{}正在学习{}", self.姓名, 科目);
}
// 考试方法
fn 考试(&mut self, 分数: i32) {
self.成绩 = 分数;
println!("{}考了{}分", self.姓名, 分数);
}
}
}
self参数
方法的第一个参数通常是self,它代表调用方法的实例。self有三种形式:
&self:不可变引用,用于只读操作&mut self:可变引用,用于修改操作self:值传递,会转移所有权
rust
fn main() {
struct 学生 {
姓名: String,
年龄: i32,
成绩: i32,
科目列表: Vec<String>,
}
impl 学生 {
// 不可变方法(只读)
fn 显示信息(&self) {
// 只能读取字段,不能修改
}
// 可变方法(可修改)
fn 学习(&mut self, 科目: &str) {
// 可以修改字段
}
// 消费方法(转移所有权)
fn 转换为字符串(self) -> String {
format!("{},{}岁,{}分", self.姓名, self.年龄, self.成绩)
}
}
}
方法的调用
调用方法使用点号(.):
rust
fn main() {
struct 学生 {
姓名: String,
年龄: i32,
成绩: i32,
科目列表: Vec<String>,
}
impl 学生 {
fn 显示信息(&self) {
println!("姓名:{},年龄:{}岁,成绩:{}分", self.姓名, self.年龄, self.成绩);
}
fn 学习(&mut self, 科目: &str) {
self.科目列表.push(String::from(科目));
}
fn 考试(&mut self, 分数: i32) {
self.成绩 = 分数;
}
}
let mut 学生一 = 学生 {
姓名: String::from("张三"),
年龄: 15,
成绩: 0,
科目列表: vec![],
};
// 调用显示信息方法
学生一.显示信息();
// 调用学习方法
学生一.学习("数学");
// 调用考试方法
学生一.考试(95);
// 再次显示信息
学生一.显示信息();
}
关联函数
关联函数是与结构体关联的函数,但它不接收self参数。关联函数通常用于创建结构体实例,就像构造函数一样。
rust
fn main() {
struct 学生 {
姓名: String,
年龄: i32,
成绩: i32,
科目列表: Vec<String>,
}
impl 学生 {
// 关联函数(构造函数)
fn 新(姓名: String, 年龄: i32) -> Self {
Self {
姓名,
年龄,
成绩: 0,
科目列表: vec![],
}
}
}
}
调用关联函数使用::操作符:
rust
fn main() {
struct 学生 {
姓名: String,
年龄: i32,
成绩: i32,
科目列表: Vec<String>,
}
impl 学生 {
fn 新(姓名: String, 年龄: i32) -> Self {
Self {
姓名,
年龄,
成绩: 0,
科目列表: vec![],
}
}
}
let 学生一 = 学生::新(String::from("张三"), 15);
}
方法与函数的区别
| 区别 | 函数 | 方法 |
|---|---|---|
| 定义位置 | 任何地方 | impl块内 |
| 调用方式 | 直接调用 | 通过实例调用 |
| self参数 | 无 | 有(通常) |
| 访问权限 | 只能通过参数访问数据 | 可以直接访问实例的字段 |
3. 动手实践
基础练习(必做)
-
为"动物"结构体添加"叫"的方法 :
ruststruct 动物 { 名称: String, 年龄: i32, 声音: String, } impl 动物 { // 关联函数(构造函数) fn 新(名称: String, 年龄: i32, 声音: String) -> Self { Self { 名称, 年龄, 声音, } } // 叫的方法 fn 叫(&self) { println!("{}在{}", self.名称, self.声音); } // 显示信息方法 fn 显示信息(&self) { println!("名称:{}", self.名称); println!("年龄:{}岁", self.年龄); println!("声音:{}", self.声音); } } fn main() { let 猫 = 动物::新(String::from("猫咪"), 2, String::from("喵喵")); let 狗 = 动物::新(String::from("狗狗"), 3, String::from("汪汪")); 猫.显示信息(); 猫.叫(); 狗.显示信息(); 狗.叫(); } -
为"汽车"结构体添加"加速"、"减速"方法 :
ruststruct 汽车 { 品牌: String, 颜色: String, 速度: f64, } impl 汽车 { // 关联函数(构造函数) fn 新(品牌: String, 颜色: String) -> Self { Self { 品牌, 颜色, 速度: 0.0, } } // 加速方法 fn 加速(&mut self, 增量: f64) { self.速度 += 增量; println!("{}加速了{}km/h,当前速度:{}km/h", self.品牌, 增量, self.速度); } // 减速方法 fn 减速(&mut self, 减量: f64) { if self.速度 >= 减量 { self.速度 -= 减量; println!("{}减速了{}km/h,当前速度:{}km/h", self.品牌, 减量, self.速度); } else { self.速度 = 0.0; println!("{}减速到0km/h", self.品牌); } } // 显示信息方法 fn 显示信息(&self) { println!("品牌:{}", self.品牌); println!("颜色:{}", self.颜色); println!("当前速度:{}km/h", self.速度); } } fn main() { let mut 我的汽车 = 汽车::新(String::from("大众"), String::from("黑色")); 我的汽车.显示信息(); 我的汽车.加速(30.0); 我的汽车.加速(20.0); 我的汽车.减速(10.0); 我的汽车.减速(50.0); } -
为"书籍"结构体添加"显示信息"方法 :
ruststruct 书籍 { 书名: String, 作者: String, 价格: f64, 页数: i32, } impl 书籍 { // 关联函数(构造函数) fn 新(书名: String, 作者: String, 价格: f64, 页数: i32) -> Self { Self { 书名, 作者, 价格, 页数, } } // 显示信息方法 fn 显示信息(&self) { println!("书名:{}", self.书名); println!("作者:{}", self.作者); println!("价格:{}元", self.价格); println!("页数:{}页", self.页数); } // 计算每页数价格的方法 fn 每页数价格(&self) -> f64 { self.价格 / self.页数 as f64 } } fn main() { let 书籍一 = 书籍::新( String::from("Rust编程入门"), String::from("李金雨"), 49.9, 300 ); 书籍一.显示信息(); println!("每页数价格:{:.4}元", 书籍一.每页数价格()); }
进阶练习(选做)
-
为"点"结构体添加方法 :
ruststruct 点 { x: f64, y: f64, } impl 点 { // 关联函数(构造函数) fn 新(x: f64, y: f64) -> Self { Self { x, y } } // 计算到原点的距离 fn 到原点距离(&self) -> f64 { (self.x * self.x + self.y * self.y).sqrt() } // 计算到另一个点的距离 fn 到点距离(&self, 另一个点: &点) -> f64 { let 差x = 另一个点.x - self.x; let 差y = 另一个点.y - self.y; (差x * 差x + 差y * 差y).sqrt() } // 移动点 fn 移动(&mut self, dx: f64, dy: f64) { self.x += dx; self.y += dy; } } fn main() { let mut 点一 = 点::新(0.0, 0.0); let 点二 = 点::新(3.0, 4.0); println!("点一:({}, {})", 点一.x, 点一.y); println!("点二:({}, {})", 点二.x, 点二.y); println!("点一到原点的距离:{}", 点一.到原点距离()); println!("点一到点二的距离:{}", 点一.到点距离(&点二)); 点一.移动(1.0, 2.0); println!("移动后点一:({}, {})", 点一.x, 点一.y); println!("移动后点一到点二的距离:{}", 点一.到点距离(&点二)); } -
为"矩形"结构体添加方法 :
ruststruct 矩形 { 长: f64, 宽: f64, } impl 矩形 { // 关联函数(构造函数) fn 新(长: f64, 宽: f64) -> Self { Self { 长, 宽 } } // 计算面积 fn 面积(&self) -> f64 { self.长 * self.宽 } // 计算周长 fn 周长(&self) -> f64 { 2.0 * (self.长 + self.宽) } // 判断是否为正方形 fn 是正方形(&self) -> bool { self.长 == self.宽 } } fn main() { let 矩形一 = 矩形::新(5.0, 3.0); let 矩形二 = 矩形::新(4.0, 4.0); println!("矩形一:长{},宽{}", 矩形一.长, 矩形一.宽); println!("面积:{}", 矩形一.面积()); println!("周长:{}", 矩形一.周长()); println!("是否为正方形:{}", 矩形一.是正方形()); println!("\n矩形二:长{},宽{}", 矩形二.长, 矩形二.宽); println!("面积:{}", 矩形二.面积()); println!("周长:{}", 矩形二.周长()); println!("是否为正方形:{}", 矩形二.是正方形()); }
挑战练习(拓展)
-
为"学生管理系统"添加方法 :
ruststruct 学生 { 姓名: String, 年龄: i32, 成绩: i32, } impl 学生 { // 关联函数(构造函数) fn 新(姓名: String, 年龄: i32, 成绩: i32) -> Self { Self { 姓名, 年龄, 成绩, } } // 显示信息方法 fn 显示信息(&self) { println!("姓名:{},年龄:{}岁,成绩:{}分", self.姓名, self.年龄, self.成绩); } // 修改成绩方法 fn 修改成绩(&mut self, 新成绩: i32) { self.成绩 = 新成绩; println!("{}的成绩已修改为{}分", self.姓名, self.成绩); } } struct 学生管理系统 { 学生们: Vec<学生>, } impl 学生管理系统 { // 关联函数(构造函数) fn 新() -> Self { Self { 学生们: vec![], } } // 添加学生方法 fn 添加学生(&mut self, 学生: 学生) { self.学生们.push(学生); println!("学生添加成功"); } // 显示所有学生信息方法 fn 显示所有学生(&self) { println!("学生列表:"); for (索引, 学生) in self.学生们.iter().enumerate() { print!("第{}名:", 索引 + 1); 学生.显示信息(); } } // 计算平均成绩方法 fn 计算平均成绩(&self) -> f64 { if self.学生们.is_empty() { return 0.0; } let mut 总分 = 0; for 学生 in &self.学生们 { 总分 += 学生.成绩; } 总分 as f64 / self.学生们.len() as f64 } } fn main() { let mut 系统 = 学生管理系统::新(); // 添加学生 系统.添加学生(学生::新(String::from("张三"), 15, 95)); 系统.添加学生(学生::新(String::from("李四"), 14, 88)); 系统.添加学生(学生::新(String::from("王五"), 15, 92)); // 显示所有学生 系统.显示所有学生(); // 计算平均成绩 println!("平均成绩:{}分", 系统.计算平均成绩()); // 修改学生成绩 if let Some(学生) = 系统.学生们.get_mut(0) { 学生.修改成绩(98); } // 再次显示所有学生 系统.显示所有学生(); // 再次计算平均成绩 println!("平均成绩:{}分", 系统.计算平均成绩()); }
4. 知识总结
核心概念回顾
- 方法:与结构体关联的函数,通过实例调用
- impl块:用于为结构体实现方法
- self参数:方法的第一个参数,代表调用方法的实例
- &self:不可变引用,用于只读操作
- &mut self:可变引用,用于修改操作
- 关联函数:与结构体关联但不接收self参数的函数
- 构造函数:通常是关联函数,用于创建结构体实例
关键代码速查
| 功能 | 代码 | 说明 |
|---|---|---|
| 方法定义 | fn 方法名(&self, 参数: 类型) -> 返回类型 { 代码 } |
定义一个方法 |
| 可变方法 | fn 方法名(&mut self, 参数: 类型) -> 返回类型 { 代码 } |
定义一个可以修改实例的方法 |
| 关联函数 | fn 函数名(参数: 类型) -> Self { 代码 } |
定义一个关联函数 |
| 方法调用 | 实例.方法名(参数) |
调用实例的方法 |
| 关联函数调用 | 结构体名::函数名(参数) |
调用关联函数 |
常见错误提醒
- self参数:方法的第一个参数通常是self
- 引用类型:根据需要选择&self、&mut self或self
- 方法调用:方法需要通过实例调用,而不是直接调用
- 关联函数调用:关联函数需要通过结构体名调用
5. 课后作业
巩固练习题
- 编写一个程序,为"手机"结构体添加"打电话"、"发短信"方法
- 编写一个程序,为"学校"结构体添加"添加学生"、"显示学生"方法
- 编写一个程序,为"电影"结构体添加"播放"、"暂停"方法
创意编程题
- 发挥你的创意,创建一个程序,为"游戏角色"结构体添加"攻击"、"防御"、"移动"方法
- 编写一个程序,为"银行账户"结构体添加"存款"、"取款"、"查询余额"方法
下篇预习提示
下一篇我们将学习"归属权与借用------Rust的安全保障",这是Rust的特色功能,能够保证内存安全。预习一下:
- 什么是归属权(所有权)?
- 为什么Rust需要归属权?
- 什么是借用?
- 借用有什么规则?
恭喜你完成了第九篇教程!你已经掌握了为结构体添加方法的技巧,学会了如何让结构体具有更多的功能。继续加油,下一篇我们将学习Rust的特色功能------所有权和借用!
学习交流
如果你在学习过程中遇到任何问题,欢迎联系我:
- ** 视频号,"时空系"
- 邮箱: wbtm2718@qq.com
- 其他提示:
- 本人正在开发纯中文编程语言和编译器,有兴趣的可以交流
- 本人正在写一本基于中国母语思维习惯的数学教材,可以让你一年内学完从初中到研究生的数学,而且可以让你快速掌握人工智能的关键数学知识,有兴趣的可以交流
祝你学习愉快!