VBA 过程以 Sub 语句开始,以 End Sub 语句结束,包含一个或多个语句,完成一个特定的目标。
无参数的 VBA 过程的基本语法如下:
Sub [过程名]()
语句1
语句2
...
语句n
End Sub
可以看到,过程以 Sub 语句开始,以 End Sub 语句结束,并且具备一个名称,名称后有括号 ()。
我们看一个简单的例子。
Sub SayHello()
Msgbox "Hello World"
End Sub
上述就是一个简单的过程,过程名是 SayHello。这个过程只包含一个语句,运行时,弹出对话框显示 Hello World。
过程还可以接受一个或多个参数,参数可以是常量、变量、表达式,并且每个参数指定其名称。在过程的语句中,接受的参数,以名称指定方式被使用。
接受参数的过程基本语法如下:
Sub [过程名]([变量名1] As [数据类型1],...[变量名n] As [数据类型n])
语句1
语句2
...
语句n
End Sub
与无参数过程相比,有参数过程在过程名后的括号 () 中,包含一个或多个参数。参数的写法与声明变量语句类似,不同点是在这里不用写 Dim。
[变量名1] As [数据类型1]
我们看一个例子。
'声明一个过程
Sub SayHello(name As String)
Msgbox "Hello" & name
End Sub
'在另一个过程,调用上述过程,调用时,提供一个实际的 name 参数
Sub MyCode()
SayHello "World 2"
End Sub
我们在运行 MyCode 过程时,提供了 name 变量,即 World 2 ,运行时弹出对话框显示 Hello World 2。
在程序开发中,把代码拆分成多个子过程和函数,可以使项目更容易管理、测试和运行,VBA 中也不例外。
实际开发中,项目通常具备一个主入口过程,或称为父过程。父过程通过调用多个子过程和函数,完成一系列复杂的操作。其中子过程和函数一般只负责一个操作或动作。
下面看一个简单的例子。
'主入口
Sub Main()
Dim name As String
Dim title As String
name = "Zhang san"
title = "CEO"
WriteInfo name & "," & title
End Sub
'子过程,在工作表A1单元格填写信息
Sub WriteInfo(info As String)
Range("A1") = info
End Sub
以上的例子中,Main 过程是一个主入口(父过程),程序从此处开始执行,先是给 name 和 title 变量赋值,最后调用 WriteInfo 子过程,将两个信息合并后写到工作表上的 A1 单元格。
调用子过程有两种方法,直接调用和使用 Call 关键词调用。两种方法对子过程的参数有不同的要求。
直接调用
直接调用,直接写过程名,即可调用过程。
Sub Main()
MySub
End Sub
Sub MySub()
'代码
End Sub
如果子过程需要输入参数,多个参数只需用逗号(,)分开即可。
Sub Main()
MySub 2019,"年"
End Sub
Sub MySub(val1 As Integer, val2 As String)
'代码
End Sub
使用关键词 Call 调用
使用 Call 关键词调用时,Call 后接过程名。
Sub Main()
Call MySub
End Sub
Sub MySub()
'代码
End Sub
如果子过程需要输入参数,则需要将参数放在括号内。
Sub Main()
Call MySub(2019,"年")
End Sub
Sub MySub(val1 As Integer, val2 As String)
'代码
End Sub
注:程序角度看,调用过程时,不需要使用 Call 关键字,因此不建议此种方法。
正常情况下,VBA 过程以 Sub 语句开始,以 End Sub 语句结束。但有时根据实际情况,可能需要提前结束并退出过程。VBA 提供 2 种提前退出过程的方法,Exit Sub 和 End 方法。
Exit Sub 语句
在一个过程中,当程序运行到 Exit Sub 语句时,立即结束当前过程,提前退出。
Sub Main()
Call MySub
Msgbox "父过程"
End Sub
Sub MySub()
Exit Sub
Msgbox "子过程"
End Sub
'运行 Main 过程,返回结果:
=> "父过程"
在以上例子中,Main 过程调用 MySub 子过程,遇到 Exit Sub 语句,立即退出子过程,回到父过程 Main ,继续运行余下的语句。
这里需要注意的是,Exit Sub 语句只作用于当前过程,不影响调用它的父过程。
End 语句
在一个过程,当程序运行到 End 语句时,立即结束当前运行的所有 VBA 过程。
Sub Main()
Call MySub
Msgbox "父过程"
End Sub
Sub MySub()
End
Msgbox "子过程"
End Sub
'运行 Main 过程,返回结果:
=> 无返回结果
在以上例子中,Main 过程调用 MySub 子过程,遇到 End 语句时,立即结束当前运行的所有过程,包括父过程 Main。
在实际开发中,应谨慎使用 End 结束语句。End 语句的效果类似于电脑的强制关机命令,立即结束所有程序,不会保存任何值,于 VBA 有以下效果:
VBA 函数与 VBA 过程很相似,除了使用的关键词外,主要区别是,函数可以返回值。
无参数 VBA 函数的基本语法如下:
Function [函数名]() As [返回值类型]
语句1
语句2
...
语句n
[函数名] = [返回值]
End Function
可以看到,函数使用 Function 和 End Function 语句作为函数的开始和结束。
函数包含的语句中,相比过程,可以看到多一个 [函数名] = [返回值] 语句,这是函数的返回值语句。函数名后制定该函数返回值的类型,语法与声明变量类似。
看一个实际的例子。
'声明函数,该函数随机返回 true 或 false。函数需指定返回值类型。
Function RandomLogic() As Boolean
RandomLogic = Rnd() > 0.5
End Function
该函数的名称是 RandomLogic,返回值类型时 Boolean 类型,运行调用后,随机返回一个 true 或 false 值。实现方法是,使用 VBA 内置函数 Rnd(随机产生0-1的数字),随机数与0.5对比大小,产生 true 或 false 值,并把值赋值给函数名。
函数与过程一样,也可以接收参数,其语法与过程相同。
Function [函数名]([变量名1] As [数据类型1],...[变量名n] As [数据类型n]) As [返回值类型]
语句1
语句2
...
语句3
[函数名] = [返回值]
End Function
同样,函数接收的参数,在函数主体中使用。
我们看一个实际的例子。
Function Add2Number(num1 As Double, num2 As Double) As Double
Add2Number = num1 + num2
End Function
上述函数接受2个 Double 类型的数字作为参数,两者相加,返回和,其类型也是 Double 类型。
函数与子过程的区别是,函数可以返回值。如果一个函数不返回值,它与子过程并无区别,其中调用方式与子过程相同。
调用有返回值的函数时,一般有两种情形:
两种情形调用函数方式相同,无参数函数直接书写,有参数函数将参数放在括号内。
Sub Main()
'使用变量存储函数返回的值
Dim result1 As Double
result1 = Add(12, 345)
'函数返回值继续参与计算
Dim result2 As Double
result2 = RandNum + Add(12, 345)
End Sub
'函数:返回一个随机值
Function RandNum()
RandNum = Rnd * 100
End Function
'函数:返回两数的和
Function Add(num1 As Double, num2 As Double) As Double
Add = num1 + num2
End Function
正常情况下,函数使用 Function 和 End Function 语句作为函数的开始和结束。但有时根据实际情况,可能需要提前结束并退出函数。VBA 提供 2 种提前退出过程的方法,Exit Function 和 End 方法。
Exit Function 语句
在一个函数中,当程序运行到 Exit Function 语句时,立即结束当前函数,提前退出。
这里需要注意的是,Exit Function 语句只作用于当前过程,不影响调用它的父过程或函数。
End 语句
在一个函数,当程序运行到 End 语句时,立即结束当前运行的所有 VBA 过程和函数。
在实际开发中,应谨慎使用 End 结束语句。End 语句的效果类似于电脑的强制关机命令,立即结束所有程序,不会保存任何值,于 VBA 有以下效果:
函数与过程类似,大部分用法相同,主要区别是函数可以返回一个值,而过程不可以。两者均可以接受0个或多个参数,参数可以在过程或函数里使用。调用函数时,参数需要放置在括号内部,接函数名后。
函数的声明语句是 Function 和 End Function,而过程的声明语句是 Sub 和 End Sub。
函数相对子过程最大的不同点是,函数可以返回指定的值。调用函数时,使用一个变量存储函数返回的值,可以在后续的代码中使用。
这里需要指出的是,函数可以不返回值,这种情况其作用与子过程相同。因此建议,不需要返回值时,直接使用子过程代替函数。
函数声明时需要指定其返回值的类型。其语法与变量声明类似,在函数名后指定数据类型。
Function MyFunc() As String '指定返回值类型,此函数将返回文本类型的值
End Function
函数返回一个值,是通过在函数主体代码中,将返回的值赋值到函数自己的方法来实现。
Function MyFunc() As String
'此函数将返回 Hello World 的文本
MyFunc = "Hello World"
End Function
当主程序中调用函数获取其值时,需要使用类型与函数返回值类型相同的变量,否则程序会出错。
Sub MyCode()
Dim text As String '声明与函数返回值相同类型的变量
text = MyFunc '调用函数,获得其返回的值
Range("A1") = text '使用函数返回的值
End Sub
'此函数被上面的过程调用
Function MyFunc() As String
'此函数将返回 Hello World 的文本
MyFunc = "Hello World"
End Function
与 Excel 内置的函数一样,用户自定义编写的函数可在公式中直接使用,其用法与内置函数一样。
自定义函数也一样可以嵌套使用:
子过程可以接受一个或多个参数,参数可以是常量、变量、表达式,并且每个参数指定其名称和数据类型。
看实际的例子,以下代码定义了带两个参数的一个过程,过程名是 CustomLog ,参数分别是 num 和 base。此过程的用途是计算任意底数的对数,num 是计算对数的值,base 是底数。
'声明一个带参数的子过程
Sub CustomLog(num As Double, base As Integer)
Debug.Print Log(num) / Log(base)
End Sub
子过程按照这种方法定义后,调用时,VBA 会提示需要提供什么参数以及参数类型。
调用带参数的过程,只需将参数按定义顺序书写即可,多个参数使用逗号分开。
以上述过程为例,在一个主过程调用 CustomLog 子过程。
'主入口
Sub Main()
CustomLog 100, 10
End Sub
除了按顺序书写参数外,也可以按任意顺序书写参数,但是这时需要给出参数名。带参数名的传递参数语法如下:
[参数名]:=[实际参数值]
参数名后写冒号等号(:=),再写需传递的参数值。看实际的例子,以下三种方式是等效的。
'主入口
Sub Main()
CustomLog 100, 10 '方式一
CustomLog num:=100, base:=10 '方式二
CustomLog base:=10, num:=100 '方式三
End Sub
实际开发中,有时子过程的参数可能不是必须的,我们希望根据参数有无情况,执行不同的操作。针对这种情况,VBA 提供了可选参数机制。
可选参数语法
可选参数在定义子过程时需要指定,方法是在参数名前添加 Optional 关键词。
Optional [参数名] As [数据类型]
还是以 CustomLog 子过程为例,我们把底数 base 设为可选参数。
'声明一个带可选参数的子过程
Sub CustomLog(num As Double, Optional base As Integer)
'子过程代码
End Sub
设置可选参数的默认值
可选参数定以后,如果在子过程中使用,需要判断参数有无提供。否则未提供而直接使用时,程序会出错。
针对这种情况,VBA 提供了默认值机制,即可选参数未提供时,使用预算设置好的默认值。
可选参数默认值,在定义过程时就设置,语法如下:
Optional [参数名] As [数据类型] = [默认值]
还是以 CustomLog 子过程为例,我们把底数 base 设为可选参数,并且默认值设为 10。
'声明一个带可选参数的子过程
Sub CustomLog(num As Double, Optional base As Integer = 10)
Debug.Print Log(num) / Log(base)
End Sub
调用时,如果提供了 base 底数,则以提供的底数计算;如果未提供 base 底数,则以默认值 10 计算。
'主入口
Sub Main()
CustomLog 100, 100 '返回 1
CustomLog 100 '返回 2
End Sub
可选参数的位置
当子过程有多个参数时,其中的可选参数需写在参数列表的末尾,否则 VBA 提示错误。
VBA 过程和函数均可以接受一个或多个参数。当调用它们时,需要注意传入的参数的书写顺序:不写参数名时,按照定义的顺序传递;写参数名时,对书写顺序没有要求。此外,过程和函数可以设置某一个参数是可选的,类似 VLOOKUP 函数的第四个参数,是否精确查找。当设置成可选时,还可以指定可选参数的默认值。
在定义过程或函数时,如果需要传递变量,则每个参数需要指定传递类型。传递类型有 2 种,分别是 ByVal 和 ByRef 。
'ByVal 传递类型
Sub TestSub1(ByVal msg As String)
End Sub
'ByRef 传递类型
Sub TestSub2(ByRef msg As String)
End Sub
针对基础数据类型,例如数字、文本等,两种传递类型的说明和区别如下:
通过以下代码测试 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 类型:
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,因此以下两种写法是等效的。
'指定 ByVal 传递类型
Sub TestSub1(ByRef msg As String)
End Sub
'省略传递类型
Sub TestSub1(msg As String)
End Sub
ByVal 和 ByRef 表示参数传递的类型。针对基础数据类型的变量,ByVal 会创建变量的一个副本,传递给过程或函数,从此之后与父过程的变量没有关系。而 ByRef 方式传递变量的引用,该引用始终会与父过程的变量相连。
因此建议,尽量使用 ByVal 传递类型,防止在子过程或函数中,不小心更改父过程里的变量,导致一些不容易发现的问题。
在过程或函数内部声明的变量,只有在当前过程或函数内被使用。例如:
Sub Test()
Dim name As String
Dim age As Integer
name = "张三"
age = 35
End Sub
以上代码中,变量 name 和 age 在 Test 过程声明,因此它们只能在该过程中内使用,包括赋值和读取。如果尝试在外部和其他过程中直接使用它们,VBA 会提示变量未定义错误。
一个模块中,在任何一个过程和函数外面,使用关键词 Private 或 Dim 声明的变量,称之为模块变量,其作用域是当前模块。例如,
Dim guest As String
Sub Test()
Dim message As String
guest = "张三"
message = "你好"
MsgBox message & "! " & guest
End Sub
以上代码中,变量 guest 是在过程 Test 外面,使用 Dim 关键词声明的,称之为模块变量。模块变量的作用域是当前模块,在模块里面任何过程和函数内均可以使用。
如前文所述,使用关键词 Private 或 Dim 声明的变量,都是模块变量,因此以下两种声明方式是等效的。
Dim guest As String
Private guest As String
Excel VBA 中,一个 Excel 工作簿是一个 VBA 工程。与之对应,工程作用域表示变量在当前工程中的模块、Excel 对象、用户窗体、类模块中均可以被使用。
工程级别变量,在所在模块顶部声明 Option Private Module 修饰语句前提下,在过程或函数外面,使用关键词 Public 声明的变量,其作用域是当前工程。例如,
Option Private Module
Public guest As String
Sub Test()
Dim message As String
guest = "张三"
message = "你好"
MsgBox message & "! " & guest
End Sub
以上例子中,变量 guest 是使用 Public 关键词声明,是工程级别变量。它在当前工程中其他的模块中也能被使用。
当相同名称的变量,多次以不同的作用域声明时,出现作用域冲突。这种情况,VBA 会自动以就近原则使用变量,即优先使用最近定义的变量。例如,
Dim name As String
Sub Test()
Dim name As String
name = "李四"
End Sub
以上例子中,两次声明 name 变量,分别是模块变量和过程变量。根据就近原则,在过程内部使用时,将使用过程变量。
在模块中,使用 Private 关键词声明的过程或函数,具备模块作用域,只能在当前模块中使用。
Private Sub Test()
End Sub
在模块中,顶部声明 Option Private Module 修饰语句,并且直接声明或使用 Public 关键词声明的过程或函数,具备工程作用域,在当前工程的所有模块中使用。
Option Private Module
Sub Test1()
End Sub
Public Sub Test2()
End Sub
以上例子中,Test1 过程和 Test2 过程均具备工程作用域。由于直接声明和使用关键词 Public 是等效的,因此可以省略 Public 关键词。
在模块中,直接声明或使用 Public 关键词声明的过程或函数,具备全局作用域。例如,
Sub Test1()
End Sub
Public Sub Test2()
End Sub
以上例子中,Test1 过程和 Test2 过程均具备全局作用域,可以在打开的任何一个工作簿中使用。
参考链接: 懒人Excel