VB.NET 堆疊(Stack) 筆記(基礎篇)
Stack(Of T) 是用來保存資料的一種集合,特色是「最後放入的資料,會最先被取出」。這種順序稱為 LIFO,也就是 Last In, First Out。
堆疊適合處理需要「回到最近一步」、「先處理最上層」、「暫存一連串待還原動作」的流程。理解 Stack 時,重點不是背方法名稱,而是看懂資料從哪裡進、從哪裡出,以及為什麼只能優先操作頂端資料。
先理解 Stack 的順序
Stack 的核心:後放上去的先拿下來
Stack 可以想成一疊由上往下堆放的資料。新增資料時放在最上面;取資料時,也從最上面開始取。因此最後加入的資料,會比前面加入的資料更早被處理。
- Push:把資料放到堆疊頂端。
- Pop:取出頂端資料,並從堆疊移除。
- Peek:查看頂端資料,但不移除。
- Count:查看目前有幾筆資料。
適合使用 Stack 的判斷方式:
- 只需要處理最新加入的資料:例如最新動作、最上層項目、最近一筆紀錄。
- 需要回復最近一步:例如撤銷、退回、回到上一個狀態。
- 需要成對檢查:例如開啟標記與關閉標記是否正確對應。
| 操作 | 做什麼 | 是否改變堆疊 |
|---|---|---|
| Push | 把資料放到頂端。 | 會增加一筆資料。 |
| Pop | 取出頂端資料。 | 會移除一筆資料。 |
| Peek | 查看頂端資料。 | 不會移除資料。 |
| Count | 取得資料筆數。 | 不會移除資料。 |
Stack(Of T) 的基本操作
場景一:客服便條堆疊
這個範例用客服便條來示範 Push、Peek、Pop。新便條會放到最上方,查看時看到最上方便條,處理時也會先取出最上方便條。
需要的主控項
TextBoxSlip:輸入便條內容。ButtonAddSlip:放入便條。ButtonViewTopSlip:查看最上方便條。ButtonProcessSlip:處理最上方便條。LabelSlipStatus:顯示目前狀態。LabelSlipCount:顯示便條數量。
範例程式碼
VB.NET / Windows Forms
Imports System.Collections.Generic
Public Class Form1
Private serviceSlips As New Stack(Of String)
Private Sub ButtonAddSlip_Click(sender As Object, e As EventArgs) Handles ButtonAddSlip.Click
Dim slipText As String = TextBoxSlip.Text.Trim()
If slipText = String.Empty Then
LabelSlipStatus.Text = "請先輸入便條內容"
Return
End If
serviceSlips.Push(slipText)
TextBoxSlip.Clear()
RefreshSlipStatus("已放入便條:" & slipText)
End Sub
Private Sub ButtonViewTopSlip_Click(sender As Object, e As EventArgs) Handles ButtonViewTopSlip.Click
If serviceSlips.Count = 0 Then
RefreshSlipStatus("目前沒有便條")
Return
End If
RefreshSlipStatus("最上方便條:" & serviceSlips.Peek())
End Sub
Private Sub ButtonProcessSlip_Click(sender As Object, e As EventArgs) Handles ButtonProcessSlip.Click
If serviceSlips.Count = 0 Then
RefreshSlipStatus("沒有可處理的便條")
Return
End If
Dim doneSlip As String = serviceSlips.Pop()
RefreshSlipStatus("已處理便條:" & doneSlip)
End Sub
Private Sub RefreshSlipStatus(ByVal message As String)
LabelSlipStatus.Text = message
LabelSlipCount.Text = "便條數量:" & serviceSlips.Count.ToString()
End Sub
End Class
操作結果範例
依序放入:查詢發票、修改電話、補寄通知
查看最上方便條:補寄通知
處理最上方便條:補寄通知
便條數量:2邏輯解析
Push會把新便條放到最上方。Peek只查看最上方便條,數量不會減少。Pop會取出並移除最上方便條。- 每次
Pop或Peek前都先檢查Count,避免空堆疊例外。
應用一:撤銷最近動作
場景二:海報編輯動作撤銷
撤銷功能很適合使用 Stack。每次編輯都把動作記錄放進堆疊;按下撤銷時,最後做的動作會最先被取出。
需要的主控項
ButtonAddTitle:加入標題動作。ButtonAddPhoto:加入照片動作。ButtonChangeColor:加入換色動作。ButtonUndo:撤銷最近動作。TextBoxLog:顯示動作紀錄,建議設定Multiline=True。
範例程式碼
VB.NET / Windows Forms
Imports System.Collections.Generic
Public Class PosterAction
Public Property ActionName As String
Public Property DetailText As String
Public Sub New(ByVal actionName As String, ByVal detailText As String)
Me.ActionName = actionName
Me.DetailText = detailText
End Sub
Public Function BuildText() As String
Return ActionName & "|" & DetailText
End Function
End Class
Public Class Form1
Private undoStack As New Stack(Of PosterAction)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
TextBoxLog.Multiline = True
TextBoxLog.ScrollBars = ScrollBars.Vertical
End Sub
Private Sub ButtonAddTitle_Click(sender As Object, e As EventArgs) Handles ButtonAddTitle.Click
AddPosterAction("加入標題", "春季活動海報")
End Sub
Private Sub ButtonAddPhoto_Click(sender As Object, e As EventArgs) Handles ButtonAddPhoto.Click
AddPosterAction("加入照片", "主視覺圖片")
End Sub
Private Sub ButtonChangeColor_Click(sender As Object, e As EventArgs) Handles ButtonChangeColor.Click
AddPosterAction("更換色系", "橘色主題")
End Sub
Private Sub ButtonUndo_Click(sender As Object, e As EventArgs) Handles ButtonUndo.Click
If undoStack.Count = 0 Then
TextBoxLog.AppendText("沒有可撤銷的動作" & Environment.NewLine)
Return
End If
Dim lastAction As PosterAction = undoStack.Pop()
TextBoxLog.AppendText("撤銷:" & lastAction.BuildText() & Environment.NewLine)
End Sub
Private Sub AddPosterAction(ByVal actionName As String, ByVal detailText As String)
Dim action As New PosterAction(actionName, detailText)
undoStack.Push(action)
TextBoxLog.AppendText("完成:" & action.BuildText() & Environment.NewLine)
End Sub
End Class
操作結果範例(TextBoxLog)
完成:加入標題|春季活動海報
完成:加入照片|主視覺圖片
完成:更換色系|橘色主題
撤銷:更換色系|橘色主題邏輯解析
- 每次編輯海報時,都把動作物件放入
undoStack。 - 最後完成的「更換色系」位於頂端,因此最先被撤銷。
- 堆疊不需要搜尋中間資料,因為撤銷永遠處理最近一次動作。
應用二:觀察堆疊列舉順序
場景三:收納箱從上往下列出
列舉 Stack 時,會從頂端開始往下列出。這點和一般 List 的直覺不同,因此很適合用收納箱示範:最後放上去的物品會先被看到。
需要的主控項
ButtonShowBox:顯示收納箱內容。ListBoxBoxItems:列出物品順序。
範例程式碼
VB.NET / Windows Forms
Imports System.Collections.Generic
Public Class Form1
Private boxItems As New Stack(Of String)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
boxItems.Push("桌墊")
boxItems.Push("筆記本")
boxItems.Push("充電線")
boxItems.Push("識別證套")
End Sub
Private Sub ButtonShowBox_Click(sender As Object, e As EventArgs) Handles ButtonShowBox.Click
ListBoxBoxItems.Items.Clear()
For Each itemName As String In boxItems
ListBoxBoxItems.Items.Add(itemName)
Next
End Sub
End Class
畫面輸出結果(ListBoxBoxItems)
識別證套
充電線
筆記本
桌墊邏輯解析
- 加入順序是桌墊、筆記本、充電線、識別證套。
- 列舉時從頂端開始,因此先看到最後放入的識別證套。
- 若需要按照原始加入順序顯示,Stack 不是最直覺的集合。
應用三:檢查成對標記
場景四:檢查菜單模板標記是否閉合
成對符號檢查是 Stack 的典型應用。這裡不用抽象括號題,而是改成菜單模板:模板中可能使用 {}、[]、() 標記欄位,檢查時需要確認每個開啟標記都有正確閉合。
需要的主控項
TextBoxTemplate:輸入菜單模板文字。ButtonCheckTemplate:檢查標記。LabelCheckResult:顯示檢查結果。
範例程式碼
VB.NET / Windows Forms
Imports System.Collections.Generic
Public Class Form1
Private Sub ButtonCheckTemplate_Click(sender As Object, e As EventArgs) Handles ButtonCheckTemplate.Click
Dim templateText As String = TextBoxTemplate.Text
If IsMarkerPairValid(templateText) Then
LabelCheckResult.Text = "模板標記正確"
Else
LabelCheckResult.Text = "模板標記不完整或順序錯誤"
End If
End Sub
Private Function IsMarkerPairValid(ByVal text As String) As Boolean
Dim markerStack As New Stack(Of Char)
For Each ch As Char In text
If ch = "("c OrElse ch = "["c OrElse ch = "{"c Then
markerStack.Push(ch)
ElseIf ch = ")"c OrElse ch = "]"c OrElse ch = "}"c Then
If markerStack.Count = 0 Then
Return False
End If
Dim leftMarker As Char = markerStack.Pop()
If Not IsPair(leftMarker, ch) Then
Return False
End If
End If
Next
Return markerStack.Count = 0
End Function
Private Function IsPair(ByVal leftMarker As Char, ByVal rightMarker As Char) As Boolean
Return (leftMarker = "("c AndAlso rightMarker = ")"c) OrElse
(leftMarker = "["c AndAlso rightMarker = "]"c) OrElse
(leftMarker = "{"c AndAlso rightMarker = "}"c)
End Function
End Class
畫面輸出結果(TextBoxTemplate = 今日套餐{主餐[飲品(冰量)]})
模板標記正確邏輯解析
- 遇到左標記時使用
Push放入堆疊。 - 遇到右標記時使用
Pop取出最近的左標記進行配對。 - 若右標記出現時堆疊是空的,代表沒有對應的左標記。
- 檢查結束後堆疊必須為空,才代表所有左標記都已閉合。
Stack 與其他集合的差異
| 集合 | 適合情境 | 取資料順序 |
|---|---|---|
| Stack(Of T) | 撤銷、回退、最近資料優先處理。 | 後進先出。 |
| Queue(Of T) | 排隊、叫號、先到先處理。 | 先進先出。 |
| List(Of T) | 需要索引、排序、搜尋、列出全部資料。 | 依索引位置操作。 |
常見誤區
- 把 Stack 當成一般清單:若常常需要讀中間元素,應改用
List(Of T)。 - 忘記空堆疊檢查:對空堆疊呼叫
Pop或Peek會發生例外。 - 需求其實是排隊:若要先進先出,應使用
Queue(Of T)。 - 只記方法不看順序:Stack 的重點是 LIFO,不是單純會新增與移除資料。
效能與使用限制
| 操作 | 時間複雜度 | 說明 |
|---|---|---|
| Push | O(1) | 把元素放到頂端。 |
| Pop | O(1) | 取出並移除頂端元素。 |
| Peek | O(1) | 查看頂端元素。 |
| Count | O(1) | 取得元素數量。 |
使用限制:Stack 很適合操作頂端資料,但不適合頻繁搜尋、排序或直接修改中間資料。若需求不是「最近加入的資料優先處理」,通常要先考慮其他集合。
重點整理
Stack(Of T)是後進先出的集合。Push會把資料放到頂端。Pop會取出並移除頂端資料。Peek只查看頂端資料,不會移除。- 呼叫
Pop或Peek前應先檢查Count。 - Stack 適合撤銷、回退、最近資料優先處理與成對標記檢查。
- 若需求是先進先出,應使用
Queue(Of T)。 - 若需求是索引、排序或搜尋,通常更適合
List(Of T)。