VBA入门3——过程和函数 (Sub | Function)

VBA 过程和函数 (Sub | Function)

VBA 过程(Sub) 入门教程和实例(组织代码的容器)

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。

调用子过程(Sub)

在程序开发中,把代码拆分成多个子过程和函数,可以使项目更容易管理、测试和运行,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 函数(Function)入门教程和实例(重复使用相同代码)

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 类型。

调用函数(Function)

函数与子过程的区别是,函数可以返回值。如果一个函数不返回值,它与子过程并无区别,其中调用方式与子过程相同。
调用有返回值的函数时,一般有两种情形:

  • 一是,使用一个变量存储函数返回的值
  • 二是,函数返回的值参与其他计算

两种情形调用函数方式相同,无参数函数直接书写,有参数函数将参数放在括号内。

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 有以下效果:

  • 程序中对象的各类事件不会被触发;
  • 任何在运行的 VBA 程序都会停止;
  • 对象引用都会失效;
  • 任何打开的窗体都被关闭。

总结

函数与过程类似,大部分用法相同,主要区别是函数可以返回一个值,而过程不可以。两者均可以接受0个或多个参数,参数可以在过程或函数里使用。调用函数时,参数需要放置在括号内部,接函数名后。

VBA 函数与过程的 6 个不同点(正确使用过程和函数)

第1点:声明语句不同

函数的声明语句是 Function 和 End Function,而过程的声明语句是 Sub 和 End Sub。

第2点:函数可以返回值

函数相对子过程最大的不同点是,函数可以返回指定的值。调用函数时,使用一个变量存储函数返回的值,可以在后续的代码中使用。

这里需要指出的是,函数可以不返回值,这种情况其作用与子过程相同。因此建议,不需要返回值时,直接使用子过程代替函数。

第3点:函数需指定返回值类型

函数声明时需要指定其返回值的类型。其语法与变量声明类似,在函数名后指定数据类型。

Function MyFunc() As String '指定返回值类型,此函数将返回文本类型的值
    
End Function

第4点:函数主体代码中,返回值赋值到函数自己

函数返回一个值,是通过在函数主体代码中,将返回的值赋值到函数自己的方法来实现。

Function MyFunc() As String
    '此函数将返回 Hello World 的文本
    MyFunc = "Hello World"
End Function

第5点:调用函数时,使用类型与函数返回值类型相同的变量获得返回值

当主程序中调用函数获取其值时,需要使用类型与函数返回值类型相同的变量,否则程序会出错。

Sub MyCode()
    
    Dim text As String '声明与函数返回值相同类型的变量
    text = MyFunc '调用函数,获得其返回的值
    Range("A1") = text '使用函数返回的值
    
End Sub

'此函数被上面的过程调用
Function MyFunc() As String
    '此函数将返回 Hello World 的文本
    MyFunc = "Hello World"
End Function

第6点:函数可在单元格内公式中使用

与 Excel 内置的函数一样,用户自定义编写的函数可在公式中直接使用,其用法与内置函数一样。
在这里插入图片描述
自定义函数也一样可以嵌套使用:在这里插入图片描述

VBA 过程和函数:传递参数教程和实例(正确定义和调用带参数的过程和函数)

带参数的子过程定义方法

子过程可以接受一个或多个参数,参数可以是常量、变量、表达式,并且每个参数指定其名称和数据类型。
看实际的例子,以下代码定义了带两个参数的一个过程,过程名是 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 会提示可选参数,参数放置在中括号中。
在这里插入图片描述

设置可选参数的默认值
可选参数定以后,如果在子过程中使用,需要判断参数有无提供。否则未提供而直接使用时,程序会出错。
在这里插入图片描述
针对这种情况,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 函数的第四个参数,是否精确查找。当设置成可选时,还可以指定可选参数的默认值。

VBA 中 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,因此以下两种写法是等效的。

'指定 ByVal 传递类型
Sub TestSub1(ByRef msg As String)

End Sub

'省略传递类型
Sub TestSub1(msg As String)

End Sub

总结

ByVal 和 ByRef 表示参数传递的类型。针对基础数据类型的变量,ByVal 会创建变量的一个副本,传递给过程或函数,从此之后与父过程的变量没有关系。而 ByRef 方式传递变量的引用,该引用始终会与父过程的变量相连。
因此建议,尽量使用 ByVal 传递类型,防止在子过程或函数中,不小心更改父过程里的变量,导致一些不容易发现的问题。

VBA 变量作用域

过程作用域

在过程或函数内部声明的变量,只有在当前过程或函数内被使用。例如:

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 变量,分别是模块变量和过程变量。根据就近原则,在过程内部使用时,将使用过程变量。

VBA 过程或函数作用域

模块作用域

在模块中,使用 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

  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值