Judged entirely on its own merits, the implementation of arrays in VBA is neither significantly better nor worse than in most other scripting languages. They are, however, idioscyncratic in ways that seem specifically designed to confuse visitors - rather like the Paris Metro. Flicking back and forth between C#, C++, Java and VBA, I often catch myself using square brackets instead of parentheses or trying to add values dynamically and suffering the wrath of the debugger as a result. Here, then, for my reference as much as for anyone else, is a quick cheat-sheet for the use of arrays in VBA.
Defining arrays:
Defining an array is the same as defining any other variable, except that you include the dimensions of the array in parentheses:Dim single_word As String 'This is a string variable Dim array_of_ten_words(10) As String 'This is an array of stringsTo write to or write from the array, you can use it as you would any other variable, provided that you specify the index in parentheses:
array_of_ten_words(1) = "one" array_of_ten_words(5) = "five" array_of_ten_words(10) = "ten" Debug.Print array_of_ten_words(5) 'Outputs "five"If you try to read from an index that has not been initialized (that is, an index to which no value has been assigned), you will receive a different value depending on the type of the array:
Dim array_of_ten_words(10) As String Debug.Print array_of_ten_words(10) 'Returns an empty string ("") Dim array_of_ten_ints(10) As Integer Debug.Print array_of_ten_ints(10) 'Returns 0 Dim array_of_ten_bools(10) As Boolean Debug.Print array_of_ten_bools(10) 'Returns falseIf you try to initialize or read an index that exceeds the dimensions, you will receive Run-time error '9': Subscript out of range:
Dim array_of_ten_words(10) As String array_of_ten_words(11) = "eleven" 'Fails with error 9 Debug.Print array_of_ten_words(11) 'Also fails with error 9It's worth remembering that in VBA arrays are by default zero-based, which means that their first index is 0. Therefore, our array_of_ten_words actually contains eleven strings: 0 to 10:
Dim array_of_ten_words(10) As String array_of_ten_words(0) = "zero" Debug.Print array_of_ten_words(0) 'Returns "zero"It is good practice, though unnecessary, to specify the lower bound of your arrays. In blatant defiance of Dijkstra's instructions, dimension specifications in VBA are inclusive:
Dim array_of_five_words(5 to 10) As String array_of_five_words(5) = "five" array_of_five_words(10) = "ten" array_of_five_words(4) = "four" 'This line will fail with error 9Thus, defining an array with a single subscript is just shorthand for specifying 0 as the lower bound:
Dim array_of_ten_words(10) As String 'Is functionally identical to Dim array_of_ten_words(0 to 10) As StringIf you want to get the upper and lower bounds of an array, you can use the UBound and LBound functions:
Dim array_of_five_words(5 to 10) As String Debug.Print LBound(array_of_five_words) 'Returns 5 Debug.Print UBound(array_of_five_words) 'Returns 10
Dynamic arrays:
So far we have seen only fixed-length arrays - once their dimensions have been set, they cannot be changed. However, you can also create dynamic arrays, the dimensions of which can be modified throughout your procedure. Creating a dynamic array is simple - don't include dimensions during definition:Dim dynamic_array() As StringAt this point the dynamic array is fairly useless, as read or write calls to any of its indexes will return errors. To use the array, you must assign it dimensions using the ReDim command:
Dim dynamic_array() As String ReDim dynamic_array(5 to 10) As String 'dynamic_array can now be used as a normal arrayWhen re-dimensioning a dynamic array the same rules apply as when defining a fixed-size array. Note that you cannot re-dimension an array that was originally defined as a fixed-size array:
Dim fixed_array(10) As String ReDim fixed_array(20) As String 'This returns an error: "Array Already Dimensioned"While that error message implies that you can't re-dimension an array more than once, you can in fact re-dimension a dynamic array as many times as you like; it is only fixed-size arrays that you cannot re-dimension. When you re-dimension a dynamic array, all the data stored in that array will be lost, and the array is effectively un-initialized. You can use this behaviour as a quick way to "wipe" an array:
ReDim dynamic_array(LBound(dynamic_array) To UBound(dynamic_array))Being a keen-eyed and sharp-witted reader (or indeed a sharp-eyed and keen-witted one; for all practical purposes such readers are interchangeable), you will have noticed that I did not include the type (As String) in that last line. This is because VBA is eager to please and knows how you defined it the first time round. You can reaffirm its type if you like (though you don't need to), but don't get carried away and try and change it. If you do something like this:
Dim dynamic_array() As String ReDim dynamic_array(10) As Integerthen you'll receive a compile error: Can't change data types of array elements, muggins. If you need to have multiple data types in the same array you can simply omit the type declaration:
Dim mixed_array(10) mixedarray(1) = "string" mixedarray(2) = 4 mixedarray(3) = false 'And so onBut back to ReDim. If you would rather that all your lovely data not be lost when you re-dimension a dynamic array, you can use the Preserve keyword to instruct VBA to prevent records from being un-initialized:
Dim dynamic_array() As String ReDim dynamic_array(5) dynamic_array(1) = "one" ReDim Preserve dynamic_array(10) Debug.Print dynamic_array(1) 'Returns "one"Preserve is a very useful modifier, not least because it allows us to append items to an array without having to go through the hassle of looping out the contents first:
Dim no_more() As String ReDim no_more(2) As String no_more(1) = "Blandford Forum" no_more(2) = "Mortehoe" ReDim Preserve no_more(UBound(no_more) + 1) 'Increase the size of the array by 1 no_more(UBound(no_more)) = "Midsomer Norton" 'Add an entry to the last index in the array
Dim dynamic_array() As String ReDim dynamic_array(5 to 10) ReDim Preserve dynamic_array(5 to 11) 'This works ReDim Preserve dynamic_array(5 to 8) 'And so does this ReDim Preserve dynamic_array(6 to 10) 'But this will throw an error ReDim Preserve dynamic_array(4 to 10) 'As will thisTo be honest I don't think I've ever had occasion to change the lower bound of an array, but I'm sure it must be done from time to time.
One final point about bounds. I mentioned earlier that by default VBA arrays are zero-based; that is, a an array that is defined without an explicitly stated lower bound is assumed to have a lower bound of 0. If you wish to change the default lower bound to 1 because you're on holiday from MATLAB, you can do so by using the Option Base command. As with other VBA Option commands, this must be stated at the start of a module before any procedures. Its only argument is the default base, which can be either 1 or 0:
Option Base 0 Sub test() Dim array(10) As String Debug.Print LBound(array) 'Returns 0 End Sub 'In another module: Option Base 1 Sub anotherTest() Dim array(10) As String Debug.Print LBound(array) 'Returns 1 End Sub
Multi-dimensional arrays:
For the most part, you can use multi-dimensional arrays in exactly the same way you can the single-dimensional arrays we've seen so far:Dim 1D_array(10) As String Dim 2D_array(10, 5) As String Dim 3D_array(10, 5, 4 to 8) As String 'And so onJust separate your dimensions by commas and you're good to go. You can even mix dimensions with explicitly-stated lower bounds and those without, as can be seen in the 3D_array example above. There are, however, two features of arrays that act slightly differently when dealing with multiple dimensions. First, you can't use ReDim to change the number of dimensions if you're using Preserve:
Dim array() ReDim array(4) 'This works, as we already know ReDim array(4, 10, 5 to 8) 'This works too ReDim array(3, 5) 'You can reduce the number of dimensions, no problem ReDim Preserve array(3) 'But this will Error #9 ReDim Preserve array(3, 5, 6) 'And so will thisSecond, if you want to use UBound or LBound, you'll need to specify the dimension to which you're referring (which is perfectly reasonable). If you don't, it will return the bound of the first dimension:
Dim array(4, 10) Debug.Print UBound(array) 'Outputs 4 Debug.Print UBound(array, 1) 'Also outputs 4 Debug.Print UBound(array, 2) 'Outputs 10 Debug.Print UBound(array, 3) 'Returns Error #9Now, being a normal, rational person (or as normal and rational as any person is who chooses to read an article about VBA arrays), you may be wondering what function returns the number of dimensions of an array. Is it .Rank, perhaps? NumDims? No. Astonishingly, there is no native VBA function this at all. This is acknowledged by Microsoft in a Retired Knowledge Base article which recommends looping through the dimensions until you hit an error. If you're really keen on showing off, it is possible to parse the array in binary and deduce its dimensions using kernel32.dll, but to be honest unless you have tens of thousands of dimensions it's almost certainly faster, and always-certainly simpler to use the error method, which I have re-written as a function below:
Function numDimensions(testArray as Variant) As Long Dim i As Long On Error GoTo ErrHandler For i = 1 to 60000 'According to KB this is the maximum number of dimensions an array can have
'Do something, anything, to trigger an error
'Trying to read the bounds of a non-existent dimension should do it: ErrorCheck = LBound(testArray, i) Next i ErrHandler: numDimensions = i - 1 End Function
0 comments:
Post a Comment