使用 For each Statement 遍历二维数组 - 每行/不是每列

T. *_*P. 2 arrays each vba for-loop multidimensional-array

首先,我不想使用嵌套的 For 循环,因为我读到这些会随着数据量的增加而变慢(n 可以高达 10k)。所以,现在,我有一个数组arrData(n,2),而 whilen是可变的,我想每行遍历这个数组,使用 for each 语句。所以这是我的代码。为简化起见,我插入了一个 (2,2) 数组:

Sub test()

    Dim arrData(2, 2) As Variant

    arrData(0, 0) = 0
    arrData(0, 1) = 0
    arrData(0, 2) = 0
    arrData(1, 0) = 1
    arrData(1, 1) = 1
    arrData(1, 2) = 1
    arrData(2, 0) = 2
    arrData(2, 1) = 2
    arrData(2, 2) = 2

    For Each Element In arrData
        MsgBox Element
    Next Element

End Sub
Run Code Online (Sandbox Code Playgroud)

我得到 012012012,但我想得到 000111222。

Com*_*ern 10

首先,让我们解决这个误解:

我读到这些会随着数据量的增加而变慢(n 可以达到 10k)

这是不正确的。多维数组仅比单维数组“慢”,因为必须计算索引器的内存地址(稍后会详细介绍)。您最有可能指的是嵌套循环的计算复杂性 - 迭代次数随着每个循环边界的乘积而增加。您有固定数量的元素,因此无论如何访问它们都是相同的。如果您想对o二维数组的每个成员执行操作,您将执行该计算 b1 * b2 次。时期。


现在,为了解释示例代码给出的结果,让我们看看 VBA 如何在内存中布置数组。我将通过仅查看数据区域来简化这一点(还有一个 SAFEARRAY 结构保存有关数组的元信息,但这并不是真正相关的)。具有单一维度的数组被布置为连续的内存区域,VBA 维护一个指向第一个元素的指针。例如,Long 的一维数组看起来像这样 ( Dim foo(4) As Long):

一维数组

SAFEARRAY 结构保存一个指向“元素 0”的指针,当您在代码中访问它时,它将索引器乘以元素类型的长度(以字节为单位),然后返回该内存地址处的值。因此,如果第一个元素位于内存地址 0x0000 并且您访问foo(2),它将乘以 2 乘以 4(a 的长度Long,将其添加到 0x0000,并为您提供从 0x0008 开始的 4 个字节。

基本上,A + (L * E1), 其中A是基地址,L是元素长度,E1是您请求的元素。


第二个维度N在内存中添加此布局的副本,其中N是第二个维度中的元素数。因此,示例代码中的数组布局如下 ( Dim foo(2, 2) As Long):

二维数组

VBA 对它的索引与一维数组相同,除了第二维之外,它将第二维的索引器与完整的第一维的总长度的乘积添加到第一维元素的地址计算中。

基本上,A + (L * E1) + (L * B1 * E2),其中B1是第一个维度的元素计数,是第二个维度E2的索引。因此,如果您foo(1, 1)从 0x0000 的基地址访问,它将是0 + (4 * 1) + (4 * 3 * 1),或 0x0010。

快速说一下 - 这就是为什么除了Redim Preserve数组的最高维度之外你不能做任何事情- 这是唯一一种简单的内存分配和复制的情况。


因此,转向您的示例,您的值在内存中是这样存储的:

OP 在内存中的数组

当您使用时For Each,VBA 的数组迭代器只是按内存顺序将每个元素返回给您,因此您会得到 012012012。对于您的具体示例,您可以通过转置它以 000111222 的顺序返回它们 - 您所说的“行”实际上是您示例中的第一个维度:

 Sub Example()
    Dim arrData(2, 2) As Variant

    arrData(0, 0) = 0
    arrData(1, 0) = 0
    arrData(2, 0) = 0
    arrData(0, 1) = 1
    arrData(1, 1) = 1
    arrData(2, 1) = 1
    arrData(0, 2) = 2
    arrData(1, 2) = 2
    arrData(2, 2) = 2

    For Each Element In arrData
        Debug.Print Element
    Next Element
End Sub
Run Code Online (Sandbox Code Playgroud)

这会像这样在内存中布置数组:

转置二维数组


也就是说,For Each循环的开销比简单For循环要多,因为 VBA 必须使用数组枚举器并将_NewEnum调用推入堆栈。虽然您可能会看到索引中的微小性能提升,因为它只是向内存地址添加一个偏移量而不是每次执行更长的计算,但这远远超过重复推入和弹出调用堆栈。所以,长话短说,只需嵌套循环:

Dim outer As Long
Dim inner As Long
For outer = LBound(arrData, 1) To UBound(arrData, 1)
    For inner = LBound(arrData, 2) To UBound(arrData, 2)
        Debug.Print arrData(outer, inner)
    Next
Next
Run Code Online (Sandbox Code Playgroud)

在您的情况下,您将通过交换内循环和外循环来“转置”数组。

注意:我没有在 Excel 的上下文中使用“行”(尽管它会是第一列),并且通过“转置”,我并不是指使用 Excel 的Transpose函数 - 这会比任何一种选择都有更差的性能。