VBA 中 ByVal 和 ByRef 的基础用法和区别
VBA 中定义过程或函数时,如果需要传递变量,需指定参数的传递类型,包括以下 2 类:
- ByVal:传递参数的值
- ByRef:传递参数的引用
本篇将介绍 2 种方法的用法以及区别。过程和函数传递参数方法基本相同,本篇以过程(Sub)举例说明他们的用法和区别。
ByVal 和 ByRef 基础
在定义过程或函数时,如果需要传递变量,则每个参数需要指定传递类型。传递类型有 2 种,分别是 ByVal
和 ByRef
。
'ByVal 传递类型
Sub TestSub1(ByVal msg As String)
End Sub
'ByRef 传递类型
Sub TestSub2(ByRef msg As String)
End Sub
针对基础数据类型,例如数字、文本等,两种传递类型的说明和区别如下:
- ByVal:传递变量时,复制一份该变量,传入过程或函数。在过程和函数内部对该变量进行修改,只对该副本有效,对上一级过程(父过程)的变量没有影响。
- ByRef:传递变量时,将该变量的引用地址传入过程或函数。传入引用地址意味着,在过程或函数内部对其修改时,也会影响上一级过程(父过程)中的变量的值。
ByVal 实例
通过以下代码测试 ByVal 类型:
Sub Test()
Dim msg As String
msg = "main"
TestSub1 msg
Msgbox msg
End Sub
'ByVal 传递类型
Sub TestSub1(ByVal msg As String)
msg = "val"
End Sub
首先定义一个 msg
变量,赋值 main
,然后调用 TestSub1
过程,传入 msg
变量,在过程内部对 msg
重新赋值 val
。最后返回上一个过程,显示 msg
变量。结果如下,msg
变量的值没有改变。
ByRef 实例
通过以下代码测试 ByRef 类型:
Sub Test()
Dim msg As String
msg = "main"
TestSub2 msg
MsgBox msg
End Sub
'ByRef 传递类型
Sub TestSub2(ByRef msg As String)
msg = "ref"
End Sub
首先定义一个 msg
变量,赋值 main
,然后调用 TestSub2
过程,传入 msg
变量,在过程内部对 msg
重新赋值 ref
。最后返回上一个过程,显示 msg
变量。结果如下,msg
变量的值已改变。
省略传递类型
默认情况下,当省略传递类型时,默认值是 ByRef
,因此以下两种写法是等效的。
'指定 ByRef 传递类型
Sub TestSub1(ByRef msg As String)
End Sub
'省略传递类型
Sub TestSub1(msg As String)
End Sub
使用 ByVal 和 ByRef 传递对象
在上述介绍中说道,以上机制适用于传递基础类型变量,例如数字、文本、逻辑值等。
使用 ByVal 和 ByRef 传递对象时,情况有些不同。具体用法和不同点将在介绍对象时详细说明。
使用 ByVal 和 ByRef 传递数组
过程或函数传递数组时,只能以引用形式传递,即以 ByRef 形式。如果尝试用 ByVal 传递数组,VBA 会提示错误。详细的用法将在介绍数组时详细说明。
总结
ByVal 和 ByRef 表示参数传递的类型。针对基础数据类型的变量,ByVal 会创建变量的一个副本,传递给过程或函数,从此之后与父过程的变量没有关系。而 ByRef 方式传递变量的引用,该引用始终会与父过程的变量相连。
因此建议,尽量使用 ByVal 传递类型,防止在子过程或函数中,不小心更改父过程里的变量,导致一些不容易发现的问题。
对象和数组变量的传递,有别于基础类型变量,在相关的教程中详细说明。
精彩
Sub Test()
Dim name As String
Dim age As Double
name = “sam”
age = 15
exam name
exam age
msgbox name & age
End Sub
Sub exam(ByRef name As String, ByRef age As Double)
name = “peter”
age = 10
End Sub
這個 Compile error: Argument not optional,不太明白,想請教一下,感謝
exam 过程需要输入两个参数,上述两次调用时只写了一个参数。
正确应该这样写:exam name, age
‘指定 ByVal 传递类型
Sub TestSub1(ByRef msg As String)
这里是不是写错了,要写‘指定ByRef传递类型
感谢反馈,已修正
Sub TestBy()
Dim msg As String
msg = “Test”
SubByVal (msg)
Debug.Print “After call SubByVal() msg = ” & msg
SubByRef (msg)
Debug.Print “After call SubByRef() msg = ” & msg
End Sub
Sub SubByVal(ByVal msg As String)
msg = “ByVal”
Debug.Print “In SubByVal() msg = ” & msg
End Sub
Sub SubByRef(ByRef msg As String)
msg = “ByRef”
Debug.Print “In SubByRef() msg = ” & msg
End Sub
执行结果
In SubByVal() msg = ByVal
After call SubByVal() msg = Test
In SubByRef() msg = ByRef
After call SubByRef() msg = Test
好像值没有变呢
问题在于,不使用Call关键字直接调用过程时,使用括号括了传递的参数。
这个时候括号的作用是对传递的参数求了一次值,然后传递到过程,因此在调用SubByRef时,实际上内部msg与外部msg已经断开了联系。
那么如何判断传入的参数被求值了,看过程名和括号之间是否有空格:如果有空格,就表示求值了。
Function Examv_al(Optional ByVal x As Integer = 2) As Integer
x = x + 1
Debug.Print x
Examv_al = x
End Function
Function Examr_ef(Optional ByRef y As Integer = 2) As Integer
y = y + 1
Debug.Print y
Examr_ef = y
End Function
我这里的两个过程值也是完全一样的。
不提供参数,立即窗口都是3,单元格里也都是3.
提供参数9,立即窗口都是10,单元格里也都是10.
例子中是两个函数,需要看一下如何调用的,以及结果是如何使用的。
和 C++ 很像很像 , 有C++ 基础, 半天就从头看到了这里, 讲的蛮清楚的
作者,还有在更新吗?
要更新的
作者,你好,还在吗?ByVal 的那个实例不是很明白,为什么出来的结果是 main 的? 下面的那个 sub 又什么意义呢?
这个需要结合ByRef例子来看。两个例子中,主过程Test里msg变量初始值是main,两种方式传参给其他过程中,msg变量的值有差异,这就是ByVal和ByRef的区别。
谢谢,作者,但还是不是很明白。什么样的情况下会用到这两种用法?
舉個例子 如果 你寫了一個變數互換的function,但是傳遞參數是Byval
這樣一來
Sub Test()
Dim a,b as String
a = “A”
b = “B”
swap a,b
End Sub
Sub swap(ByVal a As String, ByVal b As String)
tmp = a
a = b
b = tmp
End Sub
a跟b仍然是”A”跟”B”
但是使用ByRef的話
Sub Test()
Dim a,b as String
a = “A”
b = “B”
swap a,b
End Sub
Sub swap(ByRef a As String, ByRef b As String)
tmp = a
a = b
b = tmp
End Sub
結果就達成了a是”B” b是”A”
一般情况下建议使用 ByVal 类型。
ByRef就相当于C里面的传指针
23年了
作者你还在吗
我们还在等你
在呢在呢,很快开始更新
请问,为什么在 TestSub2 (msg)这里加上括号,和不加括号,返回的结果不一样呢?
Sub Test()
Dim msg As String
msg = “main”
TestSub2 (msg) ‘add (), returns different result, suppose to be ref, but main
MsgBox msg
End Sub
‘ByRef
Sub TestSub2(ByRef msg As String)
msg = “ref”
End Sub
正常情况,直接调用有参数的过程,传入参数不用括号;
本例中的括号有【计算】的作用,即先计算得到msg变量的结果”main”,再把结果传入到过程。也就是说,传入的不是msg变量,而是”main”文本。
所以,子过程修改的msg,非父过程msg变量。
我理解的是使用 ByRef的话就相当于函数有返回值
这么理解也没错,但是,不是很恰当,容易引起歧义
默认值是ByRef吧
不对吧,我看官方文档写的是默认ByVal
引用如下
https://docs.microsoft.com/en-us/dotnet/visual-basic/language-reference/modifiers/byval
If no modifier is specified, ByVal is the default.
Note
Because it is the default, you do not have to explicitly specify the
ByVal
keyword in method signatures. It tends to produce noisy code and often leads to the non-defaultByRef
keyword being overlooked.这个文档是针对VB语言的,VBA中没有明确说明,但是实际测试,默认值确实是ByRef
感谢反馈,已修正
这是最后一篇了,我都看完了,受益匪浅,感谢!
最近碰到一个问题请教下,我在Excel里面操作word中表格发现设置的标题行重复属性已经设置好了,但Word文件打开没有看到标题行重复。
Windows 10,Office 2010 32位。在excel中打开一个word文件然后save2存为一个新文件。打开这个新word文件,对表格导入数据,然后设置标题行重复和允许跨页中断,然后保存、关闭。
‘设置行标题重复
WordTable.Rows.HeadingFormat = True
WordTable.Rows.AllowBreakAcrossPages = True
请问,是否还有其他问题?
这节有点看不明白:什么情况下会使用到传递参数呢,能列举实际应用中的示例吗~感谢博主
这方面会写更多教程的,欢迎关注