Excel VBA 教學:VBA 變數一次搞懂,型別、作用域、生命週期

 


這是一份給實務開發者的 Excel VBA 變數地圖:用最少力氣避開最多坑。從 Option Explicit 的基本功開始,帶你釐清值型別與物件型別差異、作用域(程序/模組/全域/Static)與生命週期、ByRef/ByVal 的副作用,以及為何 Long 勝過 Integer、Currency/Decimal 怎麼選。

文中提供可直接套用的宣告與命名範例、Range/Worksheet 常見寫法,並整理轉型、字串/日期比較的眉角。最後列出最常見的錯誤與排除心法(Type mismatch、91、1004…),讓你寫得穩、改得快。希望本篇文章可以幫助到需要的您。


目錄

{tocify} $title={目錄} 


先講結論(實務守則)

1.    在每個模組最上面放 Option Explicit,並在 VB 編輯器勾選「Require Variable Declaration」。

2.    宣告時每個變數都要寫一次 As 型別:

        Dim i As Long, n As Long(而不是 Dim i, n As Long)。

3.    以 Long 取代 Integer;少用 Variant,除非你真的需要可變型別或錯誤值。

4.    物件一定要用 Set 指派,結束時可視情況 Set obj = Nothing

    (特別是外部 COM 物件)。

5.    參數預設是 ByRef,有副作用;不想改到外面就用 ByVal。

6.    針對 Excel:常用 Workbook / Worksheet / Range 做早期綁定(Early Binding),避免寫 Object。

7.    轉型用顯式函式(CLng、CDbl、CBool、CDate…),不要仰賴暗暗的自動轉型。


變數的三個核心:型別、作用域(Scope)、生命週期(Lifetime)

1.    型別(值型別 vs 物件)

值型別:Byte, Boolean, Long, Single, Double, Currency, String, Date, Variant 等。

        Long(32 位)> Integer,在 VBA 內部算迴圈更快且不溢位。

        Double:浮點,速度快但有誤差;Currency:定點小數,做金額更穩。

        Date:其實是 Double(整數=日期,尾數=時間)。

        Decimal:不是獨立型別,是 Variant 的子型別,只能 Dim v As Variant: v = CDec(123.45).


物件型別:Workbook, Worksheet, Range, Dictionary, Collection…

        宣告 Dim ws As Worksheet,指派用 Set ws = ...。

        釋放:Set ws = Nothing(Excel 內部物件通常不用強制,但外部 COM 物件建議釋放)。


2.    作用域

程序內變數:Dim x As Long 放在 Sub/Function 內,只在這個程序看得到。

模組層級:在「標準模組」最上方 Private mCounter As Long,整個模組可用。

全域:在標準模組最上方 Public AppName As String,整個專案可見。

Static:在程序內 Static hit As Long,變數值會在下次呼叫時保留。


3.    生命週期

程序內 Dim:跑完程序就釋放。

Static:專案仍開著就保留。

模組層級 / 全域:直到專案卸載(關閉 Excel 或重置 VBE)才釋放。


怎麼寫(宣告與命名示範)

Option Explicit  ' 強制宣告

' 模組層級變數(Private:只在此模組可見)
Private mRunCount As Long

' 全域常數(放標準模組)
Public Const APP_NAME As String = "StockCleaner"

Sub DemoVariables()
    ' 值型別
    Dim i As Long, total As Double
    Dim isOk As Boolean
    Dim when As Date

    ' Decimal 只能透過 Variant + CDec
    Dim price As Variant
    price = CDec(12345.67)

    ' 物件型別(Excel 常用)
    Dim wb As Workbook, ws As Worksheet, rng As Range
    Set wb = ThisWorkbook
    Set ws = wb.Worksheets("Data")
    Set rng = ws.Range("A1:A100")

    ' 字串
    Dim name As String
    name = "Alice"

    ' 計算
    For i = 1 To rng.Rows.Count
        total = total + CDbl(rng.Cells(i, 1).Value)
    Next

    ' ByVal/ByRef 的差在於會不會改到外面的變數
    isOk = CheckTotal(ByValValue:=total)

    mRunCount = mRunCount + 1
End Sub

Private Function CheckTotal(ByVal ByValValue As Double) As Boolean
    ' 不會外溢修改呼叫端的變數
    CheckTotal = (ByValValue > 0)
End Function

 

命名小撇步

物件前綴好辨識:wb、ws、rng、dic、col。

模組層級變數可加 m 前綴:mRunCount。

常數全大寫:APP_NAME、MAX_ROWS。


Excel 物件變數的日常

Sub SumRange()
    Option Explicit
    Dim ws As Worksheet
    Dim rng As Range
    Dim sumVal As Double

    Set ws = ThisWorkbook.Worksheets("Sales")
    Set rng = ws.Range("B2:B100")

    ' 盡量用 .Value2(更快、避免日期/貨幣自動轉換)
    sumVal = Application.WorksheetFunction.Sum(rng.Value2)

    MsgBox "Total: " & CStr(sumVal)
End Sub


Value2 比 Value 少了格式干擾,速度也更好。

WorksheetFunction 可能丟錯(#N/A…),可搭配 On Error Resume Next + 檢查,或自行迴圈。


轉型與比較(不要讓自動轉型扯你後腿)

顯式轉型:CLng, CDbl, CBool, CStr, CDate, CVar, CDec。

字串比較受 Option Compare 影響:

        Option Compare Binary(預設,大小寫/排序嚴格)

        Option Compare Text(不分大小寫)

Date 建議用 DateSerial / TimeSerial 建構,避免字串轉日期因地區格式踩雷。

Dim d As Date
d = DateSerial(2025, 9, 21) + TimeSerial(14, 30, 0)


常見型別要點速查

Boolean:True = -1、False = 0。算術運算會跟著帶入 -1/0。

String:vbNullString 比 "" 更省記憶體(呼叫 API 時常用)。

Variant:

        萬用,但慢、容易導致不預期的型別轉換。

        可裝 Empty、Null、Error、Array。對 Error 用 IsError() 判。

Decimal(Variant 子型別):金額高精度,宣告要 Variant + CDec。

Currency:固定四位小數,金額計算穩、快。

Date:CLng(Date) 可取序號;Int(d) 取日期、d - Int(d) 取時間。


物件變數的典型雷點

1.    忘記 Set

Dim ws As Worksheet
ws = ThisWorkbook.Worksheets(1)   ' ❌ 會 Type mismatch
Set ws = ThisWorkbook.Worksheets(1) ' ✅


2.    Dim a, b As Long 只有 b 是 Long,a 是 Variant

Dim a As Long, b As Long  ' ✅ 每個都寫型別


3.    As New 的隱性成本(會在每次使用前檢查是否為 Nothing,自動 new 一個)

Dim col As New Collection  ' ⚠️ 不建議大量重複呼叫
Dim col As Collection      ' ✅
Set col = New Collection


4.    早期 vs 晚期綁定

早期(有參考,IntelliSense、較快):Dim dic As Scripting.Dictionary(要參考 Microsoft Scripting Runtime)

晚期(免參考,少 IntelliSense):Dim dic As Object: Set dic = CreateObject("Scripting.Dictionary")


5.    With 區塊裡忘了加點

With ws
    .Range("A1").Value = "Hi" ' ✅
    Range("A2").Value = "Oops" ' ❌ 其實是 ActiveSheet
End With


6.    全域變數污染

能用參數就不用全域;若要全域,用有區分度的命名,集中放一處。


7.    64 位元相容

LongLong / LongPtr 僅供 API/位址用;一般計數仍用 Long。


ByRef vs ByVal(雖是參數,但本質就是變數如何被傳遞)

Sub Foo()
    Dim n As Long: n = 10
    Inc n        ' 預設 ByRef → n 會變 11
    IncByVal n   ' ByVal → n 還是 11,不會再被改
End Sub

Private Sub Inc(ByRef x As Long)
    x = x + 1
End Sub

Private Sub IncByVal(ByVal x As Long)
    x = x + 1
End Sub


常見錯誤與排除清單

1.    Type mismatch (13):值/物件搞混、Variant 裡頭裝了 Error、或字串轉數值失敗。           → 檢查 IsNumeric、顯式轉型。

2.    Object variable not set (91):忘 Set、或物件已變 Nothing。

        → 先 If obj Is Nothing Then。

3.    Overflow (6):Integer 溢位、或把大數塞到 Long/Date。

        → 換較大型別、檢查來源。

4.    Subscript out of range (9):陣列/集合/工作表索引超界。

        → 先檢查存在性。

5.    Application-defined or object-defined error (1004):

      多半是 Range/工作表參照錯。

      → 用合格參照 wb.Worksheets("...").Range(...)。


Const / Enum / UDT(讓變數更安全好讀)

Public Const MAX_COL As Long = 26

Public Enum OrderStatus
    osPending = 0
    osPaid = 1
    osShipped = 2
End Enum

' 使用者自訂型別(放標準模組)
Public Type OrderInfo
    Id As Long
    Amount As Currency
    Status As OrderStatus
End Type

Sub UseUDT()
    Dim o As OrderInfo
    o.Id = 1001
    o.Amount = 1999.99
    o.Status = osPaid
End Sub


Enum 讓魔法數字消失;Type 把多欄資料綁在一起(Class 需要時再上)。


實戰模板(可直接複製起跑)

Option Explicit
'======== Config / Globals ========
Public Const APP_TITLE As String = "Sales Audit"
Private mRun As Long

'======== Entry ========
Public Sub RunAudit()
    Dim wb As Workbook, ws As Worksheet, rng As Range
    Dim total As Currency, i As Long

    Set wb = ThisWorkbook
    Set ws = wb.Worksheets("Sales")
    Set rng = ws.Range("B2:B" & ws.Cells(ws.Rows.Count, "B").End(xlUp).Row)

    For i = 1 To rng.Rows.Count
        total = total + CCur(NzNumber(rng.Cells(i, 1).Value2))
    Next

    mRun = mRun + 1
    MsgBox APP_TITLE & vbCrLf & "Total=" & FormatCurrency(total) & vbCrLf & "#Runs=" & mRun
End Sub

' 將非數字/空值視為 0
Private Function NzNumber(ByVal v As Variant) As Double
    If IsError(v) Or IsNull(v) Or IsEmpty(v) Or Trim$(CStr(v)) = vbNullString Then
        NzNumber = 0#
    ElseIf IsNumeric(v) Then
        NzNumber = CDbl(v)
    Else
        NzNumber = 0#
    End If
End Function


速查表

1.    Option Explicit:一定要。

2.    Long > Integer;金額用 Currency 或 Decimal(Variant+CDec)。

3.    每個變數都寫一次 As 型別。

4.    物件要 Set;外部物件最後 = Nothing。

5.    參數要防副作用→ ByVal。

6.    轉型用 C* 系列函式。

7.    Value2 > Value(速度/穩定)。

8.    字串比較受 Option Compare 影響。

9.    需要多欄關聯就用 Enum / Type / Class。


問題集

Q1:真的需要 Set obj = Nothing 嗎?

    Excel 內部物件通常不必;但外部 COM(如 Word、Outlook、ADO、FileSystemObject)建議釋放,避免殘留程序或鎖定資源。

Q2:要不要全域變數?

    能不用就不用。可改以「函式傳參數」或「回傳結果」;若要全域,集中管理、命名清楚,避免到處修改。

Q3:Variant 很萬用,可以都用它嗎?

    不建議。它慢、容易被自動轉型坑到。只在你真的需要多型(如可能回傳數字或錯誤)時用。

Q4:金額用 Double 可以嗎?

    會有二進位浮點誤差。用 Currency 或 Decimal(Variant+CDec) 比較穩。

Q5:64 位 Excel 需要改什麼?

    一般變數不變;只有 API 宣告/指標要用 LongPtr 或條件編譯(#If VBA7 Then)。


延伸閱讀推薦:

張貼留言 (0)
較新的 較舊