Rust 學習筆記:結構體(struct)

Rust 學習筆記:結構體(struct)

  • Rust 學習筆記:結構體(struct)
    • 結構體的定義和實例化
    • 使用字段初始化簡寫
    • 用 Struct Update 語法從其他實例創建實例
    • 使用沒有命名字段的元組結構來創建不同的類型
    • 沒有任何字段的類單元結構
    • 結構體的所有權
    • 示例程序:Rectangle
    • 用派生特性添加有用的功能
      • Debug
      • Copy, Clone
    • 方法的語法
    • 有更多參數的方法
    • 關聯函數
    • 多個 impl 塊

Rust 學習筆記:結構體(struct)

struct 是一種自定義數據類型,它允許將多個相關的值打包在一起并命名,從而組成一個有意義的組。

結構體的定義和實例化

要定義結構,輸入關鍵字 struct 并為整個結構命名。

然后,在花括號內定義數據塊的名稱和類型,我們稱之為字段。

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

要在定義結構后使用它,需要為每個字段指定具體的值,從而創建該結構的實例。

我們通過聲明結構體的名稱來創建實例,然后添加包含鍵:值對的花括號,其中鍵是字段的名稱,值是我們希望存儲在這些字段中的數據。我們不必按照在結構體中聲明字段的順序指定字段。換句話說,結構定義就像是該類型的通用模板,實例用特定的數據填充該模板,以創建該類型的值。

fn main() {let user1 = User {active: true,username: String::from("someusername123"),email: String::from("someone@example.com"),sign_in_count: 1,};
}

為了從結構體中獲得特定的值,我們使用點表示法。例如,要訪問這個用戶的電子郵件地址,我們使用 user1.email。如果實例是可變的,我們可以通過使用點表示法和賦值到一個特定的字段來改變值。

fn main() {let mut user1 = User {active: true,username: String::from("someusername123"),email: String::from("someone@example.com"),sign_in_count: 1,};user1.email = String::from("anotheremail@example.com");
}

注意,整個實例必須是可變的;Rust 不允許我們只將某些字段標記為可變的。

結構體的新實例可以作為函數體中的最后一個表達式,以隱式返回該新實例。

fn build_user(email: String, username: String) -> User {User {active: true,username: username,email: email,sign_in_count: 1,}
}

username: username 這種寫法有點乏味。如果結構體有更多字段,重復每個名稱會變得更加煩人。幸運的是,有一種方便的速記方法!

使用字段初始化簡寫

字段 init 速記語法允許將同名的字段和參數進行簡化。

例如,因為 email 字段和 email 參數有相同的名字,所以我們只需要寫 email 而不是 email: email。

fn build_user(email: String, username: String) -> User {User {active: true,username,email,sign_in_count: 1,}
}

用 Struct Update 語法從其他實例創建實例

創建一個結構體的新實例,該實例包含另一個實例的大部分值,但更改了一些值,這通常很有用。

不使用 Struct Update 語法,每個字段都需要設置。

fn main() {// --snip--let user2 = User {active: user1.active,username: user1.username,email: String::from("another@example.com"),sign_in_count: user1.sign_in_count,};
}

使用 Struct Update 語法,指定未顯式設置的其余字段應具有與給定實例中的字段相同的值。

fn main() {// --snip--let user2 = User {email: String::from("another@example.com"),..user1};
}

..user1 必須在最后指定任何剩余的字段應該從 user1 中的相應字段獲取它們的值。

請注意,struct update 語法就像使用 = 賦值一樣,這是因為它移動數據,會發生所有權的轉移。

在本例中,在創建 user2 之后,我們不能再將 user1 作為一個整體使用,因為 user1 的 username 字段中的 String 被移到了 user2 中。

如果我們只使用 user1 的 active 和 sign_in_count 值,那么在創建 user2 之后,user1 仍然有效。這是因為 active 和 sign_in_count 都是實現Copy Trait 的類型,這些變量會復制,并不移動。

使用沒有命名字段的元組結構來創建不同的類型

Rust 還支持類似于元組的結構,稱為元組結構。沒有字段的名稱,只有字段的類型。

要定義元組結構,首先使用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);
}

注意,即使 Color 和 Point 這兩種類型都由三個 i32 值組成,但它們的實例不能互相賦值。

沒有任何字段的類單元結構

沒有任何字段的結構體被稱為類單元結構。

當需要在某些類型上實現 trait,但沒有任何想要存儲在類型本身中的數據時,類單元結構可能很有用。

下面是一個聲明和實例化一個名為 AlwaysEqual 的類單元結構的例子:

struct AlwaysEqual;fn main() {let subject = AlwaysEqual;
}

結構體的所有權

在之前的 User 結構體定義中,我們使用了 String 類型而不是 &str 字符串切片類型。這是一個經過深思熟慮的選擇,因為我們希望這個結構體的每個實例都擁有它的所有數據,并且只要整個結構體有效,這些數據就有效。

結構體也可以存儲對其他對象擁有的數據的引用,但這樣做需要使用生命周期,以確保結構體引用的數據在該結構體存在的時間內有效。假設在一個結構體中存儲一個引用而不指定生命周期,如下所示。

struct User {active: bool,username: &str,email: &str,sign_in_count: u64,
}fn main() {let user1 = User {active: true,username: "someusername123",email: "someone@example.com",sign_in_count: 1,};
}

報錯:缺少生命周期標識符

在這里插入圖片描述

示例程序:Rectangle

創建一個名為 Rectangle 的結構體,存儲矩形的寬度和高度。

再創建一個函數,入參為結構體對象,計算矩形的面積。

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
}

注意,訪問借用的結構體實例的字段并不會移動字段值,這就是為什么經常看到借用結構體的原因。

用派生特性添加有用的功能

Debug

在調試程序時打印一個 Rectangle 實例并查看其所有字段的值是很有用的。

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

println! 宏可以進行多種格式化,默認情況下,大括號告訴 println! 使用稱為顯示的格式:用于直接最終用戶使用的輸出。到目前為止,我們看到的基本類型都默認實現了 Display,但是結構體沒有提供 Display 的實現(與 println! 以及 {} 占位符)。

從報錯信息中我們能得到提示:

= 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

輸入說明符 :? 在花括號內告訴 println! 我們希望使用一種名為 Debug 的輸出格式。Debug 特性使我們能夠以一種對開發人員有用的方式打印結構體,這樣我們在調試代碼時就可以看到它的值。

只是這樣還不夠,還是報錯:

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 確實包含打印調試信息的功能,但我們必須顯式地選擇使該功能對結構體可用。為此,我們在結構定義之前添加 #[derive(Debug)]

#[derive(Debug)]
struct Rectangle {width: u32,height: u32,
}fn area(rectangle: &Rectangle) -> u32 {rectangle.width * rectangle.height
}fn main() {let rect1 = Rectangle {width: 30,height: 50,};println!("rect1 is {rect1:?}");println!("The area of the rectangle is {} square pixels.",area(&rect1));
}

現在,當我們運行程序時,我們不會得到任何錯誤,并且我們將看到以下輸出:

在這里插入圖片描述

冒號和問號之間再加一個 #,即 :#?,可以使得打印的值更有格式,這在結構體中字段多時比較有用。

在這里插入圖片描述

使用 Debug 格式打印值的另一種方法是使用 dbg! 宏,它接受一個表達式的所有權(與 println! 相反,它接受一個引用)。

注意,調用 dbg! 宏打印到標準錯誤控制臺流(stderr),println! 則打印到標準輸出控制臺流(stdout)。

Copy, Clone

復制和克隆屬性,可以讓結構體的方法不再奪取結構體實例的所有權。

在這里插入圖片描述

方法的語法

方法類似于函數:我們用 fn 關鍵字和一個名稱來聲明它們,它們可以有參數和返回值。

與函數不同,方法是在結構體(或 enum 或 trait 對象)的上下文中定義的,它們的第一個參數總是 self,它表示調用方法的結構體的實例。

#[derive(Debug)]
struct Rectangle {width: u32,height: u32,
}impl Rectangle {fn area(&self) -> u32 {self.width * self.height}
}fn main() {let rect1 = Rectangle {width: 30,height: 50,};println!("The area of the rectangle is {} square pixels.",rect1.area());
}

為了在 Rectangle 的上下文中定義函數,我們為 Rectangle 啟動了一個 impl(實現)塊。這個 impl 塊中的所有內容都將與 Rectangle 類型相關聯。然后將 area 函數移動到 impl 花括號內,并將簽名中的第一個(在本例中是唯一一個)參數更改為 self,并將函數體中的所有參數更改為 self。

在 area 的簽名中,我們使用 &self 代替 rectangle: &Rectangle。&self 實際上是 self: &Self 的縮寫。在一個 impl 塊中,Self 類型是該 impl 塊所針對的類型的別名。方法的第一個參數必須有一個名為 self 的 Self 類型的參數,因此 Rust 允許在第一個參數點只使用名稱 self 來縮寫它。

注意,我們仍然需要在 self 前面使用 & 來表示這個方法借用了 self 實例,就像我們在 rectangle: & rectangle 中所做的那樣,因為這里我們不想占有所有權。

除了提供方法語法和不必在每個方法的簽名中重復 self 的類型之外,使用方法而不是函數的主要原因是為了組織。我們把一個類型的實例所能做的所有事情都放在了一個 impl 塊中,而不是讓未來的代碼用戶在我們提供的庫的各個地方搜索 Rectangle 的功能。

注意,我們可以選擇給一個方法賦予與結構體的一個字段相同的名稱。例如,我們可以在 Rectangle 上定義一個同樣命名為 width 的方法:

impl Rectangle {fn width(&self) -> bool {self.width > 0}
}fn main() {let rect1 = Rectangle {width: 30,height: 50,};if rect1.width() {println!("The rectangle has a nonzero width; it is {}", rect1.width);}
}

這里,我們選擇讓 width 方法在實例的 width 字段值大于 0 時返回 true,在值小于等于 0 時返回 false。當我們使用帶括號的 width, Rust 知道我們指的是方法寬度;當我們不使用括號時,Rust 知道我們指的是字段寬度。

通常(但并非總是),當我們給一個方法賦予與字段相同的名稱時,我們希望它只返回字段中的值,而不做任何其他事情。像這樣的方法被稱為 getter, Rust 不像其他語言那樣為 struct 字段自動實現它們。getter 很有用,因為您可以將字段設置為私有,而將方法設置為公共,從而使對該字段的只讀訪問成為類型的公共API的一部分。

在 C/C++ 中,調用方法使用兩種不同的操作符:object->something() 或 (*object).something()。

Rust 沒有與 -> 操作符等價的操作符;相反,Rust 有一個稱為自動引用和解引用的特性。它是這樣工作的:當你用 object.something() 調用一個方法時,Rust 會自動添加 &,&mut 匹配方法的簽名。換句話說,以下內容是相同的:

p1.distance(&p2);
(&p1).distance(&p2);

第一個看起來干凈多了。這種自動引用行為之所以有效,是因為方法有一個明確的接收者——self 類型。事實上,Rust 為方法接收者提供了隱含的借用。

有更多參數的方法

讓我們通過在 Rectangle 結構體上實現第二個方法:can_hold。

這一次,我們想要一個矩形的實例接受另一個矩形的實例,如果第二個矩形可以完全適合 self(第一個矩形),返回 true;否則,它應該返回 false。

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}
}

can_hold 函數將以一個不可變的借用另一個矩形作為第二個參數,比較后返回一個布爾值。

關聯函數

在 impl 塊中定義的所有函數都稱為關聯函數,因為它們與以 impl 命名的類型相關聯。我們可以定義不以 self 作為第一個參數的關聯函數(因此不是方法),因為它們不需要使用該類型的實例。

我們已經使用過一個這樣的函數:String::from 函數,它定義在 String 類型上。

非方法的關聯函數通常用于將返回該結構的新實例的構造函數。這些通常被稱為 new,但 new 并不是一個特殊的名稱,也沒有內置到語言中。例如,我們可以選擇提供一個名為 square 的關聯函數,它將有一個維度參數,并使用它作為寬度和高度,從而使創建一個正方形矩形更容易,而不必兩次指定相同的值:

impl Rectangle {fn square(size: u32) -> Self {Self {width: size,height: size,}}
}

返回類型和函數體中的 Self 關鍵字是出現在 impl 關鍵字之后的類型的別名,在本例中是 Rectangle。

要調用這個關聯函數,可以使用 結構體名+ :: + 方法名,比如:

let sq = Rectangle::square(3);

多個 impl 塊

每個結構體允許有多個 impl 塊。比如:

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

這里沒有理由將這些方法分離到多個 impl 塊中,但這是有效的語法。

但在討論泛型類型和特征時,多個 impl 塊是有用的。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/77838.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/77838.shtml
英文地址,請注明出處:http://en.pswp.cn/web/77838.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Dify Agent節點的信息收集策略示例

Dify Agent節點的信息收集策略示例 0. 安裝"對話 Agent"插件1. 創建一個 Chatflow2. 創建一個 Agent 節點3. 創建一個條件分支節點4. 在IF分支創建一個LLM節點5. 創建一個直接回復節點6. 在ELSE分支創建一個直接回復節點7. 分布并預覽 0. 安裝"對話 Agent"…

Qt/C++開發監控GB28181系統/獲取設備信息/設備配置參數/通道信息/設備狀態

一、前言 設備注冊成功后,接下來要做的就是獲取設備的信息,尤其是通道信息,根據國標協議,永遠只有兩個層級,一個是設備,然后就是設備下面多個通道,設備編碼在整個系統中唯一,通道編…

金融風控的“天眼”:遙感技術的創新應用

在金融市場的復雜博弈中,風險管控一直是金融機構的核心競爭力。然而,傳統的風控手段在應對現代金融市場的快速變化時,往往顯得捉襟見肘。 如今,遙感技術的創新應用為金融風控帶來了全新的視角和手段。星圖云開放平臺的遙感金融立體…

HFI筆記

高頻分量: 載波頻率的一半 選擇alfabeta軸進行計算的原因 最終結果: 觀測器方程 采樣加減分離法-(高低頻分離) 高頻信號的評論高頻載波 轉子極性辨識

halcon關閉圖形窗口

1、dev_close_window () 調用一次這個函數關閉一個圖形窗口,并且先關閉最后打開的那個圖形窗口,如果一共打開了N個圖形窗口,那么就需要調用dev_close_window N次。

每日算法-250430

每日算法 - 2025年4月30日 記錄下今天解決的兩道題目。 870. 優勢洗牌 (Advantage Shuffle) 題目描述 解題思路與方法 核心思想:貪心策略 (田忌賽馬) 這道題的目標是對于 nums1 中的每個元素,找到 nums2 中一個比它小的元素進行配對(如果…

【MySQL】增刪改查(CRUD)

目錄 一. CRUD是什么 二. Create(新增數據) 2.1 單行數據全列插入 2.2 單行數據指定列插入 2.3 多行數據指定列插入 三. Retrieve (檢索/查詢) 3.1 全列查詢 3.2 指定列查詢 3.3 查詢字段為表達式 3.4 為查詢結果指定別名 3…

電商平臺 API 開發實戰:京東商品詳情數據實時獲取接口對接教程

在電商行業競爭日益激烈的當下,實時獲取商品詳情數據對于市場分析、競品監控、商品推薦等業務場景至關重要。京東作為國內領先的電商平臺,提供了強大的 API 接口,允許開發者獲取豐富的商品信息。本文將詳細介紹京東商品詳情數據實時獲取接口的…

YOLO視覺模型可視化訓練與推理測試工具

推薦一款YOLO可視化訓練測試工具: 對于yolo的訓練,新手小白往往無從下手,本章推薦的這款工具可以非常輕易的幫您從模型訓練到測試到部署。 下載地址http://www.voouer.com/yolo 可以點擊此處跳轉。 下載成功后打開這款工具,將會出現圖形化界面,類似于下圖所示: 當前頁是可視…

微調 LLaMA 2:定制大型語言模型的分步指南

微調 LLaMA 2:定制大型語言模型的分步指南 深入了解如何運用新技術在 Google Colab 平臺上對 Llama-2 進行微調操作,從而有效克服內存與計算方面的限制,讓開源大型語言模型變得更加易于獲取和使用。自從 Meta 發布了 LLaMA 的首個版本后&…

探秘明遠智睿SSD2351開發板在HMI領域的獨特魅力

人機界面(HMI)是人與機器進行交互的重要橋梁,其性能和用戶體驗直接影響到整個系統的使用效果。明遠智睿的SSD2351開發板憑借其出色的性能和豐富的功能,在HMI領域展現出了獨特的魅力。 SSD2351開發板的四核1.4GHz處理器具備強大的圖…

Keysight萬用表使用指南及基于Python采集數據生成Excel文件

文章目錄 說明使用的庫openpyxlpyvisa 代碼說明效果展示參考代碼 說明 本文介紹了 Keysight 34465A 的基本使用和 SCPI 指令設置,演示了使用 Python 的 PyVISA 庫控制兩臺 34465A 同時采集數據的完整流程,包括設置采樣參數、觸發測量、讀取數據、使用 O…

Docker 獲取 Python 鏡像操作指南

1. 安裝 Docker 環境 1.1 上傳安裝腳本(Windows → Linux) 在 Windows 的 CMD 中執行: scp docker.sh root10.1.1.58:~ 可自行前往我的飛書下載docker.sh腳本 Docs 1.2 在 Linux 中檢查文件 ls -l ~ # 確認 docker.sh 已上傳到家目錄…

JavaScript:從JS的執行機制到location對象

一、JS執行機制 (1)JS是單線程 JavaScript語言的一大特點就是單線程,也就是同一時間只能做一件事。因為JavaScript是為了處理頁面中的用戶交互,以及制作DOM二誕生的。比如我們對某個DOM元素進行添加和刪除操作,這個不…

iVX:數字化轉型全場景技術革新與生態構建實踐

在數字經濟蓬勃發展的當下,企業數字化轉型需求日益迫切。iVX 憑借其獨特的技術架構與創新解決方案,深度滲透工業互聯網、元宇宙、智慧城市等領域,成為推動全場景數字化轉型的重要力量。本文將重新梳理 iVX 的技術應用與生態價值,以…

生物化學筆記:神經生物學概論05 感受野 視覺中樞 高級視皮層中的信息走向

信息傳遞中的“擊鼓傳花” 新特性的突現 功能柱:簡化節點 高級視皮層中的信息走向

StarRocks Lakehouse 如何重構大數據架構?

隨著數據分析需求的不斷演進,企業對數據處理架構的期望也在不斷提升。在這一背景下,StarRocks 憑借其高性能的實時分析能力,正引領數據分析進入湖倉一體的新時代。 4 月 18 日,鏡舟科技高級技術專家單菁茹做客開源中國直播欄目《…

【SpringBoot】基于mybatisPlus的博客系統

1.實現用戶登錄 在之前的項目登錄中,我使用的是Session傳遞用戶信息實現校驗登錄 現在學習了Jwt令牌技術后我嘗試用Jwt來完成校驗工作 Jwt令牌 令牌一詞在網絡編程一節我就有所耳聞,現在又拾了起來。 這里講應用:令牌也就用于身份標識&a…

HCIP-security常見名詞

縮略語英文全稱解釋3DESTriple Data Encryption Standard三重數據加密標準AESAdvanced Encryption Standard高級加密標準AHAuthentication Header報文認證頭協議CACertification Authority證書頒發中心DESData Encryption Standard數據加密標準DHDiffie-Hellman密鑰交換算法DPD…

合并多個Excel文件到一個文件,并保留格式

合并多個Excel文件到一個文件,并保留格式 需求介紹第一步:創建目標文件第二步:創建任務列表第三步:合并文件第四步:處理合并后的文件之調用程序打開并保存一次之前生成的Excel文件第五步:處理合并后的文件之…