乐于分享
好东西不私藏

Excel VBA 编程基础 -- 过程与函数(三)

Excel VBA 编程基础 -- 过程与函数(三)
今天来讨论两个例子。
例1. 读取 Excel 数据集
Excel 数据集如图1所示:
图1 Excel 数据集
我们定义一个 UDT 来表示此数据集的一行,即一个记录。该 UDT 名为 MathScore,定义如下:
Type MathScore  Id As Long  Name As String  Email As String  Age AsInteger  Score AsDoubleEnd Type
有了这个 UDT,我们可以写一个函数来读取数据集,每次读一行(一个记录),并返回用 UDT 表示的值,代码如下:
Function ReadRecord(ByVal rowAs Long) As MathScore  Dim ms As MathScore  ms.Id = CLng(Cells(row1).Value)  ms.Name = Cells(row2).Value  ms.Email = Cells(row3).Value  ms.Age = CInt(Cells(row4).Value)  ms.Score = CDbl(Cells(row5).Value)  ReadRecord = msEndFunction
函数 ReadRecord 接受一个 Long 类型的参数 row,表示要读取的行,函数返回 UDT 表示的记录 MathScore。看起来非常完美,不是吗?
完整的代码如图2所示:
图2 读取 Excel 数据集
图中,我们先定义了类型 MathScore,然后定义了读取数据集记录的函数 BuildMathScore,最后在 ReadDataSet Sub 中调用 BuildMathScore 函数读取记录,并将记录放入 myA 数组中。
图中的读取记录的函数名为 BuildMathScore,与我们上面给出的 ReadRecord 名称不同,这说明函数的功能与其名字无关,只要函数名字能反映函数的作用:
  • ReadRecord:这个名字反映的是函数的物理动作
  • BuildMathScore:这个名字反映的是函数的逻辑功能
我们以前说过,像 ms 这样的变量称为局部变量(local variable),这个变量“局部”于函数 BuildMathScore,也就是说,变量 ms 只在 BuildMathScore 中有意义。那既然这样,又怎么能够返回给 ReadDataSet 过程呢?
答案是:拷贝(Copy)。
函数 BuildMathScore 的最后一行语句将 ms 赋值给函数名,就是将 ms 返回给调用者。实际的动作是:在执行这个赋值语句时,VBA 把返回的 ms 值拷贝到 myA 数组的一个元素中,然后销毁 ms 变量,控制从 BuildMathScore 返回到调用者。
要将 ms 这样一个 UDT 拷贝到 myA 数组中,需要多条机器指令才能完成,这样的拷贝成本是很高的(特别对于大型复杂的 UDT)。有没有无需拷贝的做法?有,这就是我们的第二个版本。
例2. 读取 Excel 数据集 v2
图3 读取 Excel 数据集 v2
第二版有两个方面的改动,第一,把 BuildMathScore 改成了 Sub,因为不需要返回值。第二,增加了一个参数 ma,这是一个传地址的参数(ByRef),因此在 BuildMathScore 中对该参数的修改可以反映到调用者的实参中。
从调用者的角度可以很清楚地看出,我们传递给 BuildMathScore 的第二个参数是数组 myA 的元素,因为是 ByRef,所以传递的是 myA 元素的地址,因此 BuildMathScore 中对参数 ma 的修改,也就是对 myA 元素的修改(我们以前说过,实参和形参指向同一个对象)。
这两个例子说明了对于 UDT 数据类型,我们应该如何在不同的 Sub/Function 之间传递。还有一点需要说明的是:第二版中的 ma,不能把 ByRef 改成 ByVal,VBA 不允许以 ByVal 方式传递 UDT 类型的参数,但可以从 Function 返回 UDT 类型的结果,就像我们在第一版中所做的那样。
相关阅读
Excel VBA 编程基础 -- 过程与函数(一)
Excel VBA 编程基础 -- 过程与函数(二)