W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
ch05-02-example-structs.md
commit dd7e05275822d6cf790bcdae6983b3234141b5e7
為了理解何時會需要使用結(jié)構(gòu)體,讓我們編寫一個計算長方形面積的程序。我們會從單獨的變量開始,接著重構(gòu)程序直到使用結(jié)構(gòu)體替代他們?yōu)橹埂?br>
使用 Cargo 新建一個叫做 rectangles 的二進制程序,它獲取以像素為單位的長方形的寬度和高度,并計算出長方形的面積。示例 5-8 顯示了位于項目的 src/main.rs 中的小程序,它剛剛好實現(xiàn)此功能:
文件名: src/main.rs
fn main() {
let width1 = 30;
let height1 = 50;
println!(
"The area of the rectangle is {} square pixels.",
area(width1, height1)
);
}
fn area(width: u32, height: u32) -> u32 {
width * height
}
示例 5-8:通過分別指定長方形的寬和高的變量來計算長方形面積
現(xiàn)在使用 cargo run
運行程序:
$ cargo run
Compiling rectangles v0.1.0 (file:///projects/rectangles)
Finished dev [unoptimized + debuginfo] target(s) in 0.42s
Running `target/debug/rectangles`
The area of the rectangle is 1500 square pixels.
這個示例代碼在調(diào)用 area
函數(shù)時傳入每個維度,雖然可以正確計算出長方形的面積,但我們?nèi)匀豢梢孕薷倪@段代碼來使它的意義更加明確,并且增加可讀性。
這些代碼的問題突顯在 area
的簽名上:
fn area(width: u32, height: u32) -> u32 {
函數(shù) area
本應該計算一個長方形的面積,不過函數(shù)卻有兩個參數(shù)。這兩個參數(shù)是相關(guān)聯(lián)的,不過程序本身卻沒有表現(xiàn)出這一點。將長度和寬度組合在一起將更易懂也更易處理。第三章的 “元組類型” 部分已經(jīng)討論過了一種可行的方法:元組。
示例 5-9 展示了使用元組的另一個程序版本。
文件名: src/main.rs
fn main() {
let rect1 = (30, 50);
println!(
"The area of the rectangle is {} square pixels.",
area(rect1)
);
}
fn area(dimensions: (u32, u32)) -> u32 {
dimensions.0 * dimensions.1
}
示例 5-9:使用元組來指定長方形的寬高
在某種程度上說,這個程序更好一點了。元組幫助我們增加了一些結(jié)構(gòu)性,并且現(xiàn)在只需傳一個參數(shù)。不過在另一方面,這個版本卻有一點不明確了:元組并沒有給出元素的名稱,所以計算變得更費解了,因為不得不使用索引來獲取元組的每一部分:
在計算面積時將寬和高弄混倒無關(guān)緊要,不過當在屏幕上繪制長方形時就有問題了!我們必須牢記 width
的元組索引是 0
,height
的元組索引是 1
。如果其他人要使用這些代碼,他們必須要搞清楚這一點,并也要牢記于心。很容易忘記或者混淆這些值而造成錯誤,因為我們沒有在代碼中傳達數(shù)據(jù)的意圖。
我們使用結(jié)構(gòu)體為數(shù)據(jù)命名來為其賦予意義。我們可以將我們正在使用的元組轉(zhuǎn)換成一個有整體名稱而且每個部分也有對應名字的結(jié)構(gòu)體,如示例 5-10 所示:
文件名: src/main.rs
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!(
"The area of the rectangle is {} square pixels.",
area(&rect1)
);
}
fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}
示例 5-10:定義 Rectangle
結(jié)構(gòu)體
這里我們定義了一個結(jié)構(gòu)體并稱其為 Rectangle
。在大括號中定義了字段 width
和 height
,類型都是 u32
。接著在 main
中,我們創(chuàng)建了一個具體的 Rectangle
實例,它的寬是 30,高是 50。
函數(shù) area
現(xiàn)在被定義為接收一個名叫 rectangle
的參數(shù),其類型是一個結(jié)構(gòu)體 Rectangle
實例的不可變借用。第四章講到過,我們希望借用結(jié)構(gòu)體而不是獲取它的所有權(quán),這樣 main
函數(shù)就可以保持 rect1
的所有權(quán)并繼續(xù)使用它,所以這就是為什么在函數(shù)簽名和調(diào)用的地方會有 &
。
area
函數(shù)訪問 Rectangle
實例的 width
和 height
字段(注意,訪問對結(jié)構(gòu)體的引用的字段不會移動字段的所有權(quán),這就是為什么你經(jīng)??吹綄Y(jié)構(gòu)體的引用)。area
的函數(shù)簽名現(xiàn)在明確的闡述了我們的意圖:使用 Rectangle
的 width
和 height
字段,計算 Rectangle
的面積。這表明寬高是相互聯(lián)系的,并為這些值提供了描述性的名稱而不是使用元組的索引值 0
和 1
。結(jié)構(gòu)體勝在更清晰明了。
在調(diào)試程序時打印出 Rectangle
實例來查看其所有字段的值非常有用。示例 5-11 像前面章節(jié)那樣嘗試使用 println!
宏。但這并不行。
文件名: src/main.rs
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {}", rect1);
}
示例 5-11:嘗試打印出 Rectangle
實例
當我們運行這個代碼時,會出現(xiàn)帶有如下核心信息的錯誤:
error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`
println!
宏能處理很多類型的格式,不過,{}
默認告訴 println!
使用被稱為 Display
的格式:意在提供給直接終端用戶查看的輸出。目前為止見過的基本類型都默認實現(xiàn)了 Display
,因為它就是向用戶展示 1
或其他任何基本類型的唯一方式。不過對于結(jié)構(gòu)體,println!
應該用來輸出的格式是不明確的,因為這有更多顯示的可能性:是否需要逗號?需要打印出大括號嗎?所有字段都應該顯示嗎?由于這種不確定性,Rust 不會嘗試猜測我們的意圖,所以結(jié)構(gòu)體并沒有提供一個 Display
實現(xiàn)來使用 println!
與 {}
占位符。
但是如果我們繼續(xù)閱讀錯誤,將會發(fā)現(xiàn)這個有幫助的信息:
= help: the trait `std::fmt::Display` is not implemented for `Rectangle`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
讓我們來試試!現(xiàn)在 println!
宏調(diào)用看起來像 println!("rect1 is {:?}", rect1);
這樣。在 {}
中加入 :?
指示符告訴 println!
我們想要使用叫做 Debug
的輸出格式。Debug
是一個 trait,它允許我們以一種對開發(fā)者有幫助的方式打印結(jié)構(gòu)體,以便當我們調(diào)試代碼時能看到它的值。
這樣調(diào)整后再次運行程序。見鬼了!仍然能看到一個錯誤:
error[E0277]: `Rectangle` doesn't implement `Debug`
不過編譯器又一次給出了一個有幫助的信息:
= help: the trait `Debug` is not implemented for `Rectangle`
= note: add `#[derive(Debug)]` to `Rectangle` or manually `impl Debug for Rectangle`
Rust 確實 包含了打印出調(diào)試信息的功能,不過我們必須為結(jié)構(gòu)體顯式選擇這個功能。為此,在結(jié)構(gòu)體定義之前加上外部屬性 #[derive(Debug)]
,如示例 5-12 所示:
文件名: src/main.rs
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {:?}", rect1);
}
示例 5-12:增加屬性來派生 Debug
trait,并使用調(diào)試格式打印 Rectangle
實例
現(xiàn)在我們再運行這個程序時,就不會有任何錯誤,并會出現(xiàn)如下輸出:
$ cargo run
Compiling rectangles v0.1.0 (file:///projects/rectangles)
Finished dev [unoptimized + debuginfo] target(s) in 0.48s
Running `target/debug/rectangles`
rect1 is Rectangle { width: 30, height: 50 }
好極了!這并不是最漂亮的輸出,不過它顯示這個實例的所有字段,毫無疑問這對調(diào)試有幫助。當我們有一個更大的結(jié)構(gòu)體時,能有更易讀一點的輸出就好了,為此可以使用 {:#?}
替換 println!
字符串中的 {:?}
。在這個例子中使用 {:#?}
風格將會輸出:
$ cargo run
Compiling rectangles v0.1.0 (file:///projects/rectangles)
Finished dev [unoptimized + debuginfo] target(s) in 0.48s
Running `target/debug/rectangles`
rect1 is Rectangle {
width: 30,
height: 50,
}
另一種使用 Debug
格式打印數(shù)值的方法是使用 dbg!
宏。dbg!
宏接收一個表達式的所有權(quán)(與 println!
宏相反,后者接收的是引用),打印出代碼中調(diào)用 dbg! 宏時所在的文件和行號,以及該表達式的結(jié)果值,并返回該值的所有權(quán)。
注意:調(diào)用 ?
dbg!
? 宏會打印到標準錯誤控制臺流(?stderr
?),與 ?println!
? 不同,后者會打印到標準輸出控制臺流(?stdout
?)。我們將在第十二章 “將錯誤信息寫入標準錯誤而不是標準輸出” 一節(jié)中更多地討論 ?stderr
?和 ?stdout
?。
下面是一個例子,我們對分配給 width
字段的值以及 rect1
中整個結(jié)構(gòu)的值感興趣。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let scale = 2;
let rect1 = Rectangle {
width: dbg!(30 * scale),
height: 50,
};
dbg!(&rect1);
}
我們可以把 dbg!
放在表達式 30 * scale
周圍,因為 dbg!
返回表達式的值的所有權(quán),所以 width
字段將獲得相同的值,就像我們在那里沒有 dbg!
調(diào)用一樣。我們不希望 dbg!
擁有 rect1
的所有權(quán),所以我們在下一次調(diào)用 dbg!
時傳遞一個引用。下面是這個例子的輸出結(jié)果:
$ cargo run
Compiling rectangles v0.1.0 (file:///projects/rectangles)
Finished dev [unoptimized + debuginfo] target(s) in 0.61s
Running `target/debug/rectangles`
[src/main.rs:10] 30 * scale = 60
[src/main.rs:14] &rect1 = Rectangle {
width: 60,
height: 50,
}
我們可以看到第一點輸出來自 src/main.rs 第 10 行,我們正在調(diào)試表達式 30 * scale
,其結(jié)果值是60(為整數(shù)實現(xiàn)的 Debug
格式化是只打印它們的值)。在 src/main.rs 第 14行 的 dbg!
調(diào)用輸出 &rect1
的值,即 Rectangle
結(jié)構(gòu)。這個輸出使用了更為易讀的 Debug
格式。當你試圖弄清楚你的代碼在做什么時,dbg!
宏可能真的很有幫助!
除了 Debug
trait,Rust 還為我們提供了很多可以通過 derive
屬性來使用的 trait,他們可以為我們的自定義類型增加實用的行為。附錄 C 中列出了這些 trait 和行為。第十章會介紹如何通過自定義行為來實現(xiàn)這些 trait,同時還有如何創(chuàng)建你自己的 trait。除了 derive
之外,還有很多屬性;更多信息請參見 Rust Reference 的 Attributes 部分。
我們的 area
函數(shù)是非常特殊的,它只計算長方形的面積。如果這個行為與 Rectangle
結(jié)構(gòu)體再結(jié)合得更緊密一些就更好了,因為它不能用于其他類型?,F(xiàn)在讓我們看看如何繼續(xù)重構(gòu)這些代碼,來將 area
函數(shù)協(xié)調(diào)進 Rectangle
類型定義的 area
方法 中。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: