1. 程式人生 > 實用技巧 >【Rust-book】第五章 使用結構體來組織相關聯的資料

【Rust-book】第五章 使用結構體來組織相關聯的資料

結構,或者結構體,是一種自定義資料型別,它允許我們命名多個相關的值並將它們組成一個有機的結合體。

可以把結構體視作物件中的資料屬性

1 對比元組和結構體之間的異同,並演示如何使用結構體

2 討論如何定義方法和關聯函式,他們可以指定那些與結構體資料相關的行為

結構體和列舉體是用來建立型別的基本工具,在特定領域中的新型別同樣可以享受到Rust編譯時型別檢查系統的所有優勢。

定義並例項化結構體

結構體需要給每個資料賦予名字,可以清楚地表明它們地意義。

由於欄位命名,不再需要依賴順序索引來指定或訪問例項中的值。

關鍵字struct被用來定義並命名結構體,一個良好的結構體名稱應當能反映出自身資料組合的意義。在隨後的花括號中宣告所有資料的名字及型別,這些資料被稱為欄位。

struct User {
	username: String,
	email: String, 
	sign_in_count: u64, 
	active: bool,
}

在為每個欄位賦予具體的值來建立結構體例項,賦值的順序不需要嚴格對應在結構體中宣告它們的順序。

結構體的定義就像型別的通用模版一樣,當我們將具體的資料填入模板時就創建出了新的例項。

let user1 = User {
	email: String::from("[email protected]"),
	username: String::from("someusername123"),
	active: true,
	sign_in_count: 1,
};

可以通過點號來訪問例項中的特定欄位。

let mut user1 = User {
	email: String::from("[email protected]"),
	username: String::from("someusername123"),
	active: true,
	sign_in_count: 1,
};

user1.email = String::from("[email protected]");

一旦例項可變,那麼例項中的所有欄位都將是可變的。

Rust不允許我們將單獨宣告某一部分欄位的可變性。

fn build_user( email: String, username: String) -> User {
	User { // 在變數名與欄位名相同時使用簡化版的欄位初始化方法
		// 引數與結構體欄位擁有完全一致的名稱,所以可以使用名為欄位初始化簡寫的語法(field init shorthand)
		email,
		username,
		active: true,
		sign_in_count: 1,
	}
}

使用結構體更新語法根據其他例項建立新例項

let user2 = User {
		email: String::from("[email protected]"),
		username: String::from("anotherusername455"),
		..user1 // .. 表明剩下的那些還未被顯示賦值欄位都與給定例項擁有相同的值
};

使用不需要對欄位命名的元組結構體來建立不同的型別

元組結構體, 元祖結構體同樣擁有用於表明自身含義的名稱,無須在宣告它時對其欄位進行命名,僅保留欄位的型別即可。

一般來說,當你想要給元組賦予名字,並使其區別與其他擁有同樣定義的元組時,可以使用元祖結構體。

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

let black = Color(0,0,0);
let origin = Point(0,0,0);

這裡的 black ,origin 是不同的型別,因為它們兩個分別是不同元組結構體的例項,

所以定義的每一個結構體都擁有自己的型別,即便結構體中的欄位擁有完全相同的型別。 例如,一個以Color型別作為引數的函式不能合法地接收Point型別的引數,即使它們都是由3個i32組成的。

可以通過模式匹配解構為單獨的部分,也可以通過.及索引來訪問特定欄位

沒有任何欄位的空結構體

單元結構體,沒有任何欄位的結構體()。也稱為空結構體

當需要在某些型別實現一個trait,卻不需要在該型別中儲存任何資料時,空結構體可以發揮作用。

//對於引用來說有生命週期的限制
struct User {
	username: &str, // expected lifetime parameter
	email: &str,
	sign_in_count: u64,
	active: bool,
}

fn main() {
	let user1 = User {
		email: "[email protected]",
		username: "someusername123",
		active: true,
		aign_in_count: 1,
	};
}

一個結構體的示例程式

fn main() {
	let width1 = 30;
	let height1 = 50;
	
	println!("The area of the rectangel is {} aquare pixels.", area(width1, height1));

}
fn area(width: u32, height: u32) -> u32 {
	width * height
}

元組重構

fn main() {
	let rect1 = (30, 50);
	
	println!("The area of the rectangel is {} aquare pixels.", area(rect1));
}

fn area(dimensions: (u32, u32)) -> u32 {
	dimensions.0 * dimensions.1
}

使用結構體來重構程式碼,增加有意義的描述資訊


struct Rectangle {
	width: u32,
	height: u32,
}

fn main() {
	let rect1 = Rectangle { width: 30, height: 50 };
	println!("The area of the rectangel is {} aquare pixels.", area(&rect1));
}

fn area(rectange: &Rectangle) -> u32 { //不可變借用&Rectangle ,不會獲得它的所有權
	rectangle.width * rectangle.height
}

通過派生trait增加適用功能

#[derive(Debug)]
struct Rectange {
	width: u32, 
	height: u32,
}

fn main() {
	let rect1 = Rectangle { width: 30, height: 50 };;
	println!("rect1 is {:?}", rect1);
}

方法

方法總是被定義在某個結構體、列舉體或者triat物件,並且它們的第一個引數永遠都是self,用於指代呼叫該方法的結構體例項。

#[derive(Debug)]
struct Rectangle {
	width: u32, 
	height: u32,
}

impl Rectangle {
	fn area(&self) -> u32 { // self: &Self ---> Self is Rectangle 
		self.width * sellf.height
	}
}

fn main() {
	let rect1 = Rectangle { width: 30, height: 50 };
	println!("The area of the rectangel is {} aquare pixels.", rect1.area());
}

為了在Rectangle的上下文環境中定義這個函式,需要將area函式移動到impl 關鍵字起始的程式碼塊中,並把簽名中的第一個引數(也是唯一的那個引數)和函式中使用該引數的地方改為self.

方法呼叫是通過例項後面加點好,並更上方法名、括號及可能的引數來實現。

帶有更多引數的方法

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

#[derive(Debug)]
struct Rectangle {
	width: u32, 
	height: u32,
}

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

關聯函式

impl Rectangle {
	fn squaer(size: u32) -> Rectange {
		Rectange { width: size, height: size }
	}
}

關聯函式常常用作構造器來返回一個結構體的新例項。

型別名稱後面新增::來呼叫關聯函式

:: 語法不僅被用於關聯函式,還被用於模組建立的明名稱空間。

多個imple塊

impl Rectangle {
	fn area(&self) -> u32 { // self: &Self ---> Self is Rectangle 
		self.width * sellf.height
	}
}

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