本篇文章包含的內容
- 1 結構體
- 1.1 定義和初始化結構體
- 1.2 Tuple Struct
- 1.3 結構體方法(Rust 面向對象)
- 1.4 關聯函數
- 2 枚舉
- 2.1 定義和使用枚舉
- 2.2 將數據附加到枚舉的變體中
- 2.3 Option 枚舉
- 2.4 模式匹配
- 2.4.1 match語句
- 2.4.2 if let語句
1 結構體
1.1 定義和初始化結構體
定義結構體必須顯式指定結構體成員變量的類型。實例化結構體時需要對結構體的每個成員變量賦值,使用下面的寫法定義和初始化結構體:
fn main() {let user1 = User {email: String::from("example@example.com"),username: String::from("Nikky"),active: true,sign_in_count: 556,};
}struct User {username: String,email: String,sign_in_count: u64,active: bool,
}
獲取或者修改結構體實例中某個字段的值的方式和其他語言類似,使用點標記法即可,例如user1.email
。上面例子中的user1
默認也是不可變的,如果要修改某個字段,需要將這個結構體聲明為可變的,即let mut user1 = User {...}
。需要注意,如果結構體實例是可變的,那么其中的每個字段都是可變的,Rust不允許存在一部分字段不變,另一部分字段可變的結構體。
理所當然的,結構體也可以是函數的返回值:
fn build_user(email: String, username: String) -> User {User {email: emailusername: usernameactive: true,sign_in_count: 556,}
}
上面的例子中,初始化變量名和字段的變量名相同,那么初始化時可以用下面的方法簡寫(又是一個語法糖):
fn build_user(email: String, username: String) -> User {User {email,username,active: true,sign_in_count: 556,}
}
當你想通過基于某個結構體實例來初始化另一個結構體實例時,可以使用struct更新語法:
let user1 = User {email: String::from("example@example.com"),username: String::from("Nikky"),active: true,sign_in_count: 556,};let user2 = User {email: String::from("another_example@example.com"),username: String::from("Tom"),..user1};
上面的例子中定義email
和username
的類型為String
而不是&str
,使得該struct實例擁有其所有的數據(所有權),并且只要結構體實例是有效的,那么其中的數據也一定有效。struct中也可以存放引用,但是必須使用生命周期。生命周期保證只要struct實例是有效的,那么其中的引用也一定有效。生命周期的概念之后才會涉及。
自定義的struct
往往沒有Display
方法,所以無法被println!()
直接輸出,如果要使用println!()
輸出,可以采用下面的寫法:
#[derive(Debug)] //
struct Rectangle {width: u32,length: u32,
}fn main() {let rect = Rectangle {width: 30,length: 50,};println!("{}", area(&rect));println!("{:?}", rect); // 多行打印使用{:#?}
}fn area(rect: &Rectangle) -> u32 {rect.width * rect.length
}
需要注意,打印結構體時必須使用#[derive(Debug)]
注解,使得Rectangle
派生于Debug
這個trait。
1.2 Tuple Struct
Rust中允許我們定一個類似tuple的struct,它就是Tuple Struct,它有一個整體的結構體名,但是其中的元素可以沒有名字。適用于想給整個tuple起名,讓其不同于其他的Tuple,但是又不需要給其中的每個元素起名的情況。
fn main() {struct Color(i32, i32, i32);struct Point(i32, i32, i32);let black = Color(0, 0, 0);let origin = Point(0, 0, 0); // black和origin數據類似,但是是不同的類型
}
1.3 結構體方法(Rust 面向對象)
在C/C++中,class
說白了就是擁有很多方法的struct
,那么Rust中是否可以為結構體添加方法呢?當然可以。看下面這個例子:
struct Rectangle {width: u32,length: u32,
}impl Rectangle {fn area(&self) -> u32 { // 借用self,不需要其所有權self.width * self.length}
}fn main() {let rect = Rectangle {width: 30,length: 50,};println!("{}", rect.area());
}
Rust的方法是在struct
(或者enum
、trait
對象)的上下文中使用impl
關鍵字(implementation)定義的。方法的第一個參數總是self
。
Rust會自動引用或者自動解引用,在調用方法時,rect.area()
就相當于(&rect).area()
,Rust會自動在變量前添加&
、&mut
或者*
。以便于實例匹配方法的簽名。
一個impl
里可以定義多個方法,也可以在多個impl
塊中定義,方法除了自身以外也可以有其他參數:
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 main() {let rect1 = Rectangle {width: 30,length: 50,};let rect2 = Rectangle {width: 10,length: 20,};println!("{}", rect1.area());println!("{}", rect1.can_hold(&rect2));
}
1.4 關聯函數
在impl
塊中也可以定義函數,這個函數不是對象的方法,但是它又與struct有一定的關聯,我們把這種函數稱為關聯函數。通常使用關聯函數作為對象的構造器,例如常用的String::from("")
函數構造一個String。
#[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 rect1 = Rectangle {width: 30,length: 50,};let rect2 = Rectangle {width: 10,length: 20,};let s = Rectangle::square(20);println!("{}", rect1.area());println!("{}", rect1.can_hold(&rect2));println!("{:?}", s);
}
2 枚舉
2.1 定義和使用枚舉
枚舉(Enum)允許我們使用確定的可能值定義一個類型。枚舉的可能的值稱為變體。枚舉的最基礎作用其實就是提高代碼的可讀性,這和C/C++的枚舉沒有本質的區別。
enum IpAddrKind {V4,V6,
}fn main() {let four_ip = IpAddrKind::V4;let six_ip = IpAddrKind::V6;route(four_ip);route(six_ip);route(IpAddrKind::V4);
}fn route(ip_kind:IpAddrKind) {// some code...
}
自然地,枚舉可以是結構體的成員變量:
enum IpAddrKind {V4,V6,
}struct IpAddr {ip_addr_kind: IpAddrKind,ip_addr: String,
}fn main() {let home = IpAddr {ip_addr: String::from("127.0.0.1"),ip_addr_kind: IpAddrKind::V4,};let loopback = IpAddr {ip_addr: String::from("::1"),ip_addr_kind: IpAddrKind::V6,};
}
2.2 將數據附加到枚舉的變體中
枚舉的變體中可以添加一些附加數據,這樣可以讓我們不用定義額外的結構體存儲其他的信息:
enum IpAddrKind {V4(u8, u8, u8, u8),V6(String),
}fn main() {let home = IpAddrKind::V4(192, 168, 0, 1);let loopback = IpAddrKind::V6(String::from("::1"));
}
標準庫的IpAddr
就使用了類似的設計,不過標準庫中枚舉IpAddr
中嵌入的是結構體struct。說明我么可以在枚舉中嵌入任意的數據類型,甚至嵌入另外的枚舉。枚舉也可以定義方法,枚舉方法的第一個參數也是self
,調用依然采用.
進行調用。看下面這個例子:
enum Message {Quit,Move {x: i32, y: i32}, // 關聯一個匿名結構體Write(String),ChangeColor(i32, i32, i32),
}impl Message {fn call(&self) {// some code ...}
}fn main() {let q = Message::Quit;let m = Message::Move {x: 1, y: 2};let w = Message::Write(String::from("hello"));let c = Message::ChangeColor(0, 128, 0);q.call();
}
2.3 Option 枚舉
Option枚舉位于標準庫中,它是預導入(Prelude)的。它主要是為了解決其他語言中一個值可能存在,并且可能是空值null
的情況。Rust為了解決null
的弊端,直接摒棄了null
,取而代之的是Option枚舉,使得開發者要想使用null
,則必須同時處理值存在和不存在兩種情況。
Option在標準庫中的定義如下所示,T
是泛型參數。Option枚舉是預導入的,它的兩個變體也是預導入的,所以程序中可以直接使用。
// 標準庫中的定義
enum Option<T> {Some(T),None,
}
let some_number = Some(1);
let some_string = Some("hello");let absent_number: Option<i32> = None;
2.4 模式匹配
2.4.1 match語句
match
是Rust中的一個強大的控制流運算符,它允許一個值與一系列模式進行依次匹配,這個“模式”可以是子面值,變量名或者是通配符,匹配成功后執行對應的代碼塊。
enum Coin {Penny,Nickel,Dime,Quarter,
}fn value_in_cents(coin: Coin) -> u8 {match coin {Coin::Penny => 1,Coin::Nickel => 5,Coin::Dime => 10,Coin::Quarter => 25,}
}fn main() {}
匹配到的模式可以關聯被匹配對象的部分值,利用這個特性(語法糖?),我么可以方便地提取枚舉中的值:
#[derive(Debug)]
enum UsState {Alabama,Alaska,
}enum Coin {Penny,Nickel,Dime,Quarter(UsState), // 關聯枚舉數據
}fn value_in_cents(coin: Coin) -> u8 {match coin {Coin::Penny => 1,Coin::Nickel => 5,Coin::Dime => 10,Coin::Quarter(state) => {println!("Quarter from: {:?}", state);25},}
}fn main() {value_in_cents(Coin::Quarter(UsState::Alabama));value_in_cents(Coin::Quarter(UsState::Alaska));
}
Option枚舉可以和match
語句結合處理當數據為空的情況:
fn plus_one(number: Option<i32>) -> Option<i32> {match number {None => None,Some(i) => Some(i + 1) // 返回也必須是Option<i32>}
}fn main() {let five = Some(5);let six = plus_one(five);let none = plus_one(None);
}
需要注意,match
必須窮舉所有的可能。如果沒有窮舉所有的可能編譯器就會報錯,但是有時候確實只需要處理其中一部分數據,我么需要一個default
,Rust使用下劃線通配符表示未提及的情況:
fn main() {let v = 1u8;match v {1 => println!("one!"),3 => println!("three!"),_ => {}, // 或者 _ => () }
}
2.4.2 if let語句
if let
語句相當于match
語句只需要處理一種情況時的語法糖,后面可以加else
,寫法如下:
fn main() {let v = Some(3);if let Some(i) = v { // 注意這里是 =,并且被匹配變量必須寫在后面println!("the number is {}", i);} else {println!("others");}
}
雖然看起來直接寫if
更簡單,但是if let
本質是模式匹配,除了控制流,它還有另一個重要的功能:提取枚舉攜帶的值。所以它和普通的控制流語句if
還是不同的。
??原創筆記,碼字不易,歡迎點贊,收藏~ 如有謬誤敬請在評論區不吝告知,感激不盡!博主將持續更新有關嵌入式開發、FPGA方面的學習筆記。