結構體
struct,或者 structure,是一個自定義數據類型,允許你命名和包裝多個相關的值,從而形成一個有意義的組合。如果你熟悉一門面向對象語言,struct 就像對象中的數據屬性。結構體和我們在第三章討論過的元組類似。和元組一樣,結構體的每一部分可以是不同類型。但不同於元組,結構體需要命名各部分數據以便能清楚的表明其值的意義。由於有了這名字,結構體比元組更靈活:不需要依賴順序來指定或訪問實例中的值。也就是說元組和結構體類似,但是結構體有具體的代號
結構體的聲明和實例化
#[derive(Debug)] // 必須帶有這個
// 聲明結構體
struct User {
username: String,
email: String,
active: bool,
}
fn main() {
// 實例化
let mut user1 = User {
// 使用mut可以修改里面的屬性
email: String::from("someone@qq.com"),
username: String::from("wang"),
active: true,
};
user1.email = String::from("666@qq.com");
println!("{:?}", user1);
}
結構體的實例構造函數
為了方便,我們可以寫一個函數專門用來返回新實例化的對象,以及字段名和變量名一致可以簡寫,這里我們比如說是builder_user可以如下:
#[derive(Debug)] // 必須帶有這個
// 聲明結構體
struct User {
username: String,
email: String,
active: bool,
}
fn build_user(email: String, username: String) -> User {
User {
// 使用mut可以修改里面的屬性
email: email,
username, // 一致就簡寫
active: true,
}
}
fn main() {
let mut user1 = build_user(String::from("xxx"), String::from("erwqrq"));
user1.email = String::from("666@qq.com");
println!("{:?}", user1);
}
從另一個實例來更新新實例
調用另一個實例的屬性值來創建新實例,代碼如下:
fn main() {
let user1 = User {
email: String::from("xxxx"),
username: String::from("wang"),
active: false,
};
let user2 = User {
email: String::from("455"),
username: user1.username,
active: false,
};
let user3 = User {
email:String::from("2232"),
..user2 // 支持結構賦值
};
println!("{:?}", user3);
}
使用沒有命名字段的元組結構體來創建不同的類型
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0,0,0);
let origin = Point(0,0,0);
}
結構體數據所有權
User 結構體的定義中,我們使用了自身擁有所有權的 String 類型而不是 &str 字符串 slice 類型。這是一個有意而為之的選擇,因為我們想要這個結構體擁有它所有的數據,為此只要整個結構體是有效的話其數據也是有效的。生命周期確保結構體引用的數據有效性跟結構體本身保持一致。如果你嘗試在結構體中存儲一個引用而不指定生命周期將是無效的,比如這樣:
// 聲明結構體
struct User {
username: &str,
email: &str, // 定義的時候就報錯了,提示沒有生命周期
active: bool,
}
使用結構體創建一個demo:
// 聲明結構體
fn area(width: u32, height: u32) -> u32 {
width * height
}
fn area1(dimensions: (u32, u32)) -> u32 {
dimensions.0 * dimensions.1
}
fn area2(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}
// 結構體
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
// 最初版本
let width = 30;
let height = 50;
println!("The area value :{}", area(width, height));
// 元組重構
let rect1 = (30, 50);
println!("The area value :{}", area1(rect1));
let rect2 = Rectangle {
width: 32,
height: 55,
};
println!("The area value :{}", area2(&rect2));
}
輸出結構體實例
由於Rust自帶的println!不能打印出實例,所以我們需要在{}中添加#?來打印,同時需要在文件頭部添加:#[derive(Debug)],在 {} 中加入 :? 指示符告訴 println! 我們想要使用叫做 Debug 的輸出格式。 Debug 是一個 trait,它允許我們以一種對開發者有幫助的方式打印結構體,以便當我們調試代碼時能看到它的值。
結構體方法實現
例子中,使用 &self 來替代 rectangle: &Rectangle ,因為該方法位於 implRectangle 上下文中所以 Rust 知道 self 的類型是 Rectangle 。注意仍然需要在 self 前面加上 & ,就像 &Rectangle 一樣。方法可以選擇獲取 self 的所有權,或者像我們這里一樣不可變地借用 self ,或者可變地借用 self ,就跟其他參數一樣。這里選擇 &self 的理由跟在函數版本中使用 &Rectangle 是相同的:我們並不想獲取所有權,只希望能夠讀取結構體中的數據,而不是寫入。如果想要在方法中改變調用方法的實例,需要將第一個參數改為 &mut self 。通過僅僅使用 self 作為第一個參數來使方法獲取實例的所有權是很少見的;這種技術通常用在當方法將 self 轉換成別的實例的時候,這時我們想要防止調用者在轉換之后使用原始的實例。
實例方法的調用:
在 C/C++ 語言中,有兩個不同的運算符來調用方法: . 直接在對象上調用方法,而 -> 在一個對象的指針上調用方法,這時需要先解引用(dereference)指針。換句話說,
#[derive(Debug)]
struct Point {
x: f64,
y: f64,
}
impl Point {
fn distance(&self, other: &Point) -> f64 {
let x_squared = f64::powi(other.x - self.x, 2);
let y_squared = f64::powi(other.y - self.y, 2);
f64::sqrt(x_squared + y_squared)
}
}
fn main() {
let p1 = Point { x: 0.0, y: 0.0 };
let p2 = Point { x: 5.0, y: 6.5 };
println!("{}", p1.distance(&p2));
println!("{}", (&p1).distance(&p2));
}
#[derive(Debug)] // 必須帶有這個
struct Rectangle {
width: u32,
height: u32,
}
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
}
}
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));
}
關聯函數
impl 塊的另一個有用的功能是:允許在 impl 塊中定義 不 以 self 作為參數的函數。這被稱為 關聯函數(associated functions),因為它們與結構體相關聯。它們仍是函數而不是
#[derive(Debug)] // 必須帶有這個
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
println!("Can rect1 area {}", Rectangle::square(30).area());
}
多個impl塊
每個結構體都允許擁有多個 impl 塊。每個方法有其自己的 impl 塊。
