【Rust自学】5.3. struct的方法(Method)
喜欢的话别忘了点赞、收藏加关注哦,对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
5.3.1. 什么是方法(Method)
方法和函数类似,也是用fn
关键字进行声明,方法也有名称,也有参数,也有返回值。但方法和函数也有不同之处:
- 方法在struct(或枚举或trait对象)的上下文中定义
- 方法的第一个参数总是
self
,表示方法所在的(被调用的)struct实例,类似于Python中的self
和JS中的this
。
5.3.2. 方法的实际应用
接下来还是看例子,以上一篇文章的代码为例:
struct Rectangle {
width: u32,
length: u32,
}
fn main() {
let rectangle = Rectangle{
width: 30,
length: 50,
};
println!("{}", area(&rectangle));
}
fn area(dim:&Rectangle) -> u32 {
dim.width * dim.length
}
area
这个函数的作用是计算面积,但它很特别,它只适用于矩形而不适用于其他形状或者是其他的类型。如果后面代码中要加上计算其他图形的面积的函数,那么area
这个名字就要混淆。如果改名成ractangle_area
的话又太麻烦,main
函数里所有调用了这个函数的地方都要改。
所以如果能把存储矩形长款的Rectangle
结构体和只能计算矩形面积area
这个函数结合到一起就是最好的。
对于这种需求,Rust提供了implementation
(中文意为实现),其关键字是impl
,后边跟着struct名,加上{}
,在里面像定义普通函数一样定义方法就行。
对于这个例子,struct名就是Rectangle
,把定义area
函数的代码剪贴到{}
内即可。
impl Rectangle {
fn area(dim:&Rectangle) -> u32 {
dim.width * dim.length
}
}
但注意这里的代码还不是方法,因为方法的第一个参数必须是self
,现在的代码叫关联函数,下文会讲。
这么写是没有问题的,但还可以进一步简化。上文中说到了方法的第一个参数总是self
,所以这里也可以改一下:
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.length
}
}
你当前写的这个方法绑定在谁上,self
指的就是谁,这个代码中area
这个函数被绑定在Rectangle
上,所以self
就指的是Rectangle
,area
的参数不用拿走所有权,所以在self
前面加上&
表示印引用。
当然这么改之后,main
函数里的函数调用也会改,从函数的调用改到方法的调用——实例.方法名(参数)
:
fn main() {
let rectangle = Rectangle{
width: 30,
length: 50,
};
println!("{}", rectangle.area());
}
rectangle.area()
的括号中不写东西是因为area
方法在定义时只使用了&self
作为参数,表示这个方法借用了self
(即rectangle
实例)的不可变引用。在调用area
时,你不需要显式地传递这个实例,因为方法调用已经隐式地知道self
是rectangle
。
整体代码如下:
struct Rectangle {
width: u32,
length: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.length
}
}
fn main() {
let rectangle = Rectangle{
width: 30,
length: 50,
};
println!("{}", rectangle.area());
}
输出:
1500
5.3.3. 如何定义方法
在上面的实际应用中已经写过一遍了,所以这里就只做总结:
- 在
impl
里定义方法 - 方法的第一个参数可以是
self
、&self
或是&mut self
。可以是获得所有权、引用或可变引用,这点和其他参数一样。 - 方法可以帮助更好的组织代码,因为可以把某个类型的方法都放在
impl
块里面,避免在整个代码库里搜索struct它相关的行为了。
5.3.4. 方法调用的运算符
在C/C++中,调用方法有两种运算符
->
:其格式为object->something()
,调用指针指向的对象上的方法就使用这一种(也就是object
为指针时).
:其格式为object.something()
,调用对象上的方法就使用这种(也就是object
不为指针,是个对象时)
而object->something()
实际上是语法糖,它等同于(*object).something()
,*
表示解引用。两者的流程都是先解引用,得到对象,再在对象上调用方法。
Rust提供了自动引用/解引用的特性。也就是说,在调用方法时,Rust根据情况自动添加&
、&mut
或*
,以便object
可以匹配方法的签名。这点和Go语言一样。
举个例子,下面这两行代码效果相同:
point1.distance(&point2);
(&point1).distance(&point2);
Rust会根据情况自动在point1
前加上&
。
5.3.5. 方法的参数
方法除了self
也可以带其他参数,一个或多个都可以。
举个例子,在5.3.2的代码基础上加一个判断矩形是否能容纳下另一个长方形的功能(不考虑斜着放,也不考虑矩形的长比宽长的情况)
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.length > other.length
}
}
逻辑非常好想,只要矩形的长和宽都比另一个大就行。
然后再在main
函数里写几个Rectangle
的实例,输出比较结果看看有没有问题就行,以下是完整代码:
struct Rectangle {
width: u32,
length: u32,
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.length > other.length
}
}
fn main() {
let rect1 = Rectangle{
width: 30,
length: 50,
};
let rect2 = Rectangle{
width: 10,
length: 40,
};
println!("{}", rect1.can_hold(&rect2));
}
输出:
true
5.3.6. 关联函数
可以在impl
块里定义不把self
作为第一个参数的函数,叫关联函数(不是方法)。它不是在实例上调用的,但它与这个类型有关联。例如: String::from()
就是String
这个类型上叫做from
的关联函数。
关联函数通常用于构造器,也就是用来被创建关联类型的一个实例。
比如说,在5.3.2的代码基础上加一个构建正方形的构造器(正方形也是特殊的矩形):
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle{
width: size,
length: size,
}
}
}
参数只需要一个,因为构造正方形只需要一个边长。
在main
函数里调用一下这个关联函数试试,其格式为类型名::函数名(参数)
,以下是完整代码:
#[derive(Debug)]
struct Rectangle {
width: u32,
length: u32,
}
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle{
width: size,
length: size,
}
}
}
fn main() {
let square = Rectangle::square(10);
println!("{:?}", square);
}
输出:
Rectangle { width: 10, length: 10 }
::
不仅可以用于关联函数,也可以用于模块创建命名空间(以后会讲)
5.3.7. 多个impl块
每个struct允许拥有多个impl
块。
比如我要把这篇文章里写过的所有的方法和关联函数都写到代码里。
可以这么写(多个impl
块):
#[derive(Debug)]
struct Rectangle {
width: u32,
length: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.length
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.length > other.length
}
}
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle{
width: size,
length: size,
}
}
}
fn main() {
let square = Rectangle::square(10);
println!("{:?}", square);
}
也可以这么写(合在一个impl
块里):
#[derive(Debug)]
struct Rectangle {
width: u32,
length: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.length
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.length > other.length
}
fn square(size: u32) -> Rectangle {
Rectangle{
width: size,
length: size,
}
}
}
fn main() {
let square = Rectangle::square(10);
println!("{:?}", square);
}