用VBA实现Excel函数01:VLOOKUP

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

很多学习VBA的应该都是在使用了一段时间的Excel之后,想弥补一些Excel本身的不足、或者是实现一些自动化操作。

函数是Excel里非常重要的一个功能,所以,我们结合Excel的函数,用VBA去实现一些常用的函数功能。

Excel的函数其实在底层肯定也是一段写好了的程序,只是我们看不到 它的实现过程,我们用VBA实现这些函数的功能,多半会比内置函数慢很多,而且考虑的情况也不够全面,但我们主要是学习一些思路。

1、实现简单的VLOOKUP

提到Excel的函数,VLOOKUP函数应该是最为常用的一个查找函数了,一般我们都是使用它的精确查找功能,也就是第4个参数设置为0或者false,所以我们也只实现一个精确查找的函数。

VLOOKUP函数有4个参数,我们也一样参照这个模式:

代码语言:javascript复制
Function MyVlookup(lookup_value As Variant, table_array() As Variant, col_index_num As Long, Optional range_lookup As Long = 0) As Variant

End Function

参数:

  1. lookup_value:根据这个值去查找,这个值的类型可以是数值,也可以是String,所以我们把它定义为Variant
  2. table_array:一个Variant类型的数组,我们在数组Array里讲到过Range与数组之间的赋值,在Excel里这个参数就是1个Range的范围,而在VBA里虽然也可以用Range,但是为了提升一点速度,我们这里使用数组作为参数。
  3. col_index_num:要返回table_array里的第几列。
  4. range_lookup:请注意前面的修饰符Optional,我们在使用Excel的VLOOKUP函数时,细心的应该会注意到,函数的参数提示上,第4个参数是在“[]”里的,表示的意思是可以省略的参数。VBA里的参数修饰符Optional,和这个是同样的意思,可以省略的参数,如果省略了,就是用默认的值,在我们的定义里,默认等于0.
  5. 返回值:Variant,可以返回任意数据类型的值。

要实现这个功能,最简单的自然是用lookup_value与table_array里的第一列一个一个的比对,找到了相同的就返回col_index_num列的值,那显然这里需要用到For循环,循环需要1个范围,也就是table_array数组的范围,数组范围的确定有内置函数:

  • UBound(table_array, 1):返回数组第1维的最大下标
  • LBound(table_array, 1):返回数组第1维的最小下标

举例看一下:

代码语言:javascript复制
Dim arr(10) As Variant
UBound(arr, 1) = 10
LBound(arr, 1) = 0
UBound(arr, 2) '出错,因为arr是1维数组
LBound(arr, 2)  '出错,因为arr是1维数组

arr = Range("A1:A10")
UBound(arr, 1) = 10
LBound(arr, 1) = 1
UBound(arr, 2) = 1
LBound(arr, 2) = 1

其中1是可以省略的,也就是这个参数相当于是有Optional修饰符的。

那我们就先完成这个循环的代码:

代码语言:javascript复制
    Dim arrRows As Long
    arrRows = UBound(table_array, 1) - LBound(table_array, 1)   1
    
    Dim i As Long
    For i = 1 To arrRows
        If lookup_value = table_array(i, 1) Then
            MyVlookup = table_array(i, col_index_num)
            '一旦找到了需要的值,就可以返回并提前退出了
            Exit Function
        End If
    Next

其实完成这个循环代码后,如果一切输入正常的话,这个函数已经可以运行了,我们测试一下,测试数据:

测试代码:

代码语言:javascript复制
Sub TestMyVlookup()
    Dim ret As Variant
    Dim table_array() As Variant
    
    table_array = Range("B1:C16").Value
    
    ret = MyVlookup("张三", table_array, 2, 0)
    Debug.Print "MyVlookup返回值", ret
    
    ret = Application.WorksheetFunction.VLookup("张三", table_array, 2, 0)
    Debug.Print "Excel Vlookup返回值", ret
End Sub
输出:
MyVlookup返回值              72848 
Excel Vlookup返回值          72848

一切正常,其中我们还调用了Excel的内置函数进行了对比,要使用Excel的内置函数,只需要加上Application.WorksheetFunction再调用就可以。

2、代码完善

程序到这里是不是就完成了呢?你尝试修改一下:

代码语言:javascript复制
ret = MyVlookup("xx", table_array, 2, 0)

把"张三"修改为"xx",再输出看看,或者把2改为3,又再试试,我们在Excel使用时,习惯了的#N/A和#REF!并没看到,一个是输出了空白,一个是弹出了错误。说明我们的这个程序很多非正常的因素没有考虑全,也就是健壮性不够。

代码语言:javascript复制
健壮性是指软件对于规范要求以外的输入情况的处理能力。

我们继续完善一下,没有找到需要的值的时候,我们让它返回#N/A,所以,我们只需要在For循环之后加上一条语句:

代码语言:javascript复制
MyVlookup = "#N/A"

因为一旦找到了查找的值,For循环内部就会Exit Function,提前退出,所以这条语句只要放在最后就可以。

查找之前,我们要判断col_index_num会不会越数组table_array的界,所以程序开始要增加代码:

代码语言:javascript复制
    If col_index_num > UBound(table_array, 2) Then
        MyVlookup = "#REF!"
        Exit Function
    End If

这时候,把"张三"修改为"xx",或者把2改为3,我们都能让程序正常的运行并输出需要的值了。

这时候代码是否完善了呢?假如我们的table_array的赋值语句不是通过单元格来的,是我们自己定义的1个1维数组呢?如果你仔细看了上面说的取数组下标的情况,你能知道这时候又会出错了。

但是VBA没有提供判断数组维度的函数,一般这时候我们都是用错误捕捉的方式,这里就不演示了。有兴趣的可以自己网上查一查方法。

3、小结

我们通过自己写这么一个还非常不完善的MyVlookup函数,可以看出,平时看起来比较简单的Excel内置的VLOOKUP函数,其实是非常完善的,它几乎考虑到了我们所以可能输入的错误情况。

我们平时自己碰到、或是网上看到,一些APP、或者一些网站,虽然很成熟了,也经常会有一些bug,这些都是难免的。

写代码就是这样,问题一定要考虑的非常全面,但是,不管你如何的细心,也很难写出100%完善的代码,我们能做的只是尽力完善

0 人点赞