После объявления массива использовать его в VB-коде довольно просто. Как уже объяснялось в начале этой главы, для доступа к элементу массива необходимо указать имя массива, за которым следует значение индекса, заключенное в круглые скобки.
Обращение к элементу массива имеет следующий синтаксис:
Синтаксис
arrayName(validIndex1, [validIndex2]…)
Здесь arrayName — имя массива; validIndex1 — допустимое значение индекса для первого измерения массива; validIndex2 — допустимое значение индекса для второго измерения массива, если таковое имеется. Необходимо предоставлять значение индекса для каждого измерения массива при каждом обращении к какому-либо элементу в массиве.
Например, для двумерного массива необходимо всегда определять два индекса. Допустимым индексом является любая VB-переменная (или VB-выражение), имеющая результатом целое число в диапазоне объявленных измерений массива. Например, допустимым значением индекса для одномерного массива, объявленного с индексами 1–10, может быть любое VB-выражение, имеющее результатом целое число в диапазоне от 1 до 10. Использование меньшего или большего индекса, чем диапазон для определенного измерения в массиве, приводит к тому, что отображается runtime-ошибка.
Следующий фрагмент кода показывает типичное объявление и использование массива.
Dim Factorial(0 To 3) As Double 'объявление массива из 4-х элементов
Factorial(0) = 1 'инициализировать 1-й элемент
Factorial(3) = 1 'инициализировать 4-й элемент
Использование циклов для обработки массивов
Массивы и циклы так же связаны между собой, как поезда и железные дороги. Одни без других в обоих случаях имеют мало смысла. Хотя человек может придумать способ использования поездов без железных дорог (место отдыха для определенной категории граждан) и массивов без циклов, и, в конечном итоге, все решает именно человек, видимо, поезда (за редкими исключениями) должны ходить по железным дорогам, а массивы проще обрабатывать в циклах, по крайней мере, пока у нас нет в языках высокого уровня операций обработки целых массивов.
В следующем фрагменте кода приведены операторы инициализации двумерного массива при помощи вложенного цикла:
Dim I As Integer, J As Integer
Static doMatrixA(1 To 10, 1 To 10) As Double
For I = 1 To 10
For J = 1 To 10
doMatrixA(I, J) = I * 10 + J
Next J
Next I
Чтобы получить тот же результат без цикла, нужно записать оператор присваивания значения элементу массива сто раз.
Вы можете создать Variant-массив и заполнить его другими массивами с различными типами данных. В коде приведенного далее листинга создается массив типа Variant (arr) и два массива типа Integer (intArrA и strArrB). После инициализации массивов intArrA и strArrB первому элементу массива arr присваивается массив intArrA, а второму — strArrB. Остальная часть предназначена для вывода информации с использованием функции MsgBox.
Листинг 10.1. Заполнение массива другими массивами
1 Private Sub Com()
3 Dim intX As Integer, strA As String
5 'массив для хранения массивов
6 Dim arr(2) As Variant '
8 'массивы, которые будут помещены в элементы
9 'массива arr(2)
10 Dim intArrA(5) As Integer, strArrB(5) As String
12 'инициализация массива intArrA
13 For intX = 0 To 4
14 intArrA(intX) = intX
15 Next intX
17 'инициализация массива intArrB
18 strArrB(0) = "h"
19 strArrB(1) = "e"
20 strArrB(2) = "l"
21 strArrB(3) = "l"
22 strArrB(4) = "o"
24 'заполнить массив arr другими массивами
25 arr(1) = intArrA()
26 arr(2) = strArrB()
28 'вывод массива arr на экран
29 For intX = 0 To 4
30 MsgBox arr(1)(intX)
31 Next intX
32 strA = arr(2)(0)
33 For intX = 1 To 4
34 strA = strA + arr(2)(intX)
35 Next intX
36 MsgBox strA
37 End Sub
Использование ReDim с динамическими массивами
Создать динамический массив просто:
1. Объявите массив с помощью ключевого слова (иногда говорят «оператором») Public (если нужен глобальный массив) или Dim в модуле (если нужен массив модульного уровня) или Static или Dim в процедуре (для локального массива). Размерность массива не указывайте:
Dim DynArray()
2. Укажите размерность массива с помощью оператора ReDim:
ReDim DynArray(X + 1)
Оператор ReDim может появляться только в процедуре, поскольку в отличие от Dim и Static является исполняемым оператором во время работы приложения.
При каждом выполнении оператора ReDim значения всех элементов массива, сохраненные ранее, теряются. Visual Basic переустанавливает их в Empty (для Variant-массивов), в нулевые значения (для числовых массивов), в строки нулевой длины (для строковых массивов) или в Nothing (для массивов-объектов).
Конечно, это, скорее, неудобно, чем удобно. Если нам нужно добавить к массиву новый элемент в конец массива, т.е., увеличить размер массива, то нет смысла терять при этом, может быть, с трудом полученные ранее элементы. Здесь снова уместно заметить, что, «к счастью», с помощью ключевого слова Preserve в операторе ReDim можно указать, чтобы система сохранила значения массива, размеры которого изменяются. В следующем примере размер массива увеличивается на единицу от текущего размера с сохранением значений, ранее запомненных в массиве. Текущий размер массива определяется с помощью функции UBound(подробнее об этой функции будет рассказано далее в этой главе):
ReDim Preserve DynArray(UBound(DynArray) + 1)
Применяя Preserve, следует учитывать одно ограничение: вы можете с использованием слова Preserve изменять только последнюю размерность многомерного массива. Если вы измените какую-либо другую размерность, произойдет ошибка времени исполнения.
Передача массива в качестве аргумента при вызове процедуры или функции-процедуры
Visual Basic дает возможность передавать массивы в качестве аргументов процедур и функций. Например, можно написать функцию, получающую числовой массив в качестве аргумента и возвращающую среднее значение всех чисел в массиве как результат функции. Еще один пример: можно написать процедуру, получающую массив в качестве аргумента, передаваемого по ссылке, и сортирующую этот массив. И в одном, и в другом примерах можно написать процедуру или функцию для работы с массивом любого размера — пять элементов, 10 элементов, 100 элементов и так далее.
Общий синтаксис для аргументов-массивов для процедур или функций-процедур такой же, как для любых аргументов процедур или функций-процедур:
Синтаксис
[ByVal | ByRef] arrayname() As type
Как в случае аргументов процедур и функций-процедур, о которых вы уже знаете, ключевое слово ByVal указывает Visual Basic передать аргумент-массив по значению, а ключевое слово ByRef указывает, что следует передавать аргумент-массив по ссылке. Если ByVal и ByRef пропущены, аргумент-массив передается по ссылке.
В этой синтаксической конструкции arrayname() представляет аргумент-массив; можно использовать любой допустимый VB-идентификатор в качестве имени аргумента-массива. Необходимо всегда включать пустые круглые скобки после arrayname; круглые скобки указывают, что этот аргумент является массивом. Параметр type представляет любой допустимый VB-тип (или определенный пользователем тип).
Следующий фрагмент кода показывает, как включать массивы в качестве аргументов для процедур и функций:
Sub ShowText(Lines(), NumLines As Integer)
'получает массив типа Variant, передаваемый по ссылке
…
End Sub
Sub SortList(ByRef List() As String, NumLines As Integer)
'получает массив типа String, передаваемый по ссылке
…
End Sub
Если вы помните, передача аргументов по значению (с помощью ключевого слова ByVal) приводит к тому, что Visual Basic передает копию данных функции-процедуре или процедуре. Не передавайте массивы по значению, если в этом нет особой необходимости — передача больших массивов по значению может быстро исчерпать ресурсы памяти вашего компьютера, приводя к runtime-ошибкам из-за нехватки памяти.
Бизнес-пример
Рассмотрим бизнес-пример, в котором нам необходимо заданный интервал дат разбить на столько интервалов, сколько входит месяцев в исходный интервал. Это может быть необходимо при извлечении данных из базы, в которой определенная информация располагается для каждого месяца отдельно, а в диалоговом окне пользователя допускается указать для обработки любой интервал дат.
А теперь — о деталях примера. Пусть задан интервал: dateBegin–dateEnd (начальная дата – конечная дата). Необходимо получить такие интервалы, в которых начальные и конечные даты будут иметь одинаковые месяцы. Например, интервал 10/01/2002–20/03/2002 состоит из следующих интервалов: 10/01/2002–31/01/2002, 01/02/2002–28/28/2002 и 01/03/2002–20/03/2002. Как видно из примера, количество получаемых интервалов заранее неизвестно и зависит от длины исходного интервала. Поэтому, если исходный интервал можно задавать двумя переменными (dateBegin–dateEnd ), то результат удобно получать в виде двух массивов: массив начальных дат (masBegin) и массив конечных дат (masEnd).
Алгоритм, по которому работает функция, может быть очень простым. Массивам задается размерность 1, поскольку она не может быть равной нулю (при dateBegin <= dateEnd). Первый элемент массива с начальными датами принимает значение начальной даты (переменная-аргументdateBegin). Этим же значением инициализируется временная переменная CurrentDate. Далее в цикле переменная CurrentDate, объявленная как Date, увеличивается на единицу (на один день), при этом проверяется, не произошло ли изменение месяца по сравнению с текущим. Если месяц изменился, меняется значение переменной CurrentMon, сохраняющей текущий месяц, изменяется размерность массивов, сохраняется значение конца интервала для предыдущего месяца. После окончания цикла остается только заполнить значение конца последнего интервала и значение, возвращаемое функцией-процедурой.
Функция-процедура (листинг 10.2) не проверяет правильность данных (dateBegin <= dateEnd); это может проверить вызывающая ее программа. Обратите внимание на то, как при объявлении функции-процедуры (строка 2) указываются аргументы-массивы.
Листинг 10.2. Передача массива в качестве аргумента
1 Function inervals(dateBegin As Date, dateEnd As Date, _
2 masBegin() As Date, masEnd() As Date)
3 ' Возвращает количество интервалов дат, равных количеству
4 ' вошедших в интервал месяцев. Сами диапазоны возвращаются
5 ' в массивах: masBegin, masEnd
7 Dim CurrentDate As Date 'текущая дата
8 Dim masSizeCurrent As Integer 'размерность массивов
9 Dim CurrentMon As Integer 'текущий месяц
11 masSizeCurrent = 1 ‘ размерность массивов не меньше 1
13 'Переопределение размерности массивов:
14 ReDim masBegin(masSizeCurrent)
15 ReDim masEnd(masSizeCurrent)
17 'первый элемент массива с начальной датой:
18 masBegin(0) = dateBegin
20 'Инициализация текущего месяца и даты:
21 CurrentMon = Month(dateBegin)
22 CurrentDate = dateBegin
24 'Цикл с изменением текущих значений даты и месяца
25 Do While CurrentDate < dateEnd
27 CurrentDate = CurrentDate + 1
28 If CurrentMon <> Month(CurrentDate) Then
30 'месяц изменился, меняем текущий месяц
31 CurrentMon = Month(CurrentDate)
33 'элемент массива с конечной датой:
34 masEnd(masSizeCurrent - 1) = CurrentDate – 1
36 'изменить размерность массивов
37 masSizeCurrent = 1 + masSizeCurrent
38 ReDim Preserve masBegin(masSizeCurrent)
39 ReDim Preserve masEnd(masSizeCurrent)
41 'элемент массива с конечной датой:
42 masBegin(masSizeCurrent - 1) = CurrentDate
44 End If
45 Loop
47 'последний элемент массива с конечной датой:
48 masEnd(masSizeCurrent - 1) = dateEnd
50 'значение, возвращаемой функцией
51 inervals = masSizeCurrent
53 End Function
Для тестирования функции intervals можно использовать следующую программу. Заметьте, что в исходных датах необязательно должен быть один и тот же год. До вызова функции необходимо объявить рабочие массивы, имена которых (как ссылки) должны быть переданы в функцию.
Листинг 10.3. Тестирование функции inervals
1 Private Sub Command1_Click()
2 Dim masBegin() As Date, masEnd() As Date
3 Dim dateBegin As Date, dateEnd As Date
5 'считать данные из элементов управления в переменные:
Для контроля за размерами массивов переменной размерности можно использовать одну или пару переменных для сохранения минимального и максимального индекса массива. Если для отслеживания верхнего и нижнего пределов индексов массива пользователь полагается на собственные переменные, то он полностью отвечает за обновление и точность этих переменных. В противном случае процедуры не будут работать правильно — будет обрабатываться меньше элементов, чем в действительности содержит массив, или будет получено сообщение об ошибке при попытке доступа к массиву с индексами, выходящими за фактический размер массива. Такие программные ошибки бывает трудно отслеживать.
Visual Basic имеет две функции, которые освобождают пользователя от необходимости вручную отслеживать верхний и нижний пределы массива — функции LBound и UBound. Эти функции возвращают нижнее и верхнее граничные значения индексов массива переменной размерности.
Функции LBound и UBound имеют следующий синтаксис:
Синтаксис
LBound(arrayName [, dimension])
UBound(arrayName [, dimension])
Функция LBound возвращает первый индекс массива, представленного с помощью arrayName. Функция UBound возвращает наибольший индекс массива, представленного с помощью arrayName. Аргумент dimension представляет целое число, определяющее измерение массива, для которого необходимо получить нижний или верхний предел. Если опустить dimension, возвращается предел для первого измерения массива.
Следующие фрагменты кода являются примерами использования функций LBound и Ubound:
Dim A(3 To 9) As String
For I = LBound(A) To UBound(A)
A(I) = String(10, 65 + I)
Next I
Dim aMatrix(1 To 365, 1990 To 1999)
For i = LBound(aMatrix, 1) To UBound(aMatrix, 1)
For j = LBound(aMatrix, 2) To UBound(aMatrix, 2)
Matrix(i, j) = Rnd
Next j
Next i
Заметьте, что во втором примере функции LBound и UBound используют необязательный аргумент dimension. Внешний цикл For…Next выполняется столько раз, сколько элементов имеется в первом измерении массива aMatrix, тогда как внутренний цикл For…Next выполняется столько раз, сколько элементов имеется во втором измерении массива aMatrix.
Использование Erase для очистки или удаления массивов
Visual Basic имеет особый оператор Erase, позволяющий выполнять одну из двух задач в зависимости от того, каким массивом манипулирует пользователь — переменной размерности или фиксированной. В случае массивов фиксированных размеров Erase позволяет очищать все элементы массива, в основном переустанавливая массив в то же самое состояние, какое он имел, когда Visual Basic создавал его в оперативной памяти. В случае динамических массивов Erase позволяет полностью удалять массив и его содержимое из оперативной памяти.
Когда элементы массива заполнены, данные в массиве (фиксированных размеров или переменной длины) остаются до тех пор, пока пользователь не присвоит новые значения элементам массива или пока Visual Basic не освободится от массива. (Массивы следуют тем же правилам области действия, что и любая другая переменная в Visual Basic)
При некоторых обстоятельствах может понадобиться очистить все значения в массиве, устанавливая числовые значения на 0, строковые значения на пустые строки и так далее. Обычно следует использовать цикл For…Next или For…Each для установки всех элементов в массиве на определенное значение. Следующие фрагменты кода показывают оба цикла For…Next и For…Each, используемые для инициализации всех значений в числовом массиве на 0:
For k = LBound(NumArray) To UBound(NumArray)
NumArray(k) = 0
Next k
For Each Num In NumArray
Num = 0
Next Num
Первый фрагмент кода использует цикл For…Next и функции LBound и UBound для прохождения в цикле по всем элементам массива; второй фрагмент кода использует For…Each для прохождения в цикле по каждому элементу массива, также устанавливая каждый элемент на 0. Хотя эти циклы довольно короткие, можно выполнить ту же самую задачу для массива фиксированной размерности даже более эффективно с помощью единственного оператора Erase:
Erase NumArray
Массивы «имеют тенденцию» занимать относительно большой объем оперативной памяти — например, массив из 20 элементов занимает такой же объем памяти, как 20 отдельных переменных одного и того же типа. Поскольку массивам требуется так много памяти, следует удалять динамические массивы из оперативной памяти, когда они фактически не используются.
Visual Basic удаляет из памяти массивы, объявляемые локально в процедуре (так же, как и любые другие локальные переменные), каждый раз, когда процедура прекращает выполняться. Однако массивы, объявляемые на модульном уровне, существуют, пока любая процедура в этом модуле выполняется. Если программа большая, вам может понадобиться восстановить ресурс памяти, используемой динамическими массивами модульного уровня. Оператор Erase позволяет делать именно это.
Оператор Erase имеет следующий синтаксис:
Синтаксис
Erase array1 [, array2, …]
Здесь array1 и array2 представляют любое допустимое имя VB-массива Можно перечислить столько массивов в операторе Erase, сколько хотите, отделяя каждое имя массива запятой.
Оператор Erase удаляет из памяти динамические массивы, освобождая область памяти, ранее используемую этим массивом. При удалении динамического массива с помощью оператора Erase необходимо повторно создать массив с помощью оператора ReDim перед тем, как можно будет использовать этот определенный динамический массив снова. При попытке доступа к элементам в динамическом массиве, для которого был использован оператор Erase, без его переопределения, Visual Basic отображает сообщение о runtime-ошибке.
Поведение оператора Erase для массивов фиксированных размеров немного сложнее и зависит от конкретного типа элементов массива. В следующей таблице описывается действие оператора Erase с массивами фиксированных размеров различных типов:
Тип массива фиксированного размера
Действие оператора Erase
Любой числовой тип
Устанавливает элементы массива на 0.
Любой строковый тип
Устанавливает элементы массива на строку нулевой длины («»); устанавливает строки фиксированной длины как все символы пробелов.
Variant
Устанавливает элементы массива на Empty.
Object
Устанавливает элементы массива на Nothing.
Любой пользовательский тип
Устанавливает каждую переменную в пользовательском типе отдельно: численные типы устанавливаются на 0, строковые — на строки нулевой длины, Variant — на Empty, Object — на Nothing.