跳转至

09 数组

在 VBA 编程中,数组(Array)是非常重要的基础数据结构。一旦数据量稍大,或者涉及批量处理、性能优化,数组几乎是绕不开的。一句话总结数组的价值:能用数组解决的问题,就尽量不要逐个单元格操作。(耗时又繁琐)

一、什么是数组?

数组是一组 按索引顺序存储的数据集合,可以理解为:一维数组就是一排数据,而二维数组就是一个表格,我们可以通过以下方式生成数组:

(1)使用 Array 函数

arr = Array("A", "B", "C")
特点是:下标从 0 开始,类型为 Variant, 适合少量固定数据

(2)从单元格区域直接读入数组

arr = Range("A1:D100").Value
此时得到的是一个 二维数组: arr(1,1) → A1,arr(100,4) → D100,注意这个时候下标从1开始

(3)通过直接的括号定义数组

arr=[{1,2,3;4,5,6}]
例如上式我们就定义了一个两行三列的数组,注意下标也是从1开始的,而如果我们需要声明一个数组,可以采用以下方式:

  • 一维数组声明Dim arr(1 To 5) As Integer,表示一个长度为 5 的数组,下标从 1 到 5。

  • 不指定下标声明Dim arr(5) As Integer,表示一个长度为 6 的数组,默认下标是从0到5,这个特别注意

  • 声明二维数组dim arr(1 to 3,1 to 4),比如这样就声明了一个3行4列的数组,强烈建议把上下界写清楚


二、数组的上下界和动态数组

定义了数组之后,我们就可以通过LBound(arr)来获取数组的下界,通过UBound(arr)来获取上界,如要遍历则写作:

1
2
3
4
5
Dim i As Long

For i = LBound(arr) To UBound(arr) '从下界遍历到上界
    Debug.Print arr(i)
Next i

如果怕搞错数组的维度,记住 永远不要假设数组从 0 或 1 开始,而是用 LBound / UBound,同样很多时候数据的大小并不是固定的,因此我们不能在程序一开始就给定数组的大小,因为如果少了,则数据一多就放不下,多了的话,就会浪费很多的内存,因此 动态数组 的重要性就体现出来了,它支持根据数据多少动态变化数组大小,比如程序一开始我们定义:

Dim arr() As Long
此时数组还没有大小,不能直接使用,我们可以通过Redim分配空间,如下分配了10个数据的位置:

ReDim arr(1 To 10)
但是如果后续我们重新调整数组大小(会清空原数据),之前已经存在数组中的值就被清空

ReDim arr(1 To 20)

如果要保留源数据,我们需要使用 ReDim Preserve,如下这样既开拓了区域,也保留了原来的内容

ReDim Preserve arr(1 To 20)
注意这里有两个限制条件:一是 只能改变最后一维,二是 不能改变下界(只能扩展上界),这意味着对于二维数组而言,很多时候我们想要做的是增加行(即改变第一维的值),但是实际上不可行,我们只有修改第二维(也就是最后一维),最后往表格写入的时候再转置。

另外需要注意的是:

  • Redim支持变量和常量:比如 Redim arr(1 to 3)Redim arr(1 to n) 都是可以的(这里 n 是变量)
  • Dim只支持常量:Dim arr(1 to n) 的写法就是错误的,即使你已经定义了n=3,但 Dim 还是不支持
  • 如果想要清空数组,可以使用Erase arr实现,这样表格的大小还存在,但是其内容全部被清空

三、详谈二维数组

由于在表格中我们对二维数组的使用更多,因此这里单开一节,着重讲一下二维数组的使用方法,我们通过以下方式声明一个3行4列的二维数组:Dim arr(1 To 3, 1 To 4) As Variant,根据行索引和列索引我们可以访问每个元素并赋值:

arr(1, 1) = "A"
arr(2, 3) = 100
如果我们需要遍历二维数组,则需要两层嵌套,Lbound(arr,n) 这里的第二个值代表的是第几维的意思

1
2
3
4
5
6
7
Dim i As Long, j As Long

For i = LBound(arr, 1) To UBound(arr, 1)
    For j = LBound(arr, 2) To UBound(arr, 2)
        Debug.Print arr(i, j)
    Next j
Next i
在将数组处理完毕之后,我们一般将数组重新写入单元格区域,一次性写入,比循环快得多:
Range("A1:D100").Value = arr
但是如果想要行列数随着数组变化,我们通常会搭配Resize方法,来实现动态填入,例如:

range("a1").resize(100, 4).value = arr
range("a1").resize(ubound(arr, 1), ubound(arr, 2)).value = arr

四、数组中常用的 Split / Filter / Join 方法

在 VBA 中,数组并不只是用来“存数据”,很多时候它承担的是 字符串批量处理 的角色。其中最常用、也最值得掌握的三个方法就是:Split(拆分) / Filter(筛选) / Join(拼接)

1、Split —— 字符串拆分成数组

Split 用于 按指定分隔符,将字符串拆分成数组。返回值是一个下标从0开始的 一维 Variant 数组。其基本语法为:

Split(expression, delimiter, limit, compare)

  • expression :字符串表达式
  • delimiter:分隔符号
  • limit:最多返回的个数,比如“a,b,c,d”按逗号分隔,如limit=3,则拆分为a,bc,d三个部分,最后一个不拆分
  • compare:表示拆分符号是否区分大小写,vbBinaryCompare(0)表示区分,vbTextCompare(1)表示不区分

常用的只有前两个参数,比如我们看一个最基础示例:

Dim arr As Variant
arr = Split("A,B,C", ",")

结果:arr(0) = "A"arr(1) = "B"arr(2) = "C"

2、Filter —— 从数组中筛选符合条件的元素

Filter 用于 从一维字符串数组中筛选包含指定内容的元素,只能用于 一维字符串数组。语法结构如下:

Filter(sourcearray, match, include, compare)

  • sourcearray:表示待筛选的数组(1维)
  • match:表示要匹配的子字符串
  • include:为True(包含),为False(不包含), 默认是筛选包含的
  • compare:表示是否区分大小写,vbBinaryCompare(0)表示区分,vbTextCompare(1)表示不区分

来看一个最简单的师范,如下代码,得到的结果是:result(0) = "上海",只有一个符合条件:

1
2
3
4
5
Dim arr As Variant
Dim result As Variant

arr = Array("北京", "上海", "深圳", "广州")
result = Filter(arr, "上")

而如果是排除匹配项,使用 result = Filter(arr, "上", False),则 筛选不包含“上”的元素

3、Join —— 将数组拼接成字符串

Join 用于 将一维字符串数组合并成一个字符串。其基本语法为:

Join(sourcearray, delimiter)

  • sourcearray:一个一维的字符串数组,注意必须是一维的
  • delimiter:连接符号,可以不填,默认是空格

看一个最简单的示例,以下这段代码返回结果 A,B,C

1
2
3
4
5
Dim arr As Variant
arr = Array("A", "B", "C")

Dim s As String
s = Join(arr, ",")
三个方法最大的价值在于联合使用,这是 实际项目中最有价值的一部分,我们可以看一下以下示例:

1
2
3
4
5
6
Dim arr As Variant
arr = Split("1001|1002|1003", "|")

If UBound(arr) >= 0 Then
    Debug.Print Join(arr, ",")
End If

本期给大家总结了VBA中数组的常见属性和方法,以供参考