2012-01-13 8 views
35

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

Sub test_array() 
    Dim test() As Integer 
    Dim i As Integer 
    For i = 0 To 3 
     test(i) = 3 + i 
    Next i 
End Sub 

ответ

47

в вашем для цикла использовать ReDim на массиве, как здесь:

For i = 0 to 3 
    ReDim Preserve test(i) 
    test(i) = 3 + i 
Next i 
+17

Зачем вам это делать * в цикле? 'ReDim', и особенно когда вы добавляете' Preserve', является потенциальным убийцей производительности. Вы знаете, сколько раз цикл будет итерации, поэтому определенно сделайте это за пределами цикла. Затем вы только изменяете размер массива один раз, и вам не нужен «Preserve». –

+9

@CodyGray Вы абсолютно правы, если окончательный размер массива уже определен при вводе цикла. Помещение Redim внутри цикла будет убийцей производительности. Однако я предположил, что размер массива не определяется при входе в цикл. В противном случае весь образец не имеет никакого смысла ... – Fluffi1974

+3

Он должен иметь *, определяемый при вводе цикла. Вы должны определить диапазон цикла. Даже если это переменная, а не константа, как '3', вы все еще указываете верхнюю границу в инструкции' For'. Используйте это, чтобы динамически инициализировать размер массива. –

21

Да, вы ищете 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 до 3)' –

+1

@Jean: Это хороший совет. Многие люди укусаны тем фактом, что VB (A) поддерживает нижние границы, отличные от 0. Явная всегда хорошая практика. –

9

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

  1. Создание массива достаточно большой, чтобы обрабатывать все, что вы думаете, будет выброшено на это
  2. Разумные использование в 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 
20

Первый раз постер, долгое время читатель. Как упомянуто Коди и Бреттом, вы можете уменьшить замедление 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 месяцев назад, правильно? –

+15

да! хотел предложить альтернативу другим читателям. Я понимаю, что op уже принял предыдущий ответ. Благодарю. – a505999

+0

Хорошо, тогда вот +1 для вас, интересная альтернатива! :) –

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, необходима дальнейшая проверка пределов массива. Подробное объяснение может все читать в Chip Pearson's website (который должен отмечаться как Человечеству сокровище VBA мудрости ...)

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

+0

Этот комментарий все кодированный выше, полагающийся на ubound/lbound, неверен – brettdj

Смежные вопросы