Во-первых, давайте обратимся к этому заблуждению:
Я читал, что они станут медленнее с большим объемом данных (n может быть до 10 КБ)
Это просто неправда. Многомерные массивы только «медленнее», чем одномерные массивы, потому что адрес памяти индексатора должен быть вычислен (подробнее об этом позже). Скорее всего, вы имеете в виду вычислительную сложность вложенного цикла - число итераций увеличивается как произведение границ каждого цикла. У вас есть фиксированное количество элементов, поэтому оно будет одинаковым независимо от того, как к ним обращаются Если вы хотите выполнить операцию o
для каждого члена двумерного массива, вы будете выполнять это вычисление b1 * b2 раз. Период.
Теперь, чтобы объяснить результат, который дает ваш пример кода, давайте посмотрим, как VBA размещает массив в памяти. Я немного упросту это, лишь посмотрев на область данных (есть также структура SAFEARRAY , которая содержит метаинформацию о массиве, но это не совсем уместно). Массив с одним измерением располагается как непрерывная область памяти, и VBA поддерживает указатель на первый элемент. Например, одномерный массив Long будет выглядеть так (Dim foo(4) As Long
):
Структура SAFEARRAY содержит указатель на «Элемент 0», и когда вы обращаетесь к нему в своем коде, он умножает индексатор на длину типа элемента в байтах, а затем возвращает значение по этому адресу памяти. Таким образом, если бы первый элемент был по адресу памяти 0x0000 и вы получили доступ к foo(2)
, он умножил бы 2 на 4 (длина Long
, прибавил бы это к 0x0000 и дал бы вам 4 байта, начинающиеся с 0x0008.
По сути, 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
ничего, кроме верхнего измерения массива - это случай только , когда это простое выделение памяти и копирование.
Итак, поворачиваясь к вашему примеру, ваши значения сохраняются в памяти следующим образом:
Когда вы используете 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
Это раскладывает массив как в памяти:
Тем не менее, цикл 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
В вашем случае вы бы "транспонировали" массив, меняя местами внутренние и внешние циклы.
ПРИМЕЧАНИЕ: Я не использую «строку» в контексте Excel (хотя это был бы первый ранг), а под «транспонированием» я не имею в виду использование функции Transpose
в Excel - это будет иметь худшую производительность, чем альтернатива или .