☰
VBA中Range.Value与数组的底层真相及高效实战技巧
2026/10/13 2:12:22 网站建设 项目流程

在VBA里写代码的兄弟,一定都写过类似arr = Range("A1:C10").Value这样的句子。表面上看,这就是把单元格区域的值装进变量,可等你真正去看这个变量的类型、维度、下标时,会发现它完全不按直觉来。很多人在这一步吃亏,要么把数组当普通变量用,要么循环取值时不知道下标从几开始,要么以为拿到的是文本却得到一个日期序列。这篇文章就把Range.Value与数组之间的那点事彻底说清楚,从底层行为到日常踩坑,再到配合字典、排序的进阶玩法,一次性讲透。

1. 一次Range读取背后的数据类型真相

1.1 Value属性在多单元格区域下返回的到底是什么

VBA里Range对象的Value属性,默认返回Variant类型。所谓Variant,就是一个可以装任何东西的容器。当你读取的是一个多单元格矩形区域时,VBA会把整个区域转换成一个二维数组。这里的“二维”是硬性的,哪怕你的区域只有一列,比如Range("A1:A10").Value,拿到的也是10行1列的二维数组;只有一行同理,是1行N列的二维数组。我第一次意识到这一点,是在调试窗口用TypeName查看变量类型,发现返回Variant(),又用VarType查看,返回值是8204(vbArray + vbVariant的组合),这才确认Value在区域场景下的真身其实是个数组。

为什么VBA要做这种设计?因为单元格天然具有“行”和“列”两个坐标,要用一个变量完整表达某个矩形块,最自然的数据结构就是二维数组。数组的第一维对应行号,第二维对应列号,和Excel里的行列坐标系一一对应,使用的时候几乎不需要额外做映射。这个设计从老版本一直延续到现在,所以不要怀疑,你拿到的就是一个二维数组。

有一个很容易被忽略的细节:如果区域里只有一个单元格,比如Range("A1").Value,那么此时返回的不是数组,而是一个标量值。这导致很多写通用函数的人翻车。你写一个函数接收Range参数,内部直接arr = rng.Value,如果别人传入的是单格,arr就只是个普通变量,后面凡是数组操作全部失效。处理办法是先判断区域单元格个数:

Public Function RangeToArray(rng As Range) As Variant If rng.Cells.Count = 1 Then Dim tmp(1 To 1, 1 To 1) As Variant tmp(1, 1) = rng.Value RangeToArray = tmp Else RangeToArray = rng.Value End If End Function

这样不管传入什么区域,统一得到二维数组,调用方就不会遇到数据类型突然变化的诡异问题。

1.2 下标从1开始,为什么总有人在这栽跟头

拿到数组后,第一件事往往是确定边界。很多人习惯直接用UBound(arr)取最大行号,但这里有个经典坑:如果不指定维度,UBound返回的是第一维的上界,也就是行数。如果想取列数,必须写UBound(arr, 2)。同理,LBound(arr, 2)才是列方向的起始下标。

从Range.Value拿到的数组,默认行和列的下标都从1开始。也就是说,arr(1, 1)对应你读取区域的左上角单元格。这跟手动用Dim arr(0 To 10)声明的数组不一样,那种可能从0开始。VBA里数组的起始下标默认是0,但Range.Value偏偏返回的是一个从1开始的二维数组。这一点非常容易让人错乱。所以,在写遍历代码时,最稳妥的做法不是假设下标是0或1,而是动态读取:

Dim r As Long, c As Long For r = LBound(arr, 1) To UBound(arr, 1) For c = LBound(arr, 2) To UBound(arr, 2) Debug.Print arr(r, c) Next c Next r

这样写对任何来源的数组都通用,不管是Range.Value、Application.Transpose返回的,还是别人传过来的参数。我的习惯是,凡是涉及数组循环的函数,一律用LBound/UBound,绝对不写死1。曾有项目里因为模块顶部写了Option Base 1,导致另一种数组的行为发生变化,但Range.Value却没有变,这种不一致只有靠动态边界才能规避。

1.3 什么时候返回的不是数组:单单元格与空区域

刚才提到单格返回标量,再补充一下:有时候即便区域有多个单元格,也可能拿不到想要的“普通数组”。比如使用Text属性时,返回的是显示文本的二维数组,但数组中所有元素会被强制转成字符串,日期、数字都会变成格式化后的文本,这在后续计算中很容易出错。如果使用Value2属性,它不关心单元格格式,日期会被转成序列号,拿到数组的元素类型也是以Double为主。

还有一种是“空区域”边界情况。比如你通过Union把几个区域合并,但区域间没有共同单元格,或者动态构造了一个没有单元格的区域,直接读.Value可能返回Empty。写代码前最好判断一下区域是否有效:If rng Is Nothing Then Exit Sub,或者看rng.Cells.Count = 0。

2. 数组与Range互转的实操技巧

2.1 从Range到数组:正确声明与赋值

把区域一次性读入数组的写法非常简单:

Dim arr As Variant arr = Sheet1.Range("A1:C10").Value

注意,这里arr必须声明为Variant,不能声明成具体的数组类型。如果你写成Dim arr() As String,然后直接赋值,VBA会报“类型不匹配”或者“不能给数组赋值”。因为Value返回的是Variant类型的数组,里面每个元素也是Variant,和String数组不是一个类型。所以最省事的方式就是声明为Variant,后面用的时候再用CStr、CDbl、CLng等函数显式转换。

为什么强调这种方式而不是循环单元格?因为Range.Value返回数组的过程是一次性在内存中完成的。如果是大范围数据,比如1万行乘以20列,循环单元格每次都要通过COM接口访问Excel对象模型,开销非常大;而一次性读取只需要一次对象访问,后面全是内存操作。这个差异在数据量大时极为明显,下文会有实测数据。

读取之后,数组元素的值类型五花八门:数字、字符串、日期、错误值、空值。空单元格在数组中对应的是Empty,不是空字符串,也不是Nothing。判断空单元格用IsEmpty(arr(r, c))。有很多人用arr(r, c) = ""去判断,这在某些情况下会得出错误结果,因为Empty与空字符串的比较在VBA中并不是一个可靠判断。这一点在后面问题排查部分单独展开。

2.2 数组写回Range:别让区域大小玩你

读数组是批量,写回同样可以批量。假如在内存中已经处理好了arr,现在要把结果放回Excel,直接:

Range("E1:G10").Value = arr

只要arr的维度与目标区域的行列数一致,VBA会自动逐格填入。这里有两个容易踩的坑:

第一,如果目标区域比数组小,多余的数组元素会被丢弃,不会报错。比如arr是20行3列,你赋给E1:G10(10行3列),只有前10行数据会写入,后面10行静默丢失。这在调试时很难发现,因为代码不会中断。

第二,如果目标区域比数组大,也就是arr是10行3列,你赋给E1:G20(20行3列),多余单元格会变成#N/A错误。这个行为在Excel 2016及更新版本中比较常见,老版本可能表现不同。

所以,最稳妥的做法是精确设置目标区域,让它和数组维度一致。如果不知道数组维度,就用UBound/LBound算出来,再构造Range:

Dim rows As Long, cols As Long rows = UBound(arr, 1) - LBound(arr, 1) + 1 cols = UBound(arr, 2) - LBound(arr, 2) + 1 Range("E1").Resize(rows, cols).Value = arr

这样无论arr多大都能正确写回,而且代码可读性更好。

2.3 转置的坑:Transpose、一维数组与方向

数组和区域互转时,经常会遇到方向问题。比如你从A1:A10读取,拿到的是10行1列二维数组,写回其他列时会自动按列填充,没问题。但如果你用Array()函数构造了一个一维数组,直接赋给Range,VBA默认会按行方向填充,不会自动变成一列。这时候需要用Application.Transpose转置一下:

Dim arr arr = Array(1, 2, 3, 4) Range("A1:A4").Value = Application.Transpose(arr)

这个技巧网上一搜一大把,但少有人提的是Transpose本身的限制:

  • 数组元素总数不能超过65536。这是VBA二维数组的维度上限,元素超过这个数会报错。在数据只有几万行时还算安全,但如果你处理的是几十万行数据,用Transpose极容易踩雷。
  • 数组元素如果是字符串,并且长度超过255个字符,Transpose有时会返回错误值或丢失数据。我在处理长文本字段时遇到过,排查了很久才发现是Transpose的锅。
  • 数组中如果包含错误值或某些特殊类型,Transpose也可能异常。

所以我的建议是,如果你只是处理一个1万行以内的单列数组,用Transpose没问题;如果数据量大、字符串长,最好自己写一个转置函数。下面这段代码很简单,但能绕开所有内置限制:

Function MyTranspose(ByVal src As Variant) As Variant Dim r As Long, c As Long Dim rows As Long, cols As Long rows = UBound(src, 1) - LBound(src, 1) + 1 cols = UBound(src, 2) - LBound(src, 2) + 1 Dim res As Variant ReDim res(1 To cols, 1 To rows) For r = 1 To rows For c = 1 To cols res(c, r) = src(r, c) Next c Next r MyTranspose = res End Function

注意这个函数只适用于从Range.Value得到的二维数组(下标从1开始)。如果你的数组来源复杂,可以先把下标规范化,再转置。

2.4 封装自己的通用工具函数

在项目里我通常会把下面几个工具函数放到标准模块,所有模块共用:

  • RangeToArray(rng):统一把Range转成二维Variant数组,处理单格特殊情况。
  • ArrayToRange(arr, startCell):根据数组维度自动扩展目标区域,写入数据。
  • MyTranspose(src):安全的转置函数。

这样后续写业务代码时,不需要每次纠结Range和数组的边界问题。比如读数据就写arr = RangeToArray(Sheet1.Range("A1:F10000")),写回就写Call ArrayToRange(resultArr, Sheet1.Range("H1")),代码清爽很多,也方便统一修改边界逻辑。

3. 性能对比与选型建议:数组为什么比逐格操作快

3.1 慢的根源是COM接口调用

很多VBA初学者知道数组快,但说不清为什么快。实际上,VBA操作Excel单元格,每执行一次Cells(r, c).Value = xxx,都需要通过COM接口和Excel对象模型交互,Excel要解析请求、更新内部状态,哪怕你提前设置了Application.ScreenUpdating = False,这些底层开销依然存在。循环一万次,就是一万次COM调用;而用数组读取一次,在内存里随便操作,最后写回一次,全程只有两次COM交互。这个差异是数量级的,不是单纯靠优化循环体就能弥补的。

我实测过一个简单场景:10万行,2列数据,给每行B列计算A列数值乘以2。用循环单元格的方式,耗时在十几秒左右;用数组处理,整个读写加计算不超过0.2秒。差距几十倍到上百倍。尤其在处理大量数据时,数组几乎是唯一正确的选择。

3.2 什么时候别用数组:格式、公式与超大数据

数组不是银弹,有几种场景不适合强行用:

第一,你需要读取或修改单元格格式。Range.Value只取值,不带任何格式信息。如果你要根据背景色、字体颜色、边框类型做判断,只能老老实实通过Cells(r, c).Interior.Color访问,这些操作绕不开COM交互,数组帮不上忙。

第二,你需要写入公式,并希望公式自动适应相对引用。数组可以装公式文本,赋给区域后Excel会计算,但公式中的相对引用会以目标区域左上角为锚点自动平移,不一定符合预期。比如你把字符串“=A12”写进整个数组,赋给B1:B10,那么B1的公式是=A12,B10的公式是=A102,这通常是你想要的;但如果公式是“=C12”,你可能希望每行引用C列对应行,反而会变成所有行都引用绝对行1,这时就需要手工修正,或者不用数组批量写。

第三,数据量超大且要反复排序。虽然数组内存操作快,但VBA中数组排序没有内置高效方法,如果自己写快速排序,代码量大且容易出错。对超大区域,更实用的做法是借助Excel本身的Sort功能,把数据放回临时区域排序,再读回数组。这种“曲线救国”方式,实际体验比纯数组排序好。

另外,如果数据量只有几十条,用数组反而显得代码冗长。此时循环单元格更直观,维护成本更低。不要为了用数组而用数组,性能优化要有明确的目标。

3.3 一个简单测试,自己感受差距

下面这段测试代码可以直接在模块里跑,感受循环与数组的差距:

Sub TestSpeed() Dim i As Long Dim t As Double Dim arr As Variant ' 准备10万条数据 Range("A1:A100000").Value = 1 t = Timer For i = 1 To 100000 Cells(i, 2).Value = Cells(i, 1).Value * 2 Next i Debug.Print "循环方式: " & Timer - t & " 秒" t = Timer arr = Range("A1:A100000").Value Dim r As Long For r = LBound(arr, 1) To UBound(arr, 1) arr(r, 1) = arr(r, 1) * 2 Next r Range("C1:C100000").Value = arr Debug.Print "数组方式: " & Timer - t & " 秒" End Sub

我在台式机上跑的结果:循环方式约14秒,数组方式约0.08秒。不同机器会有差异,但数量级一定差很多。这个测试不是让你每次提交代码前都跑,而是当你想说服同事改用数组时,直接把结果甩过去比任何解释都有力。

4. 常见问题与排查技巧实录

4.1 下标越界:最常见也最冤的错误

数组下标越界大概是VBA数组相关错误里出现频率最高的。从Range.Value拿到的数组,默认LBound是1。但如果你在模块顶部写了Option Base 0,那么你用Array()或者Dim声明数组时,下标默认从0开始,而Range.Value返回的数组依然从1开始。这种不一致非常容易导致代码在不同环境表现不同。

比如你写了一个函数,内部用arr = Range("A1:A10").Value,然后循环For i = 0 To UBound(arr),第一行就访问arr(0,1),直接下标越界。反过来,如果你手动声明一个下标从1开始的数组,但Option Base 0没有生效,你又会因为越界而报错。所以,无论什么时候都别硬编码下标,用LBound/UBound动态获取。这真的是血的教训。

4.2 Value、Value2、Text:你到底拿到了什么

Range.Value返回的值会受单元格格式影响。日期格式的单元格用Value读出来是日期类型;文本格式的“2024-01-01”用Value读出来是字符串;数值带千分位格式的单元格,Value读出来仍是数值而不是带格式的字符串。Range.Value2则刻意忽略格式,日期会变成数字序列号,比如2024年1月1日变成45292。Range.Text返回的是显示在单元格里的字符串,这是一个只读属性,不能用于写入。

在数组数据处理里,这几个属性的差异会带来隐蔽的错误。比如你用Value读取日期列,然后把数组元素直接拼接到SQL语句,VBA会自动把日期转成系统区域格式的字符串,有可能变成“2024/1/1”这种格式,导致SQL解析或比较预期不一致。我的建议是:数据匹配场景,统一类型;假设你从两个表分别读取数据,一个用Value,一个用Value2,日期元素就会一个保持日期类型、一个变成数字,比较结果自然不对。稳妥的做法是拿到数组后,对需要比较的列统一做CStr(arr(r, c))或者CDbl(arr(r, c))。

4.3 空单元格、日期、错误值的三个陷阱

空单元格在数组里对应Empty。判断空值最好用IsEmpty(arr(r, c)),不要用arr(r, c) = ""。VBA里Empty和空字符串在某些情况下比较会得到False,在另一些情况下又可能得到True,这取决于上下文,结果很不稳定。也不要用arr(r, c) = vbNullString,同样不可靠。

日期在数组里本质上是一个Double,但VBA会根据单元格格式显示成日期。如果你对这个值做加减乘除,没问题;但如果直接拼接字符串,得到的是这个单元格的显示文本还是底层数字,取决于你从哪里取值。用Value读取时,日期类型会保留,但直接CStr会得到系统格式的文本;用Value2读取时,日期已经变成数字,需要CDate才能转回日期。处理日期时,我的经验是:先判断是否是日期格式的单元格,或者直接用IsDate(arr(r, c))判断元素内容,再统一转换。

错误值如#N/A、#DIV/0!在数组中是Error类型的值。用arr(r, c) = CVErr(xlErrNA)比较不一定可靠,最好用IsError(arr(r, c))判断。如果数组里存在错误值,直接写回Excel也会在单元格显示错误,而且如果对这些值做连接操作,会得到错误文本拼接,需要先过滤。

4.4 遍历二维数组的顺序与降维

遍历二维数组时,循环顺序会影响性能。VBA数组在内存中的布局并不是严格按行或列连续,但习惯上先循环行再循环列,更符合Excel的行列访问习惯,也能减少缓存切换。不过对于从Range.Value得到的数组,真正影响性能的往往不是循环顺序,而是你在循环内部做的操作。如果你在循环内频繁调用WorksheetFunction,性能一样会下降。

另一个容易搞混的是“降维”。很多人在读取单列区域A1:A10得到二维数组后,想把它当成一维数组用,直接用ReDim Preserve arr(UBound(arr)),结果发现Preserve只能改变最后一维的上界,不能改变维度数量。正确做法是新建一个一维数组,循环填入:

Dim singleCol As Variant singleCol = Range("A1:A10").Value Dim num As Long num = UBound(singleCol, 1) - LBound(singleCol, 1) + 1 Dim oneD() As Variant ReDim oneD(1 To num) For i = LBound(singleCol, 1) To UBound(singleCol, 1) oneD(i) = singleCol(i, 1) Next i

如果你只是想把单列数据写回横向区域,可以直接用Application.Transpose,但注意前面说的限制。总之,“二维数组绝对不是一维数组的某种形式”,它始终是二维的,这一点要记牢。

4.5 大数组的内存占用与碎片问题

VBA中数组占用的是内存,虽然堆内存很大,但极端情况下也有坑。比如一次读取几十万行、几十列的数据,数组元素都是Variant,每个Variant至少占用16字节,一个50万行、20列的数组可能占用上百MB内存。在32位Office环境下,进程内存上限较低,可能跑着跑着就内存不足。处理这种超大数据时,建议分块读取,比如每次读5万行,处理完再合并结果。另外,动态扩展数组时,ReDim Preserve复制整个数组的开销很大,频繁使用会导致性能急剧下降。如果能够先统计出结果大小,一次性ReDim,是最优策略。

5. 进阶玩法:数组在业务场景中的实战

5.1 数组加字典:告别VLOOKUP慢循环

假设你有两张表,一张是原始订单(几万行),一张是产品价格表(几千行)。需要根据产品ID匹配价格。最笨的办法是循环原始订单每一行,然后在价格表区域里用Find或VLOOKUP函数,这样不仅慢,而且容易出错。用数组加字典就可以把价格表加载到内存,然后循环订单数组直接查字典:

Sub MatchPrice() Dim orderArr As Variant, priceArr As Variant Dim dict As Object Set dict = CreateObject("Scripting.Dictionary") priceArr = Sheet2.Range("A1:B2000").Value Dim i As Long For i = LBound(priceArr, 1) To UBound(priceArr, 1) dict(priceArr(i, 1)) = priceArr(i, 2) Next i orderArr = Sheet1.Range("A1:D50000").Value For i = LBound(orderArr, 1) To UBound(orderArr, 1) If dict.Exists(orderArr(i, 1)) Then orderArr(i, 4) = dict(orderArr(i, 1)) End If Next i Sheet1.Range("A1:D50000").Value = orderArr End Sub

这里有几个细节要注意:字典的key区分大小写,如果产品ID是文本且大小写格式不统一,最好先用UCase统一;价格表中如果有重复ID,后面写入的会覆盖前面的,要确保ID唯一;如果订单表中存在价格表没有的ID,上面代码不会报错,但列D会保持原值。建议把没有匹配到的行标一个特殊标记,比如"未匹配",方便后续核对。

5.2 数组去重统计:计数器直接用

统计某列中每个值的出现次数,这是典型场景。循环单元格时,不管用CountIf还是字典,都会消耗大量时间。用数组加字典是秒级完成:

Dim arr As Variant arr = Sheet1.Range("A1:A100000").Value Dim dict As Object Set dict = CreateObject("Scripting.Dictionary") Dim i As Long For i = LBound(arr, 1) To UBound(arr, 1) Dim key As String key = CStr(arr(i, 1)) dict(key) = dict(key) + 1 Next i

这里dict(key) = dict(key) + 1有一个妙处:当key不存在时,dict(key)会得到0,然后+1变成1。VBA的字典对象支持这种“先访问后赋值”的用法,省去了显式判断Exists的过程。注意,这里key统一转成字符串,是为了避免数字与文本因为底层类型不同而被当成两个key。如果你要统计日期,也建议先做格式化,比如Format(arr(i, 1), "yyyy-mm-dd"),否则同一个日期可能因为带不带时间部分被统计成两条。

5.3 筛选到新数组的正确姿势

VBA里想从原数组筛选出符合条件的行,不能直接用ReDim Preserve动态删行。常见错误是试图用ReDim Preserve resultArr(1 To matchCount, 1 To cols),但VBA规定ReDim Preserve只能改变最后一维的上界,第一维无法用Preserve修改。正确做法是两次遍历:第一次计数确定目标数组大小,第二次再填入数据:

Function FilterArr(ByVal src As Variant, ByVal col As Long, ByVal threshold As Double) As Variant Dim i As Long, c As Long, cnt As Long Dim rows As Long, cols As Long rows = UBound(src, 1) - LBound(src, 1) + 1 cols = UBound(src, 2) - LBound(src, 2) + 1 cnt = 0 For i = LBound(src, 1) To UBound(src, 1) If CDbl(src(i, col)) > threshold Then cnt = cnt + 1 Next i If cnt = 0 Then FilterArr = Empty Exit Function End If Dim result As Variant ReDim result(1 To cnt, 1 To cols) Dim idx As Long idx = 0 For i = LBound(src, 1) To UBound(src, 1) If CDbl(src(i, col)) > threshold Then idx = idx + 1 For c = LBound(src, 2) To UBound(src, 2) result(idx, c) = src(i, c) Next c End If Next i FilterArr = result End Function

这个函数接受源数组、条件列、阈值,返回一个只包含满足条件行的新数组。调用时,如果忘记处理单格或空数组,后面写回Range时可能出错,所以在使用前要加判断:If IsEmpty(resultArr) Then Exit Sub。这里使用了CDbl强制比较,保证数字列与文本数字都能正常工作,但如果列里本身有文本“abc”,CDbl会报错,需要根据实际数据调整成Val或CStr。

5.4 数组排序的几种可靠方案

VBA没有内置的数组排序函数。网上常见的做法是冒泡排序,代码简单但数据量大时慢得离谱。排序一万条数据,冒泡大概要几秒到十几秒,排序十万条就不可接受了。有几个替代方案:

第一,借用Excel工作表区域排序。先把数组写回临时区域,用Range.Sort排序,再读回数组。这个方案速度很快,而且不需要自己写排序算法,缺点是来回多了一次读写,但总时间通常还是优于冒泡。

第二,使用ArrayList。通过CreateObject("System.Collections.ArrayList")创建对象,Add元素后调用Sort方法。这个方法只适合一维数组或需要将二维数组先转成一维的情况。要按二维数组某一列排序,可以先把该列提取出来,排序的同时记录索引,再按索引重组数组。

第三,自己写快速排序。对于二维数组的排序,写一个递归快排,根据指定列的值交换整个行。代码量较大,但可控性强,不会因为Office版本不同而出问题。下面是一个按第一列排序的二维数组快速排序示例(升序):

Sub QuickSort2D(ByRef arr As Variant, ByVal colIndex As Long, ByVal low As Long, ByVal high As Long) If low >= high Then Exit Sub Dim i As Long, j As Long Dim pivot As Variant, tmp As Variant i = low j = high pivot = arr((low + high) \ 2, colIndex) Do While i <= j Do While arr(i, colIndex) < pivot i = i + 1 Loop Do While arr(j, colIndex) > pivot j = j - 1 Loop If i <= j Then Dim k As Long For k = LBound(arr, 2) To UBound(arr, 2) tmp = arr(i, k) arr(i, k) = arr(j, k) arr(j, k) = tmp Next k i = i + 1 j = j - 1 End If Loop If low < j Then QuickSort2D arr, colIndex, low, j If i < high Then QuickSort2D arr, colIndex, i, high End Sub

使用前先计算数组维度范围,调用一次即可。这个代码假设数组元素都是可比较的类型,比如数字或字符串。如果列里有空值或混合类型,需要先做类型统一,否则比较时会出错。实际项目中,如果数据量不大(几千行),用ArrayList可能更省心。

结尾

我在实际项目里,最开始也是习惯循环单元格,后来被数据量逼着用数组。踩过数组下标从0开始的坑,也踩过单格返回标量导致运行时错误,还曾经在Transpose上折腾了半个下午。现在写VBA处理表格,凡是涉及数据计算,第一反应就是读数组、操作数组、写回数组。踩的坑多了,慢慢总结出一个原则:无论什么业务,先读数组,再处理,最后批量写回;遇到边界用LBound/UBound判断;遇到空值用IsEmpty;遇到类型模糊就显式转换。这个套路几乎能覆盖90%的表格数据处理需求。最后再分享一个小技巧:当你拿到一个从Range.Value得到的二维数组时,可以先用Debug.Print TypeName(arr)确认它是Variant()并且VarType(arr) = 8204,这能帮你快速判断自己面对的到底是数组、标量还是别的什么怪物,很多诡异问题在这一步就暴露了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询