在VBA(Visual Basic for Applications)中,数组是一种非常有用的数据结构,用于存储一系列相同类型的元素。数组可以是一维的,也可以是二维或多维的。使用数组可以有效地处理批量数据,提高代码的效率和可读性。
1. 什么是数组?
生活化比喻
想象你有一排储物柜(数组),每个柜子(数组元素)有一个编号(索引),柜子里可以存放物品(数据)。
所有柜子的类型相同(比如只能放书),或者类型不同(混合放书、衣服等)。
编程概念
- 数组:一组连续内存空间存储的多个数据,通过索引快速访问。
- 核心特点:
避免重复定义变量(如num1, num2, num3...)
高效处理批量数据(如Excel表格中的一列数据)
2. 数组的声明与初始化
2.1 静态数组:固定大小的储物柜
' 语法:Dim 数组名(索引范围) As 数据类型
Dim locker(0 To 2) As String ' 3个柜子,索引0、1、2
locker(0) = "书包" ' 第一个柜子放书包
locker(1) = "水杯" ' 第二个柜子放水杯
locker(2) = "笔记本" ' 第三个柜子放笔记本
关键细节:
- 默认索引从0开始,Dim arr(2)实际是0,1,2共3个元素!
- 可用Option Base 1强制索引从1开始(写在模块顶部)
2.2 动态数组:可伸缩的储物柜
Dim locker() As String ' 声明时不指定大小
ReDim locker(2) ' 初始化3个柜子(索引0-2)
ReDim Preserve locker(4) ' 扩容到5个柜子,保留原有内容
那以上代码中ReDim Preserve是什么?为什么动态数组要用到它?
2.2.1. ReDim Preserve 是什么?
想象你有一个 可伸缩的储物柜(动态数组),原本有5个柜子,现在想扩容到10个柜子,但希望保留之前柜子里的物品。
ReDim Preserve 就是用来做这件事的:调整数组大小的同时,保留原有数据!
2.2.2. 核心作用
- 调整数组大小:扩大或缩小数组的容量。
- 保留原数据:原有索引位置的数据不会丢失。
- 适用场景:当你无法提前预知数组需要多大,需要动态扩展时使用(比如读取不确定行数的数据)。
2.2.3. 基础语法
ReDim Preserve 数组名(新的大小)
示例代码
Dim students() As String ' 声明动态数组
ReDim students(2) ' 初始大小:3个元素(索引0-2)
students(0) = "小明"
students(1) = "小红"
students(2) = "小刚"
' 扩容到5个元素(索引0-4),且保留原有数据
ReDim Preserve students(4)
students(3) = "小李" ' 新增第4个元素
students(4) = "小张" ' 新增第5个元素
2.2.4. 关键细节与限制
(1) 只能调整最后一维的大小
如果你想找到“最后一维”的概念,比如在多维数组中,通常我们谈论的是数组的最高维度(即最外层的维度)。对于二维数组来说,你可以认为“最后一维”实际上是“最内层的维度”,即每一行的元素。在二维数组中,每一行可以被视为一个单独的“一维数组”。
- 一维数组:自由调整。
- 多维数组:只能调整最后一个维度(比如二维数组的列数)。
错误示例(尝试调整第一维):
Dim matrix(2, 3) As Integer ' 3行4列
ReDim Preserve matrix(5, 3) ' 报错:不能调整第一维(非最后一维)
ReDim Preserve matrix(2, 5) ' 合法:调整列数(最后(右)一维)
(2) 缩小数组会丢失超出部分的数据
ReDim Preserve students(1) ' 缩小到2个元素(0-1)
' 原students(2)的值"小刚"将永久丢失!
(3) 索引范围变化
调整后的数组索引从LBound到新指定的上限,例如:
- 原数组:ReDim students(2) → 索引0-2(3个元素)
- 调整后:ReDim Preserve students(4) → 索引0-4(5个元素)
2.2.5. 为什么需要Preserve?
对比 ReDim 和 ReDim Preserve 的区别:
操作 | 是否保留数据 | 典型用途 |
ReDim arr(5) | 清空所有数据 | 初始化数组或彻底重置 |
ReDim Preserve arr(5) | 保留原数据 | 动态扩容/缩容且不丢失数据 |
2.2.6. 性能警告!
- 频繁使用ReDim Preserve会降低效率(尤其是处理超大数组时)。
- 优化建议:
- 尽量预估初始大小,减少调整次数。
- 如果必须频繁调整,可每次扩大一定容量(比如每次扩容50%)。
2.2.7.总结
- ReDim Preserve = 扩容/缩容 + 保留数据
- 牢记限制:只能动最后一维,多维数组要小心!
- 慎用场景:处理海量数据时避免频繁调用。
通过这个关键字,你可以像玩“拉伸储物柜”一样灵活管理数组,同时保证数据安全不丢失!
2.3. LBound 和 UBound:获取数组边界
作用
- LBound(数组名, 维度):返回数组指定维度的最小索引
- UBound(数组名, 维度):返回数组指定维度的最大索引
示例
Dim arr(3 To 8) As Integer ' 索引3-8(共6个元素)
Debug.Print LBound(arr) ' 输出3
Debug.Print UBound(arr) ' 输出8
Dim matrix(2, 5) As String ' 二维数组
Debug.Print UBound(matrix, 1) ' 第1维最大索引:2
Debug.Print UBound(matrix, 2) ' 第2维最大索引:5
注意
- 省略维度参数时,默认检查第1维
- 安全遍历数组必用此函数(避免越界错误)
2.4. Erase:释放数组内存
作用
- 对动态数组:彻底清空并释放内存(数组变为空)
- 对静态数组:重置所有元素为默认值(如数值变0,字符串变空)
示例
Dim arr() As Integer
ReDim arr(5)
Erase arr ' arr变为空数组,不可直接访问
注意
- 清空后需重新ReDim才能使用
2.5. Option Base 1:改变默认索引起点
作用
强制数组索引从 1 开始(默认从 0 开始)。
用法
Option Base 1 ' 写在模块顶部(所有数组受影响)
Dim arr(5) As Integer ' 索引1-5(共5个元素)
注意
- 需在模块最顶部声明
- 与显式指定索引冲突时以显式声明为准
(如 Dim arr(0 To 5) 不受 Option Base 1 影响)
总结表格
关键字/函数 | 作用 | 适用场景 |
Dim | 声明数组初始大小 | 定义新数组 |
ReDim | 调整动态数组大小 | 动态扩容/缩容 |
Preserve | 保留数据调整大小 | 需保留原数据的动态调整 |
LBound/UBound | 获取数组边界 | 安全遍历数组 |
Erase | 清空数组内存 | 重置或释放数组 |
Option Base | 改变索引起点 | 需要索引从1开始时使用 |
3. 数组的赋值与访问
3.1 直接赋值
' 一维数组
Dim fruits(2) As String
fruits(0) = "苹果"
fruits(1) = "香蕉"
fruits(2) = "橘子"
' 二维数组(想象成Excel表格)
Dim table(1 To 3, 1 To 2) As String ' 3行2列
table(1, 1) = "姓名"
table(1, 2) = "年龄"
table(2, 1) = "小明"
table(2, 2) = "25"
3.2 快速初始化:Array函数
Dim colors() As Variant
colors = Array("红", "绿", "蓝") ' 索引从0开始
3.3 从Excel范围读取数组(超高效!)
Dim dataRange As Range
Set dataRange = Range("A1:C10") ' 选中A1到C10区域
Dim arrData As Variant
arrData = dataRange.Value ' 直接存入二维数组
4. 遍历数组:逐个访问储物柜
4.1 一维数组遍历
For i = LBound(arr) To UBound(arr)
Debug.Print "第" & i & "个元素:" & arr(i)
Next i
4.2 二维数组遍历(类似Excel行列)
For row = 1 To 3
For col = 1 To 2
Debug.Print "第" & row & "行,第" & col & "列:" & table(row, col)
Next col
Next row
函数解释:
- LBound(arr):返回数组最小索引
- UBound(arr):返回数组最大索引
- UBound(arr, 2):获取二维数组的列数(第二维)
5. 动态数组的实用技巧
5.1 清空数组
Erase locker ' 动态数组内存释放,静态数组重置为初始值
5.2 动态扩容保留数据
ReDim Preserve locker(10) ' 从原来的0-2扩容到0-10
注意:扩容时原有数据保留,但只能调整最后一维!
6. 常用数组函数
6.1 Split:字符串转数组
Dim text As String
text = "苹果,香蕉,橘子"
Dim fruitArr() As String
fruitArr = Split(text, ",") ' 按逗号分割为数组
6.2 Join:数组转字符串
Dim combinedText As String
combinedText = Join(fruitArr, "|") ' 输出 "苹果|香蕉|橘子"
7. 避坑指南:常见错误
错误1:下标越界
Dim arr(2) As Integer
arr(3) = 10 ' 错误!索引最大是2
正确做法:
If i >= LBound(arr) And i <= UBound(arr) Then
arr(i) = 10
End If
错误2:类型不匹配
Dim nums(2) As Integer
nums(0) = "ABC" ' 错误!不能将字符串存入Integer数组
正确做法:
使用Variant类型数组存储混合数据:
Dim mixedArr() As Variant
mixedArr = Array(1, "苹果", True)
ReDim 不能用于静态数组
Dim staticArr(5) As Integer
ReDim staticArr(10) ' 报错!静态数组大小固定
多维数组调整限制
Dim matrix(2, 3) As Integer
ReDim Preserve matrix(5, 3) ' 调整行数(第一维)
ReDim Preserve matrix(2, 5) ' 报错!只能调整最后一维(列数)
索引起点混淆
Option Base 1
Dim arr(5) As Integer ' 索引1-5(共5个元素)
Debug.Print arr(0) ' 报错!索引从1开始
8. 实战案例:用数组快速处理Excel数据
场景:计算A列数值的总和(避免逐个操作单元格)
Sub SumColumn()
' 1. 读取数据到数组
Dim dataRange As Range
Set dataRange = Range("A1:A10000") ' 假设有1万行数据
Dim arrData As Variant
arrData = dataRange.Value ' 存入二维数组(索引从1开始!)
' 2. 遍历数组计算总和
Dim total As Double
For i = 1 To UBound(arrData, 1) ' 行数
total = total + arrData(i, 1) ' 第一列
Next i
' 3. 输出结果
MsgBox "总和:" & total
End Sub
效率对比:
- 直接操作单元格:约3秒(1万行)
- 使用数组:约0.01秒!
案例2:快速转置行列
Sub 行列转置()
Dim 原始数据(1 To 3, 1 To 2) As Variant
原始数据 = Range("A1:B3").Value
Dim 转置结果(1 To 2, 1 To 3) As Variant
For i = 1 To 3
For j = 1 To 2
转置结果(j, i) = 原始数据(i, j)
Next
Next
Range("D1:F2").Value = 转置结果
End Sub
案例3:多表数据汇总
Sub 合并数据()
Dim 总数据() As Variant
Dim 行号 As Long: 行号 = 0
For Each ws In Worksheets
Dim 当前表数据 As Variant
当前表数据 = ws.Range("A1").CurrentRegion.Value
ReDim Preserve 总数据(行号 To 行号 + UBound(当前表数据) - 1, 1 To 5)
For i = 1 To UBound(当前表数据)
行号 = 行号 + 1
For j = 1 To 5
总数据(行号, j) = 当前表数据(i, j)
Next
Next
Next
Sheets("总表").Range("A1").Resize(行号, 5).Value = 总数据
End Sub
9. 练习与巩固
练习1:反转数组
将数组 [1,2,3,4,5] 反转为 [5,4,3,2,1]
提示:用循环交换首尾元素。
练习2:统计字符出现次数
将字符串 "hello world" 存入数组,统计字母l出现的次数。
总结
- 数组核心优势:提升代码速度,简化数据管理
- 学习路径:静态数组 → 动态数组 → 多维数组 → 实战应用
- 避坑口诀:
① 索引从0还是1?用LBound和UBound检查!
② 动态数组先ReDim,扩容记得加Preserve!
通过以上步骤,即使零基础也能逐步掌握VBA数组的核心用法!建议边学边写代码测试,遇到问题随时回看文档
下章预告:《VBA之字典》