W3Cschool
恭喜您成為首批注冊用戶
獲得88經驗值獎勵
ch05-03-method-syntax.md
commit dd7e05275822d6cf790bcdae6983b3234141b5e7
方法(method)與函數(shù)類似:它們使用 fn
關鍵字和名稱聲明,可以擁有參數(shù)和返回值,同時包含在某處調用該方法時會執(zhí)行的代碼。不過方法與函數(shù)是不同的,因為它們在結構體的上下文中被定義(或者是枚舉或 trait 對象的上下文,將分別在第六章和第十七章講解),并且它們第一個參數(shù)總是 self
,它代表調用該方法的結構體實例。
讓我們把前面實現(xiàn)的獲取一個 Rectangle
實例作為參數(shù)的 area
函數(shù),改寫成一個定義于 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
方法
為了使函數(shù)定義于 Rectangle
的上下文中,我們開始了一個 impl
塊(impl
是 implementation 的縮寫),這個 impl
塊中的所有內容都將與 Rectangle
類型相關聯(lián)。接著將 area
函數(shù)移動到 impl
大括號中,并將簽名中的第一個(在這里也是唯一一個)參數(shù)和函數(shù)體中其他地方的對應參數(shù)改成 self
。然后在 main
中將我們先前調用 area
方法并傳遞 rect1
作為參數(shù)的地方,改成使用 方法語法(method syntax)在 Rectangle
實例上調用 area
方法。方法語法獲取一個實例并加上一個點號,后跟方法名、圓括號以及任何參數(shù)。
在 area
的簽名中,使用 &self
來替代 rectangle: &Rectangle
,&self
實際上是 self: &Self
的縮寫。在一個 impl
塊中,Self
類型是 impl
塊的類型的別名。方法的第一個參數(shù)必須有一個名為 self
的Self
類型的參數(shù),所以
Rust 讓你在第一個參數(shù)位置上只用 self
這個名字來縮寫。注意,我們仍然需要在 self
前面使用 &
來表示這個方法借用了 Self
實例,就像我們在 rectangle: &Rectangle
中做的那樣。方法可以選擇獲得 self
的所有權,或者像我們這里一樣不可變地借用 self
,或者可變地借用 self
,就跟其他參數(shù)一樣。
這里選擇 &self
的理由跟在函數(shù)版本中使用 &Rectangle
是相同的:我們并不想獲取所有權,只希望能夠讀取結構體中的數(shù)據(jù),而不是寫入。如果想要在方法中改變調用方法的實例,需要將第一個參數(shù)改為 &mut self
。通過僅僅使用 self
作為第一個參數(shù)來使方法獲取實例的所有權是很少見的;這種技術通常用在當方法將 self
轉換成別的實例的時候,這時我們想要防止調用者在轉換之后使用原始的實例。
使用方法替代函數(shù),除了可使用方法語法和不需要在每個函數(shù)簽名中重復 self
的類型之外,其主要好處在于組織性。我們將某個類型實例能做的所有事情都一起放入 impl
塊中,而不是讓將來的用戶在我們的庫中到處尋找 Rectangle
的功能。
請注意,我們可以選擇將方法的名稱與結構中的一個字段相同。例如,我們可以在 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
,等于 0 時則返回 false
:我們可以出于任何目的,在同名的方法中使用同名的字段。在 main
中,當我們在 rect1.width
后面加上括號時。Rust
知道我們指的是方法 width
。當我們不使用圓括號時,Rust 知道我們指的是字段 width
。
通常,但并不總是如此,與字段同名的方法將被定義為只返回字段中的值,而不做其他事情。這樣的方法被稱為 getters,Rust 并不像其他一些語言那樣為結構字段自動實現(xiàn)它們。Getters 很有用,因為你可以把字段變成私有的,但方法是公共的,這樣就可以把對字段的只讀訪問作為該類型公共 API 的一部分。我們將在第七章中討論什么是公有和私有,以及如何將一個字段或方法指定為公有或私有。
-> 運算符到哪去了?
在 C/C++ 語言中,有兩個不同的運算符來調用方法:
.
直接在對象上調用方法,而->
在一個對象的指針上調用方法,這時需要先解引用(dereference)指針。換句話說,如果object
是一個指針,那么object->something()
就像(*object).something()
一樣。
Rust 并沒有一個與
->
等效的運算符;相反,Rust 有一個叫 自動引用和解引用(automatic referencing and dereferencing)的功能。方法調用是 Rust 中少數(shù)幾個擁有這種行為的地方。
它是這樣工作的:當使用
object.something()
調用方法時,Rust 會自動為object
添加&
、&mut
或*
以便使object
與方法簽名匹配。也就是說,這些代碼是等價的:
p1.distance(&p2); (&p1).distance(&p2);
第一行看起來簡潔的多。這種自動引用的行為之所以有效,是因為方法有一個明確的接收者————
self
的類型。在給出接收者和方法名的前提下,Rust 可以明確地計算出方法是僅僅讀?。?code>&self),做出修改(&mut self
)或者是獲取所有權(self
)。事實上,Rust 對方法接收者的隱式借用讓所有權在實踐中更友好。
讓我們通過實現(xiàn) Rectangle
結構體上的另一方法來練習使用方法。這回,我們讓一個 Rectangle
的實例獲取另一個 Rectangle
實例,如果 self
(第一個 Rectangle
)能完全包含第二個長方形則返回 true
;否則返回 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:使用還未實現(xiàn)的 can_hold
方法
同時我們希望看到如下輸出,因為 rect2
的兩個維度都小于 rect1
,而 rect3
比 rect1
要寬:
Can rect1 hold rect2? true
Can rect1 hold rect3? false
因為我們想定義一個方法,所以它應該位于 impl Rectangle
塊中。方法名是 can_hold
,并且它會獲取另一個 Rectangle
的不可變借用作為參數(shù)。通過觀察調用方法的代碼可以看出參數(shù)是什么類型的:rect1.can_hold(&rect2)
傳入了 &rect2
,它是一個 Rectangle
的實例 rect2
的不可變借用。這是可以理解的,因為我們只需要讀取 rect2
(而不是寫入,這意味著我們需要一個不可變借用),而且希望 main
保持 rect2
的所有權,這樣就可以在調用這個方法后繼續(xù)使用它。can_hold
的返回值是一個布爾值,其實現(xiàn)會分別檢查 self
的寬高是否都大于另一個 Rectangle
。讓我們在示例 5-13 的 impl
塊中增加這個新的 can_hold
方法,如示例 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
上實現(xiàn) can_hold
方法,它獲取另一個 Rectangle
實例作為參數(shù)
如果結合示例 5-14 的 main
函數(shù)來運行,就會看到期望的輸出。在方法簽名中,可以在 self
后增加多個參數(shù),而且這些參數(shù)就像函數(shù)中的參數(shù)一樣工作。
所有在 impl
塊中定義的函數(shù)被稱為 關聯(lián)函數(shù)(associated functions),因為它們與 impl
后面命名的類型相關。我們可以定義不以 self
為第一參數(shù)的關聯(lián)函數(shù)(因此不是方法),因為它們并不作用于一個結構體的實例。我們已經使用了一個這樣的函數(shù):在 String
類型上定義的 String::from
函數(shù)。
不是方法的關聯(lián)函數(shù)經常被用作返回一個結構體新實例的構造函數(shù)。這些函數(shù)的名稱通常為 new
,但 new
并不是一個關鍵字。例如我們可以提供一個叫做 square
關聯(lián)函數(shù),它接受一個維度參數(shù)并且同時作為寬和高,這樣可以更輕松的創(chuàng)建一個正方形 Rectangle
而不必指定兩次同樣的值:
文件名: src/main.rs
impl Rectangle {
fn square(size: u32) -> Self {
Self {
width: size,
height: size,
}
}
}
關鍵字 Self
在函數(shù)的返回類型中代指在 impl
關鍵字后出現(xiàn)的類型,在這里是 Rectangle
使用結構體名和 ::
語法來調用這個關聯(lián)函數(shù):比如 let sq = Rectangle::square(3);
。這個函數(shù)位于結構體的命名空間中:::
語法用于關聯(lián)函數(shù)和模塊創(chuàng)建的命名空間。第七章會講到模塊。
每個結構體都允許擁有多個 impl
塊。例如,示例 5-16 中的代碼等同于示例 5-15,但每個方法有其自己的 impl
塊。
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
示例 5-16:使用多個 impl
塊重寫示例 5-15
這里沒有理由將這些方法分散在多個 impl
塊中,不過這是有效的語法。第十章討論泛型和 trait 時會看到實用的多 impl
塊的用例。
結構體讓你可以創(chuàng)建出在你的領域中有意義的自定義類型。通過結構體,我們可以將相關聯(lián)的數(shù)據(jù)片段聯(lián)系起來并命名它們,這樣可以使得代碼更加清晰。在 impl
塊中,你可以定義與你的類型相關聯(lián)的函數(shù),而方法是一種相關聯(lián)的函數(shù),讓你指定結構體的實例所具有的行為。
但結構體并不是創(chuàng)建自定義類型的唯一方法:讓我們轉向 Rust 的枚舉功能,為你的工具箱再添一個工具。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: