Rust結構體


 

 

結構體

  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 和 Point 元組結構體的定義和用法:
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,它允許我們以一種對開發者有幫助的方式打印結構體,以便當我們調試代碼時能看到它的值。

 

結構體方法實現

  為了使函數定義於 Rectangle 的上下文中,我們開始了一個 impl 塊( impl 是implementation 的縮寫)。接着將 area 函數移動到 impl 大括號中,並將簽名中的第一個
(在這里也是唯一一個)參數和函數體中其他地方的對應參數改成 self 。然后在 main 中方法語法將我們先前調用 area 方法並傳遞 rect1 作為參數的地方,改成使用 方法語法(method
syntax)在 Rectangle 實例上調用 area 方法。方法語法獲取一個實例並加上一個點號,后跟方法名、圓括號以及任何參數。
 

  例子中,使用 &self 來替代 rectangle: &Rectangle ,因為該方法位於 implRectangle 上下文中所以 Rust 知道 self 的類型是 Rectangle 。注意仍然需要在 self 前面加上 & ,就像 &Rectangle 一樣。方法可以選擇獲取 self 的所有權,或者像我們這里一樣不可變地借用 self ,或者可變地借用 self ,就跟其他參數一樣。這里選擇 &self 的理由跟在函數版本中使用 &Rectangle 是相同的:我們並不想獲取所有權,只希望能夠讀取結構體中的數據,而不是寫入。如果想要在方法中改變調用方法的實例,需要將第一個參數改為 &mut self 。通過僅僅使用 self 作為第一個參數來使方法獲取實例的所有權是很少見的;這種技術通常用在當方法將 self 轉換成別的實例的時候,這時我們想要防止調用者在轉換之后使用原始的實例。

 

實例方法的調用:

  在 C/C++ 語言中,有兩個不同的運算符來調用方法: . 直接在對象上調用方法,而 -> 在一個對象的指針上調用方法,這時需要先解引用(dereference)指針。換句話說,

如果 object 是一個指針,那么 object->something() 就像 (*object).something() 一樣。Rust 並沒有一個與 -> 等效的運算符;相反,Rust 有一個叫 自動引用和解引用(automatic referencing and dereferencing)的功能。方法調用是 Rust 中少數幾個擁有這種行為的地方。他是這樣工作的:當使用 object.something() 調用方法時,Rust 會自動為 object 添加 & 、 &mut 或 * 以便使 object 與方法簽名匹配。也就是說,這些代碼是等價的:
#[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));
}

  

帶有更多參數的方法
  rect1.can_hold(&rect2) 傳入了 &rect2 ,它是一個 Rectangle 的實例rect2 的不可變借用。這是可以理解的,因為我們只需要讀取 rect2 (而不是寫入,這意味着我們需要一個不可變借用),而且希望 main 保持 rect2 的所有權,這樣就可以在調用這個方法后繼續使用它。
#[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),因為它們與結構體相關聯。它們仍是函數而不是

方法,因為它們並不作用於一個結構體的實例。你已經使用過 String::from 關聯函數了。(類似與結構體方法,類似與python的類方法)
#[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 塊。  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM