当前位置: 首页 > news >正文

rust-方法语法

方法语法

方法类似于函数:我们用 fn 关键字和一个名称来声明它们,它们可以有参数和返回值,并且包含一些在从其他地方调用该方法时运行的代码。与函数不同,方法是在结构体(或枚举、trait 对象,分别在第6章和第18章介绍)上下文中定义的,其第一个参数总是 self,代表调用该方法的结构体实例。

定义方法

让我们将接收 Rectangle 实例作为参数的 area 函数改为定义在 Rectangle 结构体上的 area 方法,如清单5-13所示。

文件名: src/main.rs

#[derive(Debug)]
struct Rectangle {width: u32,height: u32,
}impl Rectangle {fn area(&self) -> u32 {self.width * self.height}
}fn main() {let rect1 = Rectangle {width: 30,height: 50,};println!("The area of the rectangle is {} square pixels.",rect1.area());
}

清单5-13:在Rectangle结构体上定义area方法
为了在Rectangle上下文中定义函数,我们开始了一个针对Rectangle的 impl(实现)块。这个 impl 块中的所有内容都与类型 Rectangle 相关联。然后我们把 area 函数移到 impl 的大括号内,并将签名中的第一个(也是唯一一个)参数改为 self,同时函数体内也相应替换。在 main 中,我们原本调用传入 rect1 参数的 area 函数,现在可以使用方法语法直接对我们的 Rectangle 实例调用 area 方法。方法语法写在实例后面:加点,再跟上方法名、括号及任何参数。

area 的签名里使用 &self 而不是 rectangle: &Rectangle。&self 实际上是 self: &Self 的简写。在 impl 块中,Self 是当前实现块对应类型的别名。所有的方法必须以名字为 self 且类型为 Self 的参数作为首个参数,因此 Rust 允许你只写成 self 来简化书写。但注意仍需加 & 表明此处借用了 Self 实例,就像之前用 rectangle: &Rectangle 一样。方法可以取得 self 所有权,也可以不可变借用自我(如这里),或者可变借用自我,就像对待其它任意参数一样。

这里选择 &self 和之前函数版本里的 &Rectangle 原因相同:不想获取所有权,只想读取结构体数据而非修改。如果希望改变被调用实例,则首个参数应设为 &mut self。而仅以 self 为首参并取得所有权的方法较少见;通常用于将自身转换成另一种东西,从而阻止调用者继续使用原始实例。

除了提供更方便的方法语法、不必每次重复指定自我类型外,使用方法代替普通函数最主要原因是组织性好——把能作用于某一类型实例的一切功能集中放进同一 impl 块,而不用让未来用户去库里各处寻找该类型能力所在。

注意,可以给某个字段起同样名字的方法。例如,可以给 Rectangle 定义一个也叫 width 的方法:

文件名: src/main.rs

impl Rectangle {fn width(&self) -> bool {self.width > 0}
}fn main() {let rect1 = Rectangle {width: 30,height: 50,};if rect1.width() {println!("The rectangle has a nonzero width; it is {}", rect1.width);}
}

这里,我们让宽度(width)这个同名的方法判断如果实例字段width大于0则返回 true,否则 false;即使名称相同,在该命名空间下,该字段依然可用于任何目的。在 main 中,当跟着圆点后带括号时,比如 rect1.width() ,Rust 知道指的是宽度这个“方法”;当没有括号时,比如 rect1.width ,Rust 知道指的是字段本身。

通常但不总是如此,当给字段起同样名字的方法时,这类“getter”只会返回对应字段值,不做其它操作。而 Rust 并不会自动帮 struct 字段生成 getter 方法,这一点与某些语言不同。这类 getter 很实用,因为你可以把字段设置成私有,但公开对应 getter,使得外部只能读不能改,这是设计公共 API 时常见手段。本书将在第7章讲解什么是公有(private)、私有(public),以及如何标记成员访问权限。

-> 操作符去哪儿了?
C 和 C++ 调用对象上的成员或其指针上的成员分别需要 . 和 -> 两种操作符,即若 object 是指针,则 object->something() 等价于 (*object).something() 。

Rust 没有等价于 -> 操作符,而采用了一种称作自动引用和解引用(automatic referencing and dereferencing)的新特性。这也是 Rust 少数几个支持这种行为的位置之一——即调用对象上的某个 method 时,如果签名要求引用或可变引用甚至取值,会自动帮你补充 &, &mut 或 * 。

例如下面两句完全等效:

p1.distance(&p2);
(&p1).distance(&p2);

第一句看起来更简洁。这种自动添加引用行为之所以行得通,是因为每个 method 都明确知道自己的接收者(receiver)—即那个叫做 self 参数的数据类型。有了接收者信息及 method 名称,Rust 能准确推断出这是读取(&self)、修改(&mut self)还是消费(self)。这种隐式借用机制极大提升了拥有权系统实际编程体验的人机友好度。

带有更多参数的方法

让我们通过在 Rectangle 结构体上实现第二个方法来练习使用方法。这次,我们希望 Rectangle 的一个实例接收另一个 Rectangle 实例作为参数,并返回 true,如果第二个矩形可以完全放入第一个矩形(self)内;否则,返回 false。也就是说,一旦定义了 can_hold 方法,我们就能像清单 5-14 中那样编写程序。

文件名:src/main.rs

fn main() {let rect1 = Rectangle {width: 30,height: 50,};let rect2 = Rectangle {width: 10,height: 40,};let rect3 = Rectangle {width: 60,height: 45,};println!("Can rect1 hold rect2?{}", rect1.can_hold(&rect2));println!("Can rect1 hold rect3?{}", rect1.can_hold(&rect3));
}

清单 5-14:使用尚未编写的 can_hold 方法
预期输出如下,因为 rect2 的两个维度都小于 rect1,而 rect3 比 rect1 宽:

Can rect1 hold rect2? true
Can rect1 hold rect3? false

我们知道要定义一个方法,所以它会在 impl Rectangle 块中。方法名为 can_hold,它将接受另一个不可变借用的 Rectangle 参数。通过查看调用该方法的代码可知参数类型:rect1.can_hold(&rect2)传入的是 &rect2,这是对 rect2(Rectangle 实例)的不可变借用。这很合理,因为我们只需要读取 rect2(而非修改,需要可变借用),且希望 main 保留对 rect2 的所有权,以便调用完 can_hold 后还能继续使用它。can_hold 返回值是布尔型,实现时检查 self 的宽和高是否分别大于另一个矩形的宽和高。让我们把新的 can_hold 方法添加到清单 5-13 中的 impl 块,如清单 5-15 所示。

文件名:src/main.rs

impl Rectangle {fn area(&self) -> u32 {self.width * self.height}fn can_hold(&self, other: &Rectangle) -> bool {self.width > other.width && self.height > other.height}
}

清单 5-15:为接受另一个 Rectangle 实例作为参数的 can_hold 方法实现

当运行包含清单 5-14 中 main 函数的代码时,将得到期望输出。方法可以接受多个参数,这些参数加在 self 参数之后,其工作方式与函数中的参数相同。

关联函数

所有定义在 impl 块中的函数称为关联函数,因为它们与 impl 后面的类型相关联。我们可以定义不以 self 为首个参数(因此不是方法)的关联函数,因为这些函数不需要某个类型实例即可工作。例如,我们已经使用过 String 类型上的 String::from 函数就是这样一种关联函数。

非方法形式的关联函数通常用于构造器,用来返回结构体的新实例。这类构造器常被命名为 new,但 new 并不是特殊名称,也没有内置于语言中。例如,我们可以提供一个名为 square 的关联函数,它只有一个尺寸参数,并将其同时赋给宽和高,从而更方便地创建正方形矩形,而无需重复指定相同值:

文件名:src/main.rs

impl Rectangle {fn square(size: u32) -> Self {Self {width: size,height: size,}}
}

Self 在返回类型及函数体中是对 impl 后面出现类型(此处即 Rectangle)的别名。

调用这个关联函数时,使用 :: 符号连接结构体名称,例如 let sq = Rectangle::square(3); 。该功能由结构体命名空间限定符管理;:: 符号既用于关联函数,也用于模块创建命名空间。本书第7章将讨论模块内容。

多个 impl 块

每个结构体允许拥有多个 impl 块。例如,清单5-15等价于下面分开各自实现每个方法的代码,如清单5-16所示:

impl Rectangle {fn area(&self) -> u32 {self.width * self.height}
}impl Rectangle {fncan_hold(&self, other:&Rectangle)->bool{self.width>other.width&&self.height>other.height}
}

清单5-16:用多个impl块重写清单5-15

这里没必要拆分成多个impl块,但这种语法是合法的。在第10章讲解泛型和特征时,会看到多重impl块派上用场的时候。

总结

结构体让你能够创建符合领域需求、自定义含义的数据类型。利用结构体,可以把相关数据组合起来并给每部分命名,使代码更加明晰。在 impl 块里,你能定义与该类型相关联的方法,其中“方法”是一种特殊形式、绑定到具体实例行为上的关联函 数。但自定义数据类型不仅限于struct,让我们转向 Rust 枚举(enum),再增加一项强力工具吧!

http://www.xdnf.cn/news/1181143.html

相关文章:

  • C++STL系列之set和map系列
  • 基于python django的农业可视化系统,以奶牛牧场为例
  • 用 Function Call 让 AI 主动调用函数(超入门级示例)|保姆级大模型应用开发实战
  • SpringBoot航空订票系统的设计与实现
  • 进阶系统策略
  • 技术赋能多元探索:我的技术成长与行业洞察
  • Linux应用开发基础知识——进程学习2(exec函数、system函数、popen函数)(三)
  • 斐波那契数列策略
  • 人形机器人_双足行走动力学:Maxwell模型及在拟合肌腱特性中的应用
  • Java学习----原型模式
  • 使用Claude Code从零到一打造一个现代化的GitHub Star项目管理器
  • day46day47 通道注意力
  • 无源域自适应综合研究【2】
  • C++ 性能优化
  • 力扣 hot100 Day54
  • pytest中使用skip跳过某个函数
  • 无人机速度模块技术要点分析
  • 第三章:掌握 Redis 存储与获取数据的核心命令
  • MNIST 手写数字识别模型分析
  • 秋叶sd-webui频繁出现生成后无反应的问题
  • 【Web APIs】JavaScript 节点操作 ⑧ ( 删除节点 - removeChild 函数 | 删除节点 - 代码示例 | 删除网页评论案例 )
  • 算法竞赛阶段二-数据结构(34)数据结构链表STL vector
  • 【PyTorch】图像二分类项目-部署
  • Spring Boot 3整合Spring AI实战:9轮面试对话解析AI应用开发
  • HttpServletRequest深度解析:Java Web开发的核心组件
  • PyTorch数据选取与索引详解:从入门到高效实践
  • Vue3 面试题及详细答案120道(91-105 )
  • 开立医疗2026年校园招聘
  • 论文复现-windows电脑在pycharm中运行.sh文件
  • 工具篇之开发IDEA插件的实战分享