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