用VBA实现Excel函数02:SUM

2020-07-28 10:20:29 浏览数 (1)

说到Excel的SUM函数,我估计只要用过Excel的,应该没人不知道了,SUM函数多简单啊,点一下自动求和,自动就能定位好范围,回车就完成了。

是的,SUM函数用起来太简单、太方便了,让我们自己用VBA来实现看看,你会发现SUM函数也有它不简单的一面。

1、不简单的SUM第1参数:

SUM函数的参数提示上,number1是一定要有的,这个number1我们在Excel里使用的时候可能看不出它有什么特殊,那是因为我们没有去仔细想过。我们现在知道了VBA Function参数,我们再仔细想想这个number1有什么特殊之处:

  • 可以直接输数字
  • 可以直接输非数字的文本(会返回#VALUE!)
  • 可以输1个单元格或者1个单元格范围

它可以允许我们很方便的输入许多种情况,说明的是它实现过程都考虑到了这些情况,并能够解析。

2、不定长参数ParamArray:

你注意看SUM函数的参数提示,应该能注意到,它的参数最后有“...”这个东西,我们在前面没有说到过,不过,在Function函数里,我们让大家去看官方文档,如果仔细看了,里面有这么一个东西:

代码语言:javascript复制
ParamArray  可选。仅用作 arglist 中的最后一个参数来指示最后的参数为 Variant 元素的 Optional 数组。
            ParamArray 关键字允许您提供任意数量的参数。
            它无法用于 ByVal、ByRef 或 Optional

ParamArray修饰符,是一种不定长的参数,就是你在函数中参数用逗号分隔开,函数内部收到的其实就是1个数组,所以我们在用SUM函数的时候,你可以不停的用逗号分隔需要相加的单元格,函数都能正确的返回。

这种修饰符给代码的编写提供了极大的便利,像C语言里的printf函数一样。

SUM函数的不定长参数也和第1个参数一样,可以输入多种形式。

3、代码实现

通过上面的介绍,解析number1的各种输入形式是比较重要的,所以我们把解析单独做成1个函数:

代码语言:javascript复制
Function ParseValue(num1 As Variant) As Variant
    Dim dsum As Double
    
    Select Case VBA.VarType(num1)
        Case vbEmpty, vbNull, vbObject, vbError
            dsum = 0
        Case vbInteger, vbLong, vbSingle, vbDouble, vbDate, vbBoolean, vbByte
            dsum = VBA.CDbl(num1)
        Case vbString
            If VBA.IsNumeric(num1) Then
                dsum = VBA.CDbl(num1)
            Else
                ParseValue = "#VALUE!"
                Exit Function
            End If
        Case Else
            dsum = 0
    End Select
    
    ParseValue = dsum
End Function

VarType返回变量的数据类型,具体参看官方文档,这里没有使用if判断,而改用了Select Case,这种和if的逻辑差不多,但更方便判断这种多类型的。

程序通过判断num1的数据类型来决定如何处理:

  • 像vbError这种都当作0处理
  • vbString为了和Excel的SUM相同进行了一些特殊判断,使用IsNumeric判断它是否是纯数字的文本,是的情况转化为数字处理,否则返回错误#VALUE!
  • 其他我们只简单处理了数据类型

这里故意没有去处理数组类型,因为一旦在这里处理数组类型,就需要用到递归了,递归这个东西对写程序很重要,我觉得就相当于学函数需要会相对引用绝对引用以及数组公式一样。但是初学者一般很难理解,有兴趣的可以上网查一些资料学习。

数组的处理放到了主函数里:

代码语言:javascript复制
Function MySum(num1 As Variant, ParamArray argcs() As Variant) As Variant
    Dim dsum As Double
    Dim v As Variant
    Dim tmp As Variant
    
    If VBA.IsArray(num1) Then
        For Each v In num1
            tmp = ParseValue(v)
            If tmp = "#VALUE!" Then
                MySum = tmp
                Exit Function
            End If
            dsum = dsum   tmp
        Next
    Else
        tmp = ParseValue(num1)
        If tmp = "#VALUE!" Then
            MySum = tmp
            Exit Function
        End If
        dsum = dsum   tmp
    End If
    
    Dim vv As Variant
    For Each v In argcs
        If VBA.IsArray(v) Then
            For Each vv In v
                tmp = ParseValue(vv)
                If tmp = "#VALUE!" Then
                    MySum = tmp
                    Exit Function
                End If
                dsum = dsum   tmp
            Next
        Else
            tmp = ParseValue(v)
            If tmp = "#VALUE!" Then
                MySum = tmp
                Exit Function
            End If
            dsum = dsum   tmp
        End If
    Next
    
    MySum = dsum
End Function

IsArray函数能判断参数是否是1个数组,如果是数组,我们就用For Each 遍历其中的每一个元素,并调用ParseValue函数进行处理。

注:这里其实也不够严谨,理论上数组里的元素仍然可以是数组,不停的延续下去,这种最好就是用递归处理的。

测试代码:

代码语言:javascript复制
Sub TestMySum()
    Dim arr(1) As Integer
    arr(0) = 2
    arr(1) = 8
    
    Debug.Print MySum(Range("C2"), 1, 2, 3, arr)
End Sub
输出:
26    (Range("C2") = 10)

4、小结

实现了一个非常简单的SUM函数,多种形式输入的参数解析,不定长参数的传递和使用。

0 人点赞