ExcelTip.Net留存知识帖 ---【注:附件之前被网盘供应商清空后,现已修复-现已修复-现已修复为本地下载!】
现在位置:首页 > E文精选 > Excel VBA > VBA参数传递研究以及在数组排序算法中的应用

VBA参数传递研究以及在数组排序算法中的应用

作者:绿色风 分类: 时间:2022-08-17 浏览:189
楼主
amulee
VBA中,自定义函数和自定义过程可以带有参数,可以使的函数或者过程更为通用。
在参数中,一般可以传入基本的数据类型,数组,对象等类型的数据。然而VBA中却无法传入某个函数或者过程作为参数运行。对于某些特定功能,当使用函数或者过程作为参数传递时,将使得程序具有更好的通用性和实用性。本次讨论函数或函数作为参数传递的可能性与使用方法,并介绍一个数组排序的例子向大家展示其优点。


2楼
amulee
Application.Run
Application.Run方法可以使用一个字符串作为参数传入,该字符串表示一个函数或者过程的名称,则该方法可以运行该字符串所表示的函数或过程。
如以下Test1

  1. Sub Test1()
  2.     ParaExample1 "Func1"
  3.     ParaExample1 "Func2"
  4.     ParaExample1 "Func3"
  5. End Sub

  6. '示例过程,用参数传递函数或过程名称
  7. Sub ParaExample1(FuncName As String)
  8.     Application.Run FuncName
  9. End Sub

  10. '过程1
  11. Sub Func1()
  12.     MsgBox 1
  13. End Sub

  14. '过程2
  15. Sub Func2()
  16.     MsgBox 2
  17. End Sub

  18. '过程3
  19. Sub Func3()
  20.     MsgBox 3
  21. End Sub



ParaExample1过程中,调用了Application.Run方法,将使其运行由FuncName参数所传递来的相应的过程。当运行Test1程序的时候,会调用ParaExample1过程,并分别将三个过程“Func1”、“Func2”和“Func3”的名称作为参数传入,因而依次运行了三个过程。从一个角度来看,当调用ParaExample1过程时,将过程名作为参数传递给了该过程,其结果相当于过程作为参数传递给过程ParaExample1
3楼
amulee
我们再来写一个例子看一下。现在我们将ParaExample改写一下,如以下ParaExample2:

  1. '示例过程,用参数传递函数或过程名称
  2. Sub ParaExample2(FuncName As String)
  3.     Dim num1, num2
  4.     num1 = 4
  5.     num2 = 5
  6.     If Application.Run(FuncName, num1, num1) Then
  7.         MsgBox "OK"
  8.     Else
  9.         MsgBox "Not OK"
  10.     End If
  11. End Sub


该过程允许传入一个作为另一个函数名称的字符参数,在该过程中,调用了该函数,并根据该函数的运行结果,弹出OK或者Not OK的对话框。

然后再编写三个自定义函数,分别对传入的两个参数进行运算,并返回结果:

  1. '函数4
  2. Function Func4(num1, num2)
  3.     Func4 = num1 > num2
  4. End Function

  5. '函数5
  6. Function Func5(num1, num2)
  7.     Func5 = num1 = num2
  8. End Function

  9. '函数6
  10. Function Func6(num1, num2)
  11.     Func6 = num1 < num2
  12. End Function


以及主程序:
  1. Sub Test2()
  2.     ParaExample2 "Func4"
  3.     ParaExample2 "Func5"
  4.     ParaExample2 "Func6"
  5. End Sub


当调用Test2时,将调用ParaExample2过程,并传递不同的函数名称作为参数。ParaExample2将根据作为参数传入的函数的运行结果的不同而弹出不同提示信息的对话框。

从另一个角度来看,我们已经成功将“函数”作为“参数”传递给一个过程,而且该过程将随着传入的函数的运行结果不同而运行不同的结果。
4楼
amulee
结论

当某些数据处理的模式相同,而有部分过程有差别时,则可以考虑该方法来进行处理。首先编写一个数据处理的主程序,在该程序中进行一般的数据处理,当需要特别的数据处理时,则使用Application.Run方法调用函数,实现另一部分的个性化的数据处理。
5楼
amulee
应用实例
在VBA编程中,经常进行数组的排序。对于经常编写VBA程序的朋友而言,编写一个针对一维数组排序的过程非常容易实现,并且可以实现其升序和降序的定义。而对于二维数组的排序,由于其情况复杂,编写一个通用的排序过程难度就非常大。
首先我们来看一下,我们如果通过一个传递一个自定义函数给一个一维数组排序的过程。
一维数组排序
对于排序而言,其主要就是对于数组的元素进行比较,然后进行元素位置的交换。其中,对于元素比较的结果即影响着排序的结果(升序,降序或者自定义)。我们以一个插入排序的算法为例(如下)
  1. Sub SortInsertionOrigin()
  2.     Dim i&, j&, Temp, arr
  3.     For i = LBound(arr)+1 To UBound(arr)
  4.         Temp = arr(i)
  5.         j = i - 1
  6.         Do
  7.             If j < LBound(arr) Then Exit Do
  8.             If arr(j) <= Temp Then Exit Do
  9.             arr(j + 1) = arr(j)
  10.             j = j - 1
  11.         Loop
  12.         If j <> (i - 1) Then arr(j + 1) = Temp
  13.     Next i
  14. End Sub


其中,进行元素比较的语句为
  1. If arr(j) <= Temp Then Exit Do

若将该语句更改为
  1. If arr(j) >= Temp Then Exit Do

则程序将变为降序排序。

因而考虑将该语句编写为调用函数来实现。现在,将该过程改写为一个接受两个参数的过程,分别接受数组,以及替代比较语句的函数名:
  1. Sub **Sort(arr, Comparer As String)
  2.     Dim i&, j&, Temp
  3.     For i = LBound(arr)+1 To UBound(arr)
  4.         Temp = arr(i)
  5.         j = i - 1
  6.         Do
  7.             If j < LBound(arr) Then Exit Do
  8.             If Application.Run(Comparer, arr(j), Temp) Then Exit Do
  9.             arr(j + 1) = arr(j)
  10.             j = j - 1
  11.         Loop
  12.         If j <> (i - 1) Then arr(j + 1) = Temp
  13.     Next i
  14. End Sub


可以发现,该程序与之前不同的是,用Application.Run(Comparer, arr(j), Temp)替代了arr(j) <= Temp进行比较。

因而,当使用该程序进行数组排序时,需要额外写一个返回值是逻辑值的函数,用于定义程序中arr(j)和Temp的数据比较的结果,并且该函数必须允许传入两个参数代表原始的数据。

  1. Function Comparer1(data1, data2) As Boolean
  2.     Comparer1 = data1 <= data2
  3. End Function


以上函数实质替代了arr(j) <= Temp的比较语句。
我们构建一个主函数测试一下该程序:
  1. Sub Test1()
  2.     Dim i As Long
  3.     Dim arr(1 To 10)
  4.     For i = 1 To 10
  5.         arr(i) = Rnd
  6.     Next i
  7.     **Sort arr, "Comparer1"
  8.     Debug.Print Join(arr, ",")
  9. End Sub




6楼
amulee
当运行该程序后,可以在立即窗口中看到升序排列的10个小数。
再来加一个用于比较的函数如下:
  1. Function Comparer2(data1, data2) As Boolean
  2.     Comparer2 = data1 >= data2
  3. End Function

于是,在Test1程序中加入以下语句并运行后,可以得到一组降序排列的数据,是因为Comparer2替代了arr(j) >= Temp比较语句。
  1. **Sort arr, "Comparer2"
  2. Debug.Print Join(arr, ",")


我们可以发现,这样的程序写法其实很方便。一旦我们写完了**Sort程序,将来需要不同的排序时。我们不需要去更改该程序,只需要另外写一个用于比较的函数即可实现不同方式的排序。如此省去了许多精力去讨论各种不同的情形,因为在自定义函数中,开发者只需要关心两个数据哪个大哪个小即可。

关于自定义函数部分,其实该函数用于data1和data2两个数据的比较结果,其意义是:若希望将data1放在后面,则函数应该返回False,若希望将data1放在前面,则函数应该返回True。

上述的例子中,当我们需要升序排列时,我们的函数返回data1<=data2。当data1小于或等于data2时,则该不等式比较结果为True,函数返回True,将data2放在了后面;当data1大于data2时,则该不等式比较结果为False,函数返回False,则将data1放在了后面。无论哪种情况都将较大的数据放在了后面,因而实现了升序排序。

7楼
amulee
一维数组的自定义排序
我们再来利用该程序进行一个比较复杂的自定义排序:假设我们数组中有部分数据为空值(Empty),我们把所有空(Empty)的数据放在数组的最后,其余的数据按照升序排列。
于是编写一个比较的函数:
  1. Function Comparer3(data1, data2) As Boolean
  2.     If IsEmpty(data1) Then
  3.         Comparer3 = False
  4.     ElseIf IsEmpty(data2) Then
  5.         Comparer3 = True
  6.     Else
  7.         Comparer3 = data1 <= data2
  8.     End If
  9. End Function

该函数表示,当data1或者data2为空时,则将其放在后面。其余情况,按照升序排列。
我们利用以下程序进行测试:
  1. Sub Test2()
  2.     Dim i As Long
  3.     Dim arr(1 To 10)
  4.     For i = 1 To 10
  5.         If i Mod 2 Then
  6.             arr(i) = Empty
  7.         Else
  8.             arr(i) = Rnd
  9.         End If
  10.     Next i
  11.     **Sort arr, "Comparer3"
  12.     Debug.Print Join(arr, ",")
  13. End Sub

可以发现结果有5个数字在之前按照升序排列,后面5个为空文本,因而实现了我们所需要的自定义排序。

优缺点当我们尝试加大数组的尺寸时,可以发现,排序的效率很低。以笔者的电脑而言,当数组尺寸加到2000以上就显得无法忍受了。因为在比较算法中,调用了另一个程序,无疑会拖慢速度。当然,我们也有一些手段对于其进行优化。该部分将在下面逐步介绍。当然,以上编写程序最好的优点就是简化了程序的编写方法,并且使得已经编写的某些自定义程序变得通用,使得程序更为灵活。

8楼
amulee
二维数组的排序对于二维数组的排序,其算法其实和一维数组一样,唯一不同的是,其比较方式和元素交换的方法比一维数组要复杂得多。与一维数组一样,其用于比较的语句我们将采用一个函数进行替代。而用于数组中数据元素的交换,此处我们采用一个变通的方法。
将二维数组改造成一维数组
我们可以将二维数组改造成一维数组,其方式如下图所示:


 

左侧所示的数组为原始的二维数组,右侧为经过改造后的一维数组,每个元素中实际存放了另一个一维数组。
因而我们需要两个过程,可以实现二维数组进行从左到右和从右到左互相切换的功能。同时还需要一个改良的Index函数用于获取二维数组的子数组(工作表函数的Index函数只能获取不超过65536行数据的数组)。

  1. '2维数组转换为1维数组
  2. Private Sub GetNewArray(ByRef ArrayList)
  3.     Dim i   As Long
  4.     ReDim NewArr(minRow To maxRow)
  5.     For i = minRow To maxRow
  6.         NewArr(i) = MyIndex(ArrayList, i)
  7.     Next i
  8. End Sub
  9. '1维数组转换为2维数组
  10. Private Sub GetOldArray()
  11.     Dim arrTmp
  12.     Dim i   As Long
  13.     Dim j   As Long
  14.     Dim colN As Long
  15.     ReDim OldArr(minRow To maxRow, _
  16.                  minCol To maxCol)
  17.     For i = minRow To maxRow
  18.         arrTmp = ResultArr(i)
  19.         colN = 0
  20.         For j = minCol To maxCol
  21.             colN = colN + 1
  22.             OldArr(i, j) = arrTmp(colN)
  23.         Next j
  24.     Next i
  25. End Sub
  26. '自定义函数,将指定的行转换为数组输出
  27. Private Function MyIndex(ArrayList, index As Long)
  28.     Dim rowN As Long
  29.     Dim colN As Long
  30.     Dim col As Long
  31.     Dim arrResult()
  32.     rowN = index + minRow - 1
  33.     ReDim arrResult(1 To maxCol - minCol + 1)
  34.     For colN = minCol To maxCol
  35.         col = col + 1
  36.         arrResult(col) = ArrayList(rowN, colN)
  37.     Next colN
  38.     MyIndex = arrResult
  39. End Function


当数组作为数组的元素时,可以直接调用其进行赋值,而不必进行逐个元素的赋值。比如数组Arr与变量Temp只需要用Temp=Arr即可将数组Arr整体赋值给变量Temp。因而,我们就完全可以按照之前一维数组的方式编写排序函数了。此处我们将该数组功能写入一个类中,并定义了一个默认的比较函数,其作用是按照第一列的升序排列。其中的代码不再详细介绍。
9楼
amulee
笔者准备了一个工作表的数据用于进行排序的测试,并且编写了主程序与比较函数如下:

 
  1. Sub MySortExample()
  2.     Dim arr
  3.     '获取原始数据
  4.     arr = Sheet1.Range("A2:D38").Value
  5.    
  6.     '新建数组排序类
  7.     Dim CC As New SortTool
  8.    
  9.     '默认排序
  10.     CC.SortArray arr
  11.     Sheet1.Range("I2:L38").Value = arr
  12.     MsgBox "按照默认的第1列升序排列"
  13.    
  14.     '自定义排序1
  15.     CC.SortArray arr, "CompareMe1"
  16.     Sheet1.Range("I2:L38").Value = arr
  17.     MsgBox "按照自定义的第2列升序以及第4列降序排列"
  18.    
  19.     '自定义排序2
  20.     CC.SortArray arr, "CompareMe2"
  21.     Sheet1.Range("I2:L38").Value = arr
  22.     MsgBox "按照自定义的第2列升序以及第4列升序排列"
  23.    
  24.     Set CC = Nothing
  25. End Sub

  26. '自定义排序的比较函数。按照自定义的第2列升序以及第4列降序排列。
  27. Function CompareMe1(ByRef data1, ByRef data2) As Boolean
  28.     If data1(2) <> data2(2) Then
  29.         CompareMe1 = data1(2) < data2(2)
  30.     Else
  31.         CompareMe1 = data1(4) > data2(4)
  32.     End If
  33. End Function

  34. '自定义排序的比较函数。按照自定义的第2列升序以及第4列升序排列。
  35. Function CompareMe2(data1, data2) As Boolean
  36.     If data1(2) <> data2(2) Then
  37.         CompareMe2 = data1(2) < data2(2)
  38.     Else
  39.         CompareMe2 = data1(4) < data2(4)
  40.     End If
  41. End Function
10楼
amulee
效率的提升
为了提升效率,本例的实现使用了快速排序替代了插入排序。
另外,排序中的元素交换涉及了数组的整体赋值,该过程比较消耗系统资源,拖慢了执行的速度。为了提高效率,笔者此处使用了一个索引数组来替代转换后的一维数组进行排序。
所谓索引数组,是记录了数组下标的与原始数组尺寸相同的一个数组。如下图所示:


 

由于数据数组中的每个元素都是一个独立的数组,进行数据交换势必拖慢速度,因而所有的数据交换只是索引数组的交换。当数据比较时,只需根据索引数组中的索引,即可找到真实的数据,而进行比较。
最后,当数据排序完毕后,即索引数组完成排序后,需要将数据数组按照索引的位置进行重新排列,从而完成数组的排序。如图所示:


 

此处代码略。有兴趣的朋友可以查看附件的代码示例。
11楼
amulee
总结本次我们讨论了将函数或者过程作为参数传递给函数和过程的可能性,以及其实现的方法。其功能实现类似于回掉函数(CallBack)
我们此处使用了Application.Run方法,将函数的名称字符串作为参数传递运行,并且使用了数组排序的示例以说明其代码实现的方法。
我们可以轻易地发现其灵活、易于实现、通用的优点,但同时也具有以下几个缺点:
1、        由于多次调用函数,效率相对较低。
2、        若封装已有的固定算法,则作为参数传递的函数的写法必须给出相应说明,且函数应该按照其特定的签名进行编写,但是在编写时并无法由VBA给出该函数签名的提示。
如本例中的比较函数写为Function Comp1(data) as boolean将使程序无法正确运行。

其实示例中数组的排序方法类似于.Net中数组的排序算法,通过这次介绍的内容将其实现在了VBA中。

关于第2点,我们可以通过接口的方式来解决,通过编写一个接口类,来实现函数作为参数的传递,同时也可以在一定程度上提高效率。

接口将在我之后的帖子中进一步讨论。





本例附件下载

参数传递及数组排序.rar






免责声明

有感于原ExcelTip.Net留存知识的价值及部分知识具有的时间限定性因素, 经与ExcelTip.Net站长Apolloh商议并征得其同意, 现将原属ExcelTip.Net的知识帖采集资料于本站点进行展示, 供有需要的人士查询使用,也慰缅曾经的论坛时代。 所示各个帖子的原作者如对版权有异议, 可与本人沟通提出,或于本站点留言,我们会尽快处理。 在此,感谢ExcelTip.Net站长Apolloh的支持,感谢本站点所有人**绿色风(QQ:79664738)**的支持与奉献,特此鸣谢!
------本人网名**KevinChengCW(QQ:1210618015)**原ExcelTip.Net总版主之一

评论列表
sitemap