分享

VBA的过程及参数详解

 yuxinrong 2010-01-12

VBA的过程及参数详解

2009年1月23日

VBA中的过程(Procedure)有两种,一种叫函数(Function),另外一种叫子程序(Subroutine),分别使用Function和Sub关键字。它们都是一个可以获取参数、执行一系列语句、以及改变其参数的值的独立过程。而与 Function 过程不同的是:带返回值的 Sub 过程不能用于表达式。

这里主要介绍子程序的使用方法,同样这些方法也可以应用到Function上。

语法

[Private | Public | Friend] [Static] Sub name [(arglist)]
[statements]
[Exit Sub]
[statements]
End Sub
* 用[]符号括起来的选项是可选项

[Private | Public | Friend]
这三个关键字与作用范围有关。
Private表示私有,即这个过程只能从本模块里面调用。使用这个关键字,从菜单“工具”->”宏”->”宏…”中将看不到该过程。
Public表示公用,这样从其它的模块也可以访问这个过程。如果没有使用 Public、Private 或 Friend 显式指定,Sub 过程按缺省情况就是公用的。公用的过程可以从菜单”工具”->”宏”->”宏…”中看到。
Friend用在类模块里面,较少使用,在此就不介绍了。

Static是静态的意思(你可能还记得用Static声明静态变量),用它声明过程的话,表示这个过程中声明的局部变量在下次调用这个过程时仍然保持它原来的值。
下面是Static声明过程的用法。

Static Sub m1()
Dim i As Integer
Dim j As Integer
i = i + 1
j = j + 1
Debug.Print "i=" & i & " j=" & j
End Sub
Private Sub m2()
Dim i As Integer
Dim j As Integer
i = i + 1
j = j + 1
Debug.Print "i=" & i & " j=" & j
End Sub
Sub try1()
Dim i As Integer
Debug.Print "静态过程:"
For i = 1 To 10
Call m1
Next i
Debug.Print "私有过程:"
For i = 1 To 10
Call m2
Next i
End Sub

运行try1过程,然后可以在立即窗口里看到结果。
=

下面是一些使用过程时需要注意到地方。

  • Sub过程可以是递归的;也就是说,该过程可以调用自己来完成某个特定的任务。不过,递归可能会导致堆栈上溢。通常 Static 关键字和递归的 Sub 过程不在一起使用。
  • 所有的可执行代码都必须属于某个过程。不能在别的 Sub、Function 或 Property 过程中定义 Sub 过程。
  • Exit Sub 语句使执行立即从一个 Sub 过程中退出。程序接着从调用该 Sub 过程的语句下一条语句执行。在 Sub 过程的任何位置都可以有 Exit Sub 语句。
  • 在Sub过程中使用的变量分为两类:一类是在过程内显式定义的,另一类则不是。在过程内显式定义的变量(使用 Dim或等效方法)都是局部变量。对于使用了但又没有在过程中显式定义的变量,除非其在该过程外更高级别的位置有显示地定义,否则也是局部的。
  • 小心过程可以使用没有在过程内显式定义的变量,但只要有任何在模块级别定义的名称与之同名,就会产生名称冲突。如果过程中使用的未定义的变量与别的过程,常数,或变量的名称相同,则认为过程使用的是模块级的名称。显式定义变量就可以避免这类冲突。可以使用 Option Explicit 语句来强制显式定义变量。
  • 注意:不能使用 GoSub、GoTo 或 Return 来进入或退出 Sub 过程。

参数表arglist

语法:
[Optional] [ByVal | ByRef] [ParamArray] varname[( )] [As type] [= defaultvalue]

Optional表示这个参数是可选的,也就是说在调用过程时可以不传递值也可以传递值给这个参数.。如果有传递defaultvalue给这个参数时(如optional iInput2 As Integer=13), 当调用过程没有传递值给这个参数时,在过程中会默认使用这个defaultvalue。
Optional必须对最后面的那些参数使用,也就是说某个参数使用了Optional,该参数后面的参数也必须使用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=" & iInput3
End Sub
Sub subTry()
' 可以给3个参数都赋值
    Call mmm(23, 34, 2)           	'iInput=23, iInput2=34, iInput3=2
	'也可以给第3个赋值,而不给第2个参数赋值,这样iInput2会等于默认值13
    Call mmm(23, , 2)         		'iInput=23, iInput2=13, iInput3=2
	'可以给第2个参数都赋值而不给第3个参数赋值
    Call mmm(23, 34)             	'iInput=23, iInput2=34, iInput3=0
	' 第2个,第3个参数都不赋值
    Call mmm(23)                	'iInput=23, iInput2=12, iInput3=0
End Sub

ParamArray的用法
ParamArray只能用于 arglist 的最后一个参数,指明最后这个参数是一个包含Variant类型元素的 Optional 数组,但你传递值给过程时还是使用逗号分开多个参数,过程里面会把找几个参数合并成一个数组。使用 ParamArray 的好处是你可以提供不定数目不定类型的参数给过程。ParamArray不能与 ByVal,ByRef,或 Optional 一起使用。

Sub m1(iInput1 As Integer, ParamArray argArr())
Dim strList As String
Dim i As Integer
 
strList = "iInput1=" & iInput1 & vbCrLf
For i = 0 To UBound(argArr)
On Error Resume Next
strList = strList & "argArr(" & i & ")=" & argArr(i) & vbCrLf
' 如果参数为空,将生成错误
        If Err.Description <> "" Then
strList = strList & "argArr(" & i & ")=缺失参数" & vbCrLf
Err.Clear		' 清除错误
        End If
Next i
MsgBox strList
End Sub
Sub 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)=缺失参数, 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)=data
End Sub

ByVal和ByRef
VBA中默认使用ByRef。ByVal的意思是按值传递参数,因为是按值传递,这个参数在过程里面的值有变化的话它影响的范围只是在这个过程里面。出了过程就没有用了。而ByRef是按地址或者说按引用传递,传递给过程的实际是这个数值的地址,而不是值本身,在过程中对改变这个参数也就是改变这个地址的值,这样在过程外面也可以看到这个值被改变了。运行下面的例子可以看到其中的区别。

Sub mmm(ByVal iI1 As Integer, iI2 As Integer)
iI1 = iI1 + 10
iI2 = iI2 + 10
MsgBox "Inside: iI1=" & iI1 & " iI2=" & iI2
End Sub
Sub mySub()
Dim iI1 As Integer
Dim iI2 As Integer
iI1 = 10
iI2 = 12
MsgBox "Before: iI1=" & iI1 & " iI2=" & iI2
Call mmm(iI1, iI2)
MsgBox "After: iI1=" & iI1 & " iI2=" & iI2
' 按顺序分别显示
    ' 显示 Before: iI1=10 iI2=12
    ' 显示 Before: iI1=20 iI2=22
    ' 显示 Before: iI1=10 iI2=22
    ' 过程mmm中修改了iI1和iI2,但是iI1是按值传递,在mmm过程之外的iI1并受影响
    ' 而iI2是按引用传递,mmm过程之外的iI2也被改变了
End Sub

另外,如果参数是数组的话,只能使用按引用传递,因为传递的实际上是数组第一个元素的地址。例如下面代码的用法。

Sub GetArray(arrTemp() As Integer)
Dim i As Integer
For i = 0 To UBound(arrTemp)
Debug.Print "Item " & i & ": " & arrTemp(i)
Next i
End Sub
Sub PassArray()
Dim arrInt(3) As Integer
arrInt(0) = 1
arrInt(1) = 2
arrInt(2) = 3
arrInt(3) = 4
Call GetArray(arrInt)
' 在立即窗口打印出
	' Item 0: 1
	' Item 1: 2
	' Item 2: 3
	' Item 3: 4
End Sub

但对于对象来说,使用ByVal实际上传递的仍然是对对象的引用,这样在过程中对象的修改将会影响过程外部对象的值或属性。如果使用ByVal,而在过程中创建一个新的对象实例,将该对象赋值给传递的对象,则不影响调用对象的外部的属性或值。而如果使用ByRef,在过程中创建一个新的对象实例,将该对象赋给传递的对象,却会影响过程外该对象的属性或值。

Sub TestByValByRef()
Dim objDic As Object
 
Set objDic = CreateObject("Scripting.Dictionary")
objDic(1) = 100
Debug.Print "byValueTest1"
Debug.Print "原始值: objDic(1)=" & objDic(1)
Call byValTest1(objDic)
Debug.Print "外部值: objDic(1)=" & objDic(1) & vbCrLf
 
objDic(1) = 100
Debug.Print "byValueTest2"
Debug.Print "原始值: objDic(1)=" & objDic(1)
Call byValTest2(objDic)
Debug.Print "外部值: objDic(1)=" & objDic(1) & vbCrLf
 
objDic(1) = 100
Debug.Print "byRefTest"
Debug.Print "原始值: objDic(1)=" & objDic(1)
Call byRefTest(objDic)
Debug.Print "外部值: objDic(1)=" & objDic(1)
End Sub
Private Sub byValTest1(ByVal c As Object)
Dim a As Object
Set a = CreateObject("Scripting.Dictionary")
a(1) = 200
Set c = a
End Sub
Private Sub byValTest2(ByVal c As Object)
c(1) = 200
End Sub
Private Sub byRefTest(ByRef c As Object)
Dim a As Object
Set a = CreateObject("Scripting.Dictionary")
a(1) = 200
Set c = a
End Sub

运行过程TestByValByRef,将在立即窗口打印出下面的结果。

byValueTest1
原始值: objDic(1)=100
外部值: objDic(1)=100

byValueTest2
原始值: objDic(1)=100
外部值: objDic(1)=200

byRefTest
原始值: objDic(1)=100
外部值: objDic(1)=200

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多