Заполнение динамических массивов VBA

35

Следующий код дает мне ошибку 9 "индекс вне диапазона". Я хотел объявить динамический массив, чтобы размер изменялся по мере добавления к нему элементов. Должен ли я создавать "пятно" в массиве, прежде чем я что-то храню в нем, как в JS?

Sub test_array()
    Dim test() As Integer
    Dim i As Integer
    For i = 0 To 3
        test(i) = 3 + i
    Next i
End Sub
Теги:
arrays

5 ответов

45
Лучший ответ

в вашем цикле for используйте Redim для массива, как здесь:

For i = 0 to 3
  ReDim Preserve test(i)
  test(i) = 3 + i
Next i
  • 17
    Зачем вы делаете это в цикле? ReDim , особенно когда вы добавляете Preserve , является потенциальным ReDim производительности. Вы знаете, сколько раз цикл будет повторяться, поэтому определенно сделайте это вне цикла. Тогда вы изменяете массив только один раз, и вам не нужен Preserve .
  • 12
    @CodyGray Вы абсолютно правы, если окончательный размер массива уже определен при входе в цикл. Помещение Redim внутри цикла будет убийцей производительности. Однако я предположил, что размер массива не определяется при входе в цикл. В противном случае весь образец не имеет смысла вообще ...
Показать ещё 5 комментариев
22

Да, вы ищете оператор ReDim, который динамически выделяет требуемое количество пространства в массиве.

Следующее утверждение

Dim MyArray()

объявляет массив без измерений, поэтому компилятор не знает, насколько он велик и не может хранить что-либо внутри него.

Но вы можете использовать оператор ReDim для изменения размера массива:

ReDim MyArray(0 To 3)

И если вам нужно изменить размер массива при сохранении его содержимого, вы можете использовать ключевое слово Preserve и инструкцию ReDim:

ReDim Preserve MyArray(0 To 3)

Но обратите внимание, что как ReDim, так и особенно ReDim Preserve имеют высокую производительность. Старайтесь избегать этого снова и снова в петле, если это вообще возможно; ваши пользователи будут вам благодарны.


Однако в простом примере, показанном в вашем вопросе (если это не просто выборка), вам вообще не нужно ReDim. Просто объявите массив с явными размерами:

Dim MyArray(0 To 3)
  • 3
    +1, хотя я бы добавил, что нижняя граница может и по моему мнению должна быть указана явно: ReDim MyArray(0 To 3)
  • 1
    @Жан: это хороший совет. Многих людей укусил тот факт, что VB (A) поддерживает нижние границы, отличные от 0. Быть явным - это всегда хорошая практика.
21

Первый плакат, долгое время читатель. Как упомянуто Коди и Бреттом, вы можете уменьшить замедление VBA с разумным использованием Redim Preserve. Бретт предложил Mod сделать это.

Для этого также можно использовать пользовательские Type и Sub. Рассмотрим мой код ниже:

Public Type dsIntArrayType
   eElems() As Integer
   eSize As Integer
End Type

Public Sub PushBackIntArray( _
    ByRef dsIntArray As dsIntArrayType, _
    ByVal intValue As Integer)

    With dsIntArray
    If UBound(.eElems) < (.eSize + 1) Then
        ReDim Preserve .eElems(.eSize * 2 + 1)
    End If
    .eSize = .eSize + 1
    .eElems(.eSize) = intValue
    End With

End Sub

Это вызывает Redim Preserve только при удвоении размера. Переменная-член eSize отслеживает фактический размер данных eElems. Этот подход помог мне улучшить производительность, когда конечная длина массива неизвестна до времени выполнения.

Надеюсь, что это тоже поможет другим.

  • 1
    Вы понимаете, что этот вопрос был задан (и ответил) 18 месяцев назад, верно?
  • 16
    да! хотел предложить альтернативу и для других читателей. я понимаю, что оператор уже принял предыдущий ответ. Благодарю.
Показать ещё 3 комментария
9

В дополнение к полезным комментариям Коди стоит отметить, что время от времени вы не будете знать, насколько велик ваш массив. В этой ситуации два варианта:

  • Создание массива, достаточно большого для обработки всего, что вы думаете, будет брошено на него
  • Разумное использование Redim Preserve

Ниже приведен пример процедуры, которая будет измерять myArray в соответствии с переменной lngSize, а затем добавить дополнительные элементы (равные размеру начального массива) с помощью теста Mod всякий раз, когда верхняя граница будет превышена

Option Base 1

Sub ArraySample()
    Dim myArray() As String
    Dim lngCnt As Long
    Dim lngSize As Long

    lngSize = 10
    ReDim myArray(1 To lngSize)

    For lngCnt = 1 To lngSize*5
        If lngCnt Mod lngSize = 0 Then ReDim Preserve myArray(1 To UBound(myArray) + lngSize)
        myArray(lngCnt) = "I am record number " & lngCnt
    Next
End Sub
8

Я вижу много (всех) сообщений выше, полагающихся на LBound/UBound, называет еще потенциально неинициализированный динамический массив VBA, что вызывает неизбежную смерть приложения...

Ошибочный код:

Dim x As Long Dim arr1() As SomeType ... x = UBound(arr1) 'crashes

Правильный код:

Dim x As Long Dim arr1() As SomeType ... ReDim Preserve arr1(0 To 0) ... x = UBound(arr1)

... т.е. любой код, в котором Dim arr1() следует немедленно с помощью LBound(arr1)/UBound(arr1) вызовов без ReDim arr1(...) между ними, происходит сбой. Круговое движение должно использовать On Error Resume Next и проверить Err.Number сразу после вызова LBound(arr1)/UBound(arr1) - оно должно быть 0, если массив инициализирован, иначе отличный от нуля. Поскольку существует некоторое ошибочное поведение VBA, необходима дальнейшая проверка пределов массива. Подробное объяснение может быть прочитано на веб-сайте Чипа Пирсона (который следует отметить как сокровище человечества мудрости VBA...)

Хех, это мой первый пост, считаю, что это разборчиво.

  • 0
    Этот комментарий, все закодированные выше, полагаясь на ubound / lbound, неверен

Ещё вопросы

Сообщество Overcoder
Наверх
Меню