VBA中的過(guò)程(Procedure)有兩種,一種叫函數(shù)(Function),另外一種叫子程序(Subroutine),分別使用Function和Sub關(guān)鍵字。它們都是一個(gè)可以獲取參數(shù)、執(zhí)行一系列語(yǔ)句、以及改變其參數(shù)的值的獨(dú)立過(guò)程。而與 Function 過(guò)程不同的是:帶返回值的 Sub 過(guò)程不能用于表達(dá)式。
這里主要介紹子程序的使用方法,同樣這些方法也可以應(yīng)用到Function上。
語(yǔ)法
[Private | Public | Friend] [Static] Sub name [(arglist)]
[statements]
[Exit Sub]
[statements]
End Sub
* 用[]符號(hào)括起來(lái)的選項(xiàng)是可選項(xiàng)
[Private | Public | Friend]
這三個(gè)關(guān)鍵字與作用范圍有關(guān)。
Private表示私有,即這個(gè)過(guò)程只能從本模塊里面調(diào)用。使用這個(gè)關(guān)鍵字,從菜單“工具”->”宏”->”宏…”中將看不到該過(guò)程。
Public表示公用,這樣從其它的模塊也可以訪問(wèn)這個(gè)過(guò)程。如果沒(méi)有使用 Public、Private 或 Friend 顯式指定,Sub 過(guò)程按缺省情況就是公用的。公用的過(guò)程可以從菜單”工具”->”宏”->”宏…”中看到。
Friend用在類模塊里面,較少使用,在此就不介紹了。
Static是靜態(tài)的意思(你可能還記得用Static聲明靜態(tài)變量),用它聲明過(guò)程的話,表示這個(gè)過(guò)程中聲明的局部變量在下次調(diào)用這個(gè)過(guò)程時(shí)仍然保持它原來(lái)的值。
下面是Static聲明過(guò)程的用法。
Static Sub m1()Dim i As IntegerDim j As Integeri = i + 1j = j + 1Debug.Print "i=" & i & " j=" & jEnd SubPrivate Sub m2()Dim i As IntegerDim j As Integeri = i + 1j = j + 1Debug.Print "i=" & i & " j=" & jEnd SubSub try1()Dim i As IntegerDebug.Print "靜態(tài)過(guò)程:"For i = 1 To 10Call m1Next iDebug.Print "私有過(guò)程:"For i = 1 To 10Call m2Next iEnd Sub
運(yùn)行try1過(guò)程,然后可以在立即窗口里看到結(jié)果。
=

下面是一些使用過(guò)程時(shí)需要注意到地方。
- Sub過(guò)程可以是遞歸的;也就是說(shuō),該過(guò)程可以調(diào)用自己來(lái)完成某個(gè)特定的任務(wù)。不過(guò),遞歸可能會(huì)導(dǎo)致堆棧上溢。通常 Static 關(guān)鍵字和遞歸的 Sub 過(guò)程不在一起使用。
- 所有的可執(zhí)行代碼都必須屬于某個(gè)過(guò)程。不能在別的 Sub、Function 或 Property 過(guò)程中定義 Sub 過(guò)程。
- Exit Sub 語(yǔ)句使執(zhí)行立即從一個(gè) Sub 過(guò)程中退出。程序接著從調(diào)用該 Sub 過(guò)程的語(yǔ)句下一條語(yǔ)句執(zhí)行。在 Sub 過(guò)程的任何位置都可以有 Exit Sub 語(yǔ)句。
- 在Sub過(guò)程中使用的變量分為兩類:一類是在過(guò)程內(nèi)顯式定義的,另一類則不是。在過(guò)程內(nèi)顯式定義的變量(使用 Dim或等效方法)都是局部變量。對(duì)于使用了但又沒(méi)有在過(guò)程中顯式定義的變量,除非其在該過(guò)程外更高級(jí)別的位置有顯示地定義,否則也是局部的。
- 小心過(guò)程可以使用沒(méi)有在過(guò)程內(nèi)顯式定義的變量,但只要有任何在模塊級(jí)別定義的名稱與之同名,就會(huì)產(chǎn)生名稱沖突。如果過(guò)程中使用的未定義的變量與別的過(guò)程,常數(shù),或變量的名稱相同,則認(rèn)為過(guò)程使用的是模塊級(jí)的名稱。顯式定義變量就可以避免這類沖突??梢允褂?Option Explicit 語(yǔ)句來(lái)強(qiáng)制顯式定義變量。
- 注意:不能使用 GoSub、GoTo 或 Return 來(lái)進(jìn)入或退出 Sub 過(guò)程。
參數(shù)表arglist
語(yǔ)法:
[Optional] [ByVal | ByRef] [ParamArray] varname[( )] [As type] [= defaultvalue]
Optional表示這個(gè)參數(shù)是可選的,也就是說(shuō)在調(diào)用過(guò)程時(shí)可以不傳遞值也可以傳遞值給這個(gè)參數(shù).。如果有傳遞defaultvalue給這個(gè)參數(shù)時(shí)(如optional iInput2 As Integer=13), 當(dāng)調(diào)用過(guò)程沒(méi)有傳遞值給這個(gè)參數(shù)時(shí),在過(guò)程中會(huì)默認(rèn)使用這個(gè)defaultvalue。
Optional必須對(duì)最后面的那些參數(shù)使用,也就是說(shuō)某個(gè)參數(shù)使用了Optional,該參數(shù)后面的參數(shù)也必須使用Optional。
Sub mmm(iInput1 As Integer, Optional iInput2 As Integer = 13, Optional iInput3 As Integer)'iInput2用了Optional后,iInput3也必須用Optional MsgBox "iInput1=" & iInput1 & vbCrLf & "iInput2=" & iInput2 & vbCrLf & "iInput3=" & iInput3End SubSub subTry()' 可以給3個(gè)參數(shù)都賦值 Call mmm(23, 34, 2) 'iInput=23, iInput2=34, iInput3=2 '也可以給第3個(gè)賦值,而不給第2個(gè)參數(shù)賦值,這樣iInput2會(huì)等于默認(rèn)值13 Call mmm(23, , 2) 'iInput=23, iInput2=13, iInput3=2 '可以給第2個(gè)參數(shù)都賦值而不給第3個(gè)參數(shù)賦值 Call mmm(23, 34) 'iInput=23, iInput2=34, iInput3=0 ' 第2個(gè),第3個(gè)參數(shù)都不賦值 Call mmm(23) 'iInput=23, iInput2=12, iInput3=0End Sub
ParamArray的用法
ParamArray只能用于 arglist 的最后一個(gè)參數(shù),指明最后這個(gè)參數(shù)是一個(gè)包含Variant類型元素的 Optional 數(shù)組,但你傳遞值給過(guò)程時(shí)還是使用逗號(hào)分開(kāi)多個(gè)參數(shù),過(guò)程里面會(huì)把找?guī)讉€(gè)參數(shù)合并成一個(gè)數(shù)組。使用 ParamArray 的好處是你可以提供不定數(shù)目不定類型的參數(shù)給過(guò)程。ParamArray不能與 ByVal,ByRef,或 Optional 一起使用。
Sub m1(iInput1 As Integer, ParamArray argArr())Dim strList As StringDim i As Integer strList = "iInput1=" & iInput1 & vbCrLfFor i = 0 To UBound(argArr)On Error Resume NextstrList = strList & "argArr(" & i & ")=" & argArr(i) & vbCrLf' 如果參數(shù)為空,將生成錯(cuò)誤 If Err.Description <> "" ThenstrList = strList & "argArr(" & i & ")=缺失參數(shù)" & vbCrLfErr.Clear ' 清除錯(cuò)誤 End IfNext iMsgBox strListEnd SubSub try1()Call m1(23, 24, 25) ' iInput=23, argArr(0)=24, argArr(1)=25 Call m1(23, 24, , 25) ' iInput=23, argArr(0)=24, argArr(1)=缺失參數(shù), argArr(2)=25 Call m1(23, 24, 64.4, 25, "data") ' iInput=23, argArr(0)=24, argArr(1)=64.4, argArr(2)=25, argArr(3)=dataEnd Sub
ByVal和ByRef
VBA中默認(rèn)使用ByRef。ByVal的意思是按值傳遞參數(shù),因?yàn)槭前粗祩鬟f,這個(gè)參數(shù)在過(guò)程里面的值有變化的話它影響的范圍只是在這個(gè)過(guò)程里面。出了過(guò)程就沒(méi)有用了。而ByRef是按地址或者說(shuō)按引用傳遞,傳遞給過(guò)程的實(shí)際是這個(gè)數(shù)值的地址,而不是值本身,在過(guò)程中對(duì)改變這個(gè)參數(shù)也就是改變這個(gè)地址的值,這樣在過(guò)程外面也可以看到這個(gè)值被改變了。運(yùn)行下面的例子可以看到其中的區(qū)別。
Sub mmm(ByVal iI1 As Integer, iI2 As Integer)iI1 = iI1 + 10iI2 = iI2 + 10MsgBox "Inside: iI1=" & iI1 & " iI2=" & iI2End SubSub mySub()Dim iI1 As IntegerDim iI2 As IntegeriI1 = 10iI2 = 12MsgBox "Before: iI1=" & iI1 & " iI2=" & iI2Call mmm(iI1, iI2)MsgBox "After: iI1=" & iI1 & " iI2=" & iI2' 按順序分別顯示 ' 顯示 Before: iI1=10 iI2=12 ' 顯示 Before: iI1=20 iI2=22 ' 顯示 Before: iI1=10 iI2=22 ' 過(guò)程mmm中修改了iI1和iI2,但是iI1是按值傳遞,在mmm過(guò)程之外的iI1并受影響 ' 而iI2是按引用傳遞,mmm過(guò)程之外的iI2也被改變了End Sub
另外,如果參數(shù)是數(shù)組的話,只能使用按引用傳遞,因?yàn)閭鬟f的實(shí)際上是數(shù)組第一個(gè)元素的地址。例如下面代碼的用法。
Sub GetArray(arrTemp() As Integer)Dim i As IntegerFor i = 0 To UBound(arrTemp)Debug.Print "Item " & i & ": " & arrTemp(i)Next iEnd SubSub PassArray()Dim arrInt(3) As IntegerarrInt(0) = 1arrInt(1) = 2arrInt(2) = 3arrInt(3) = 4Call GetArray(arrInt)' 在立即窗口打印出 ' Item 0: 1 ' Item 1: 2 ' Item 2: 3 ' Item 3: 4End Sub
但對(duì)于對(duì)象來(lái)說(shuō),使用ByVal實(shí)際上傳遞的仍然是對(duì)對(duì)象的引用,這樣在過(guò)程中對(duì)象的修改將會(huì)影響過(guò)程外部對(duì)象的值或?qū)傩?。如果使用ByVal,而在過(guò)程中創(chuàng)建一個(gè)新的對(duì)象實(shí)例,將該對(duì)象賦值給傳遞的對(duì)象,則不影響調(diào)用對(duì)象的外部的屬性或值。而如果使用ByRef,在過(guò)程中創(chuàng)建一個(gè)新的對(duì)象實(shí)例,將該對(duì)象賦給傳遞的對(duì)象,卻會(huì)影響過(guò)程外該對(duì)象的屬性或值。
Sub TestByValByRef()Dim objDic As Object Set objDic = CreateObject("Scripting.Dictionary")objDic(1) = 100Debug.Print "byValueTest1"Debug.Print "原始值: objDic(1)=" & objDic(1)Call byValTest1(objDic)Debug.Print "外部值: objDic(1)=" & objDic(1) & vbCrLf objDic(1) = 100Debug.Print "byValueTest2"Debug.Print "原始值: objDic(1)=" & objDic(1)Call byValTest2(objDic)Debug.Print "外部值: objDic(1)=" & objDic(1) & vbCrLf objDic(1) = 100Debug.Print "byRefTest"Debug.Print "原始值: objDic(1)=" & objDic(1)Call byRefTest(objDic)Debug.Print "外部值: objDic(1)=" & objDic(1)End SubPrivate Sub byValTest1(ByVal c As Object)Dim a As ObjectSet a = CreateObject("Scripting.Dictionary")a(1) = 200Set c = aEnd SubPrivate Sub byValTest2(ByVal c As Object)c(1) = 200End SubPrivate Sub byRefTest(ByRef c As Object)Dim a As ObjectSet a = CreateObject("Scripting.Dictionary")a(1) = 200Set c = aEnd Sub
運(yùn)行過(guò)程TestByValByRef,將在立即窗口打印出下面的結(jié)果。
byValueTest1
原始值: objDic(1)=100
外部值: objDic(1)=100byValueTest2
原始值: objDic(1)=100
外部值: objDic(1)=200byRefTest
原始值: objDic(1)=100
外部值: objDic(1)=200