VB.NET 結構 (Structure) 筆記 (進階篇)
在 VB.NET 中,結構 (Structure)結構就像是一個可組裝的積木模型,它可以將多個不同類型的部件組合在一起,形成一個完整的單元。當需要將一些相關的資料組織在一起,又不需要類別那麼多的功能時,就可以使用結構。是一種用於封裝少量相關資料的值型別。它與類別 (Class) 有些相似之處,但也有一些重要的差異。瞭解結構的特性和使用方法,可以幫助我們更好地組織程式中的資料,提高程式的效率和可讀性。
認識結構
結構 (Structure): 結構是一種用於封裝少量相關資料的複合值型別。它可以包含多個不同類型的欄位 (Field),以及一些方法 (Method)、屬性 (Property)、事件 (Event) 等成員。結構是值型別,這意味著它的實例直接儲存在記憶體的堆疊 (Stack) 中,而不是在堆積 (Heap) 中。
結構具有以下重要特性:
- 值型別結構的值型別特性就像是一個獨立的積木模型,它的每一個實例都是完整的,不會與其他實例共享部件。當將一個結構賦值給另一個結構時,會複製整個結構的內容,而不是只複製引用。這可以避免意外修改共享資料而引發的問題。:結構是值型別,它的實例直接儲存在記憶體的堆疊中,傳遞時會複製整個結構的內容。
- 可包含成員結構可以包含多種類型的成員,就像是一個可以裝入不同種類部件的積木盒。這些成員可以是欄位、方法、屬性、事件等,它們共同定義了結構的行為和狀態。透過合理設計結構的成員,可以使結構更加靈活和實用。:結構可以包含欄位、方法、屬性、事件等成員,用於定義結構的行為和狀態。
- 可實現介面結構可以實現介面,就像是按照統一的規範來組裝積木模型。實現介面可以確保結構具有特定的功能,並且可以與其他模組進行互動。這樣可以提高結構的可重用性和相容性,使其更加靈活。:結構可以實現一個或多個介面,定義一組共同的行為規範。
- 不能繼承結構不能繼承其他結構或類別,就像是每個積木模型都是獨立設計的,不能在現有模型的基礎上進行改造。這是結構與類別的一個重要區別。雖然不能繼承,但結構仍然可以通過實現介面來擴展功能。:結構不能作為基礎被其他結構或類別繼承,也不能繼承其他結構或類別(除了 ValueType)。
結構通常用在以下場景:
- 表示一組緊密相關的值,如座標、顏色、日期範圍等。
- 作為方法的參數或返回值,傳遞少量的資料。
- 需要大量建立和銷毀實例的情況,因為結構的建立和銷毀比類別更高效。
使用結構時需要注意值型別的特性。將一個結構賦值給另一個結構時,會複製整個結構的內容複製結構的內容就像是將一個積木模型拆開,然後按照原樣重新組裝出一個新的模型。新的模型具有與原來完全相同的部件和結構,但它們是相互獨立的,修改一個不會影響另一個。這樣可以確保每個結構實例的獨立性。,而不是只複製引用。這與類別的參考型別特性不同,需要特別注意。
定義結構
在 VB.NET 中,使用 Structure 關鍵字來定義一個結構。以下是定義結構的基本語法:
Public Structure StructureName
' 欄位、屬性、方法等成員
End Structure
其中,StructureName 是結構的名稱,遵循 Pascal 命名法(每個單詞的首字母大寫)。在結構內部,可以定義各種類型的成員,如欄位、屬性、方法等。
以下是一個簡單的結構定義範例:
Public Structure Point
Public X As Integer
Public Y As Integer
Public Sub New(x As Integer, y As Integer)
Me.X = x
Me.Y = y
End Sub
Public Function ToString() As String
Return $"({X}, {Y})"
End Function
End Structure
在這個範例中,定義了一個名為 Point 的結構,用於表示二維平面上的一個點。結構包含了兩個整數型別的欄位 X 和 Y,表示點的橫座標和縱座標。結構還定義了一個建構函式建構函式就像是組裝積木模型的說明書,它告訴我們如何正確地初始化結構的各個部件。透過建構函式,可以在建立結構實例時方便地設定各個欄位的初始值,確保結構的有效性和一致性。 New,用於在建立結構實例時初始化欄位的值。建構函式的參數列表對應著欄位的型別和順序,在函式內部使用 Me 關鍵字來存取結構的欄位。
定義結構時需要注意以下幾點:
- 結構應該是小型的,通常不超過 16 個位元組。
- 結構的欄位應該是公共的 (Public),以便在外部存取。
- 結構應該定義一個無參數的建構函式,用於初始化欄位為其預設值。
- 結構可以實現一個或多個介面,但不能繼承其他結構或類別。
- 結構不應該定義複雜的行為,通常只包含簡單的資料儲存和存取邏輯。
以下是一個更完整的結構定義範例,展示了如何使用結構來表示一個矩形:
Public Structure Rectangle
Public X As Integer
Public Y As Integer
Public Width As Integer
Public Height As Integer
Public Sub New(x As Integer, y As Integer, width As Integer, height As Integer)
Me.X = x
Me.Y = y
Me.Width = width
Me.Height = height
End Sub
Public Function GetArea() As Integer
Return Width * Height
End Function
Public Function GetPerimeter() As Integer
Return 2 * (Width + Height)
End Function
Public Overrides Function ToString() As String
Return $"Rectangle: ({X}, {Y}) - {Width}×{Height}"
End Function
End Structure
在這個範例中,Rectangle 結構包含了四個整數型別的欄位,分別表示矩形的左上角座標 (X, Y) 以及寬度 Width 和高度 Height。結構定義了兩個計算方法:GetArea() 用於計算矩形的面積,GetPerimeter() 用於計算矩形的周長。這些方法展示了如何在結構中封裝與資料相關的計算邏輯。
結構應用範例
以下是一個完整的 Windows Forms 應用程式範例,展示了如何定義和使用結構來管理學生資訊系統:
範例:學生資訊管理系統
這個範例允許使用者輸入學生的基本資訊,並將其儲存在結構中。使用者可以查看學生列表、計算班級的統計資料,以及搜尋特定學生的資訊。
使用的控制項:
- TextBox1:用於輸入學生姓名。
- TextBox2:用於輸入學生年齡。
- TextBox3:用於輸入學生成績。
- Button1:用於添加學生資料。
- Button2:用於計算班級統計資料。
- Button3:用於搜尋學生資訊。
- ListBox1:用於顯示學生列表。
- Label1:用於顯示最高分。
- Label2:用於顯示最低分。
- Label3:用於顯示平均分。
- Label4:用於顯示搜尋結果。
Public Structure Student
Public Name As String
Public Age As Integer
Public Score As Double
Public Sub New(name As String, age As Integer, score As Double)
Me.Name = name
Me.Age = age
Me.Score = score
End Sub
Public Function GetGrade() As String
If Score >= 90 Then
Return "優秀"
ElseIf Score >= 80 Then
Return "良好"
ElseIf Score >= 70 Then
Return "中等"
ElseIf Score >= 60 Then
Return "及格"
Else
Return "不及格"
End If
End Function
Public Overrides Function ToString() As String
Return $"{Name} (年齡: {Age}, 成績: {Score:F2}, 等級: {GetGrade()})"
End Function
Public Function Equals(other As Student) As Boolean
Return Name = other.Name AndAlso Age = other.Age AndAlso Score = other.Score
End Function
End Structure
Public Class Form1
' 宣告一個陣列儲存學生資料
Private students() As Student
' 宣告一個變數追蹤學生數量
Private count As Integer = 0
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Try
' 獲取使用者輸入的學生資料
Dim name As String = TextBox1.Text.Trim()
Dim age As Integer = Integer.Parse(TextBox2.Text)
Dim score As Double = Double.Parse(TextBox3.Text)
' 驗證輸入資料
If String.IsNullOrEmpty(name) Then
MessageBox.Show("請輸入學生姓名!", "輸入錯誤", MessageBoxButtons.OK, MessageBoxIcon.Warning)
Return
End If
If age < 1 Or age > 150 Then
MessageBox.Show("年齡必須在 1-150 之間!", "輸入錯誤", MessageBoxButtons.OK, MessageBoxIcon.Warning)
Return
End If
If score < 0 Or score > 100 Then
MessageBox.Show("成績必須在 0-100 之間!", "輸入錯誤", MessageBoxButtons.OK, MessageBoxIcon.Warning)
Return
End If
' 調整陣列大小以容納新的學生資料
ReDim Preserve students(count)
' 建立新的學生結構實例並儲存到陣列中
students(count) = New Student(name, age, score)
' 增加學生數量
count += 1
' 清空輸入欄位
TextBox1.Clear()
TextBox2.Clear()
TextBox3.Clear()
' 更新學生列表
UpdateStudentList()
MessageBox.Show("學生資料已成功添加!", "操作成功", MessageBoxButtons.OK, MessageBoxIcon.Information)
Catch ex As Exception
MessageBox.Show("輸入資料格式錯誤,請檢查後重新輸入!", "輸入錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
If count = 0 Then
MessageBox.Show("目前沒有學生資料!", "資料不足", MessageBoxButtons.OK, MessageBoxIcon.Information)
Return
End If
' 計算班級統計資料
Dim scores() As Double = New Double(count - 1) {}
For i As Integer = 0 To count - 1
scores(i) = students(i).Score
Next
Dim maxScore As Double = scores.Max()
Dim minScore As Double = scores.Min()
Dim avgScore As Double = scores.Average()
' 顯示統計結果
Label1.Text = "最高分:" & maxScore.ToString("F2")
Label2.Text = "最低分:" & minScore.ToString("F2")
Label3.Text = "平均分:" & avgScore.ToString("F2")
' 統計各等級人數
Dim excellent As Integer = 0
Dim good As Integer = 0
Dim fair As Integer = 0
Dim pass As Integer = 0
Dim fail As Integer = 0
For i As Integer = 0 To count - 1
Select Case students(i).GetGrade()
Case "優秀"
excellent += 1
Case "良好"
good += 1
Case "中等"
fair += 1
Case "及格"
pass += 1
Case "不及格"
fail += 1
End Select
Next
Dim gradeStats As String = $"等級分布:" & vbCrLf &
$"優秀:{excellent}人" & vbCrLf &
$"良好:{good}人" & vbCrLf &
$"中等:{fair}人" & vbCrLf &
$"及格:{pass}人" & vbCrLf &
$"不及格:{fail}人"
MessageBox.Show(gradeStats, "成績統計", MessageBoxButtons.OK, MessageBoxIcon.Information)
End Sub
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
' 獲取使用者輸入的學生姓名
Dim searchName As String = TextBox1.Text.Trim()
If String.IsNullOrEmpty(searchName) Then
MessageBox.Show("請輸入要搜尋的學生姓名!", "輸入錯誤", MessageBoxButtons.OK, MessageBoxIcon.Warning)
Return
End If
' 在學生結構陣列中搜尋指定姓名
Dim found As Boolean = False
Dim foundStudent As Student
For i As Integer = 0 To count - 1
If students(i).Name.Equals(searchName, StringComparison.OrdinalIgnoreCase) Then
found = True
foundStudent = students(i)
Exit For
End If
Next
' 如果找到匹配的學生,顯示其詳細資訊;否則顯示未找到
If found Then
Label4.Text = "搜尋結果:" & foundStudent.ToString()
Else
Label4.Text = "未找到學生 """ & searchName & """ 的資訊。"
End If
End Sub
Private Sub UpdateStudentList()
' 清空學生列表
ListBox1.Items.Clear()
' 將學生資訊添加到列表中
For i As Integer = 0 To count - 1
ListBox1.Items.Add($"{i + 1}. {students(i).ToString()}")
Next
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' 初始化界面
Label1.Text = "最高分:--"
Label2.Text = "最低分:--"
Label3.Text = "平均分:--"
Label4.Text = "搜尋結果:--"
' 設定提示文字
TextBox1.PlaceholderText = "請輸入學生姓名"
TextBox2.PlaceholderText = "請輸入年齡"
TextBox3.PlaceholderText = "請輸入成績"
End Sub
End Class
在這個範例中,我們定義了一個 Student 結構,包含了學生的姓名、年齡和成績三個欄位。結構提供了多個方法:
- New:建構函式,用於初始化學生資料。
- GetGrade():根據成績返回對應的等級。
- ToString():返回學生資訊的字串表示。
- Equals():比較兩個學生結構是否相等。
主程式使用 Student 結構陣列來儲存所有學生的資料,並提供了添加學生、計算統計資料和搜尋學生等功能。這個範例展示了結構在實際應用中的使用方法和優勢。
結構與類別的比較
選擇使用結構還是類別是程式設計中的重要決策。需要綜合考慮以下幾個因素:
- 資料的大小和複雜度。對於小型、簡單的資料,使用結構更加高效;對於大型、複雜的資料,使用類別更加合適。
- 資料的使用場景。如果需要頻繁建立、銷毀和傳遞實例,使用結構可以提高效能;如果需要封裝複雜的行為和邏輯,使用類別更加靈活。
- 資料的生命週期。結構適合用於短暫存在的局部資料;類別適合用於長期存在的共享資料。
- 資料的語義。結構表示一組密切相關的值,強調資料的整體性;類別表示一個獨立的物件,強調物件的身份和行為。
| 資料類型 | 優點 | 缺點 |
|---|---|---|
|
結構 (Structure) 值型別結構的值型別特性就像是一套獨立的積木,每套積木都是完整且獨立的,不會與其他套積木共享部件。複製結構就像是複製整套積木,修改一套不會影響其他套。這樣可以確保每個結構實例的獨立性和資料安全性。 |
建立和銷毀速度快,佔用記憶體小,適合大量使用 傳遞時複製整個實例,不會影響原始資料 適合表示一組密切相關的小型資料 | 複製大型結構的開銷較大 轉換為物件(裝箱)或將物件轉換為結構(拆箱)會產生額外開銷 不適合封裝複雜的行為和邏輯 |
|
類別 (Class) 參考型別類別的參考型別特性就像是一套共享的大型積木,多個地方可以透過引用來使用同一套積木。複製類別實例就像是複製積木的使用權,而不是複製整套積木。這樣可以節省記憶體,方便共享資料,但也需要注意多個地方同時修改時的同步問題。 |
傳遞時只複製引用,節省記憶體和提高效能 支援繼承和多型,可以封裝複雜的行為和邏輯 適合表示獨立的物件,具有唯一的身份 | 建立和銷毀速度較慢,需要動態分配記憶體 多個引用指向同一個物件時,需要注意同步和一致性問題 不適合大量建立和銷毀的場景 |
在實際開發中,要根據具體的情況選擇合適的資料類型。對於簡單的、大量使用的資料,使用結構可以提高效能;對於複雜的、長期存在的物件,使用類別更加靈活。無論選擇哪種類型,都要注意其特性和使用場景,合理設計程式結構,優化資源利用,提高程式的效能和可維護性。
進階主題
結構作為一種基本的資料類型,還有一些進階的用法和注意事項。以下是一些常見的進階主題:
結構的實現介面
結構可以實現一個或多個介面,定義一組公共的行為規範。透過實現介面,結構可以與其他相容的物件進行互操作,提高程式的靈活性和可重用性。
範例:
Public Interface IShape
Function GetArea() As Double
Function GetPerimeter() As Double
End Interface
Public Structure Rectangle
Implements IShape
Public Width As Double
Public Height As Double
Public Sub New(width As Double, height As Double)
Me.Width = width
Me.Height = height
End Sub
Public Function GetArea() As Double Implements IShape.GetArea
Return Width * Height
End Function
Public Function GetPerimeter() As Double Implements IShape.GetPerimeter
Return 2 * (Width + Height)
End Function
Public Overrides Function ToString() As String
Return $"Rectangle: {Width}×{Height}"
End Function
End Structure
結構的相等性比較
由於結構是值型別,比較兩個結構的相等性時,是逐欄位進行值的比較。可以重寫 Equals 方法和 GetHashCode 方法,自定義結構的相等性比較邏輯。
範例:
Public Structure Point
Public X As Integer
Public Y As Integer
Public Sub New(x As Integer, y As Integer)
Me.X = x
Me.Y = y
End Sub
Public Overrides Function Equals(obj As Object) As Boolean
If TypeOf obj Is Point Then
Dim other As Point = DirectCast(obj, Point)
Return X = other.X AndAlso Y = other.Y
End If
Return False
End Function
Public Overrides Function GetHashCode() As Integer
Return X Xor Y
End Function
Public Shared Operator =(left As Point, right As Point) As Boolean
Return left.Equals(right)
End Operator
Public Shared Operator <>(left As Point, right As Point) As Boolean
Return Not left.Equals(right)
End Operator
End Structure
結構的設計原則
在設計結構時,應該遵循以下原則:
- 小型化:結構應該是小型的,通常不超過 16 個位元組。
- 不可變性:盡量使用唯讀欄位,避免在結構建立後修改其狀態。
- 值語義:結構應該表示值,而不是身份。
- 自包含:結構應該是自包含的,不依賴於外部狀態。
不可變結構範例:
Public Structure ImmutablePoint
Private ReadOnly _x As Integer
Private ReadOnly _y As Integer
Public Sub New(x As Integer, y As Integer)
_x = x
_y = y
End Sub
Public ReadOnly Property X As Integer
Get
Return _x
End Get
End Property
Public ReadOnly Property Y As Integer
Get
Return _y
End Get
End Property
Public Function Translate(deltaX As Integer, deltaY As Integer) As ImmutablePoint
Return New ImmutablePoint(_x + deltaX, _y + deltaY)
End Function
Public Overrides Function ToString() As String
Return $"({X}, {Y})"
End Function
End Structure
結構使用注意事項:
- 避免頻繁的裝箱和拆箱操作,這會產生額外的效能開銷。
- 當結構作為集合的元素時,修改結構的欄位可能不會反映在集合中。
- 結構不能定義無參數的建構函式,系統會自動提供一個預設的建構函式。
- 結構的所有欄位必須在建構函式中被初始化。
- 結構不支援繼承,但可以實現介面。