编写自定义函数是为了处理数据,如何向自定义的函数(UDF)传递待处理的数据就成为开发者要考虑的问题。
就一般意义上的函数来说,参数列表通常是参数个数固定,类型明确的,比如下面的声明:
Public Function MyFunc1(name As String, age As Integer) As String
但是 Excel 里的函数有它的特殊性。我们在工作表里编写公式的时候,往往并不提供具体的数字或字符串,比如("张三",23),作为参数。在 Excel 里,我们习惯用单元格或单元格范围的引用来传递参数,像上面的例子,我们在调用函数时的表达方式可能是 =MyFunc1(A1,B1)。就上面的声明而言,如果我们只允许传递单一的单元格引用给参数是正确的,Excel 知道单元格引用的数据类型。
但有的时候,对于函数的某个参数,我们希望既可以传递单一的单元格,也可以传递一块单元格范围,增加函数的灵活性,就像 =Sum(A1) 和 =Sum(A1:A5) 都是可以的。这时候我们就不合适把参数声明为基本数据类型了,声明为 Excel 特有的数据类型 Range 就可以解决上面提到的问题,因为 Range对象既可以是单一的单元格,也可以是一块单元格范围。
更进一步,如果我们希望参数列表是变长的呢?提供一个参数可以,提供两个三个也可以,参数的个数是任意的。
一种解决方法是用 Optional 关键词。带 Optional 关键词的参数不是必需的,我们可以给带 Optional关键词的参数一个默认值,这样省略了该参数也没有什么关系了。
Public Function MyFunc2(name As String, Optional age As Integer =0) As String
但是用 Optional 关键词并不是真正的变长参数列表。我认为 Optional关键词只是省去了用户的一些输入,在函数体内,参数的个数还是固定的。并且,如果我们希望提供的参数个数超出了定义的参数个数怎么办?Optional是只能少不能多的,不是真正意义随心所欲的变长。
把参数声明为 Range 类型也是一种解决方法。Range 类型的参数可以是一个单元格,也可以是任意行列数的一块单元格。但是 Range 类型的参数也有个问题,我们传递单元格引用的时候是可行的,传递常数就会出错(Range 类型可以向下转换成基本类型,基本类型不能向上转换成 Range 类型)。我们希望在传递参数的数据类型上也保持一定的灵活性。
更贴近我们需求的解决方案是用 ParamArray关键词。ParamArray关键词允许我们传递任意个数的参数。并且,用 ParamArray 关键词声明的参数是 Variant类型的数组,每个元素可以是各种数据类型。注意,ParamArray 关键词和 Optional 关键词是互斥的。
下面的例子是模拟 SUM 函数功能的自定义加总函数:
Function MySum(ParamArray numbers()) As Double
Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim argD As Integer
Dim rtn As Double
rtn = 0
argD = UBound(numbers)
For i = 0 To argD
If IsArray(numbers(i)) Then
If IsObject(numbers(i)) Then
For j = 1 To numbers(i).Rows.Count
For k = 1 To numbers(i).Columns.Count
If IsNumeric(numbers(i)(j, k)) Then
rtn = rtn + numbers(i)(j, k)
End If
Next k
Next j
Else
For j = LBound(numbers(i)) To UBound(numbers(i))
rtn = rtn + numbers(i)(j)
Next j
End If
Else
If IsNumeric(numbers(i)) Then
rtn = rtn + numbers(i)
End If
End If
Next i
MySum = rtn
End Function
做几点说明,用 ParamArray 关键词声明的参数数组只返回最高一级的维度,看上去像是一维的。这是因为用 ParamArray关键词声明的参数数组里面的元素可以是很不同的,不一定要是一个几乘几的整齐的矩阵。比如,我们上面自定义的 MySum 函数可以这样调用 =MySum(A2:D4,2,{1,2,3,4})。
我们可以在代码里加入断点,然后从“Locals Window”查看 Numbers 这个数组的结构。如果我们尝试用 Numbers(i,j) 这样的表达式,反而会收到“Wrong number of dimensions”错误。
了解了用 ParamArray关键词声明的参数数组的性质,就可以理解怎样遍历每个元素,并对每个元素进行不同的处理。