- •Создание динамических структур данных
- •Встроенный динамический класс Collection
- •Создание собственных динамических классов
- •Обертывание коллекции vba
- •Несколько слов об api, Win32, dll
- •Вызов функций и оператор Declare
- •Две кодировки ansi и Unicode
- •Два языка: c и vb. Различия при вызове функций
- •Соответствие между простыми типами данных
- •Структуры языка c и тип, определенный пользователем, в языке vba
- •Об описателях языка c и объектах Windows
- •Void функции языка c
- •Вызов аргументов по ссылке ByRef и по значению ByVal
- •Строковые аргументы при вызове функций Win32 api
- •Примеры работы с Win32 api функциями
- •Работа с окнами
- •Характеристики окружения
- •Вызов функций Win32 api, работающих в Unicode кодировке
- •Обработка ошибок, возникающих при вызове функций Win32 api
- •Функции api и вызов Callback функций
- •Функции высших порядков и конструкция AddressOf
- •Функции перечисления Win32 api
- •Функция EnumWindows
- •Еще один пример работы с функцией EnumWindows
- •Функции Win32 api для работы с таймером
- •Функция SetTimer
- •Функция обратного вызова TimerProc
- •Функция KillTimer
- •Пример создания, работы и удаления таймера
- •Классы как обертка вызовов функций Win32 api
- •Построение класса "ВашТаймер"
- •Использование класса ВашТаймер
- •Операторы
- •Операторы и строки
- •Оператор комментария
- •Присваивание
- •Оператор Let
- •Оператор lSet
- •Оператор rSet
- •Оператор Set
- •Управляющие операторы
- •Условный оператор If Then Else End If
- •Оператор выбора Select Case
- •Цикл For Next
- •Цикл Do...Loop
- •Цикл While...Wend
- •Цикл For Each...Next
- •Работа с каталогами, папками и файлами
- •Изменение текущего диска: оператор ChDrive
- •Изменение текущего каталога (папки): оператор ChDir
- •Создание каталога (папки): оператор MkDir
- •Переименование каталогов (папок) и файлов: оператор Name
- •Удаление каталога (папки): оператор RmDir
- •Установка атрибутов файла: оператор SetAttr
- •Копирование файлов: оператор FileCopy
- •Удаление файлов: оператор Kill
- •Прочие операторы
- •Операции с одним объектом. Оператор With
- •Операции
- •Работа с числовыми данными
- •Математические функции
- •Работа со строками
- •Сравнение строк
- •Сравнение с образцом
- •Основные операции над строками
- •Новые функции для работы со строками
- •Функция InStrRev - поиск последнего вхождения подстроки
- •Функция Replace - замена всех вхождений подстроки
- •Удаление подстроки
- •Разбор строки. Функции Split, Join и Filter
- •Преобразование строки в массив. Функция Split
- •Сборка элементов массива в строку. Функция Join
- •Фильтрация элементов массива. Функция Filter
- •Несколько модификаций встроенных функций
- •Замена, основанная на шаблоне. Функция WildReplace
- •Замена разных символов строки. Функция CharSetReplace
- •Фильтрация, основанная на шаблоне. Функция WildFilter
- •Разбор строки, допускающей разные разделители ее элементов. Функция WildSplit
- •Работа с датами и временем
- •Присваивание значений
- •Встроенные функции для работы с датами
- •Определение текущей даты или времени.
- •Вычисления над датами
- •Функция Timer и хронометраж вычислений
- •Некоторые встроенные функции
- •Функции проверки типов данных
- •Преобразование типов данных
- •Форматирование данных. Функции группы Format
- •Функция Format.
- •Другие функции форматирования
- •Описание и создание процедур
- •Классификация процедур
- •Синтаксис процедур и функций
- •Функции с побочным эффектом
- •Создание процедуры
- •Создание процедур обработки событий
- •Вызовы процедур и функций Вызовы процедур Sub
- •Вызовы функций
- •Использование именованных аргументов
- •Аргументы, являющиеся массивами
- •Конструкция ParamArray
- •Задача о медиане
- •Пользовательские функции, принимающие сложный объект Range
- •Рекурсивные процедуры
- •Деревья поиска
- •Класс TreeNode
- •Класс BinTree
- •Работа со словарем
- •Отладка
- •Написание надежных программ
- •Искусство отладки
- •Средства отладки
- •Панель отладки и команды меню
- •Окна наблюдения
- •Окно локальных переменных - Locals
- •Окно проверки - Immediate
- •Окно контрольных выражений - Watch
Аргументы, являющиеся массивами
Аргументы процедуры могут быть массивами. Процедуре передается имя массива, а размерность массива определяется встроенными функциями LBound и UBound. Приведем пример процедуры, вычисляющей скалярное произведение векторов:
Public Function ScalarProduct(X() As Integer, Y() As Integer) As Integer
'Вычисляет скалярное произведение двух векторов.
'Предполагается, что границы массивов совпадают.
Dim i As Integer, Sum As Integer
Sum = 0
For i = LBound(X) To UBound(X)
Sum = Sum + X(i) * Y(i)
Next i
ScalarProduct = Sum
End Function
Оба параметра процедуры, передаваемые по ссылке, являются массивами, работа с которыми в теле процедуры не представляет затруднений, благодаря тому, что функции LBound и UBound позволяют установить границы массива по любому измерению. Приведем программу, в которой вызывается функция ScalarProduct:
Public Sub TestScalarProduct()
Dim A(1 To 5) As Integer
Dim B(1 To 5) As Integer
Dim C As Variant
Dim Res As Integer
Dim i As Integer
C = Array(1, 2, 3, 4, 5)
For i = 1 To 5
A(i) = C(i - 1)
Next i
C = Array(5, 4, 3, 2, 1)
For i = 1 To 5
B(i) = C(i - 1)
Next i
Res = ScalarProduct(A, B)
Debug.Print Res
End Sub
Конструкция ParamArray
Иногда, когда в процедуру следует передать только один массив, для этой цели можно использовать конструкцию ParamArray. Следующая процедура PosNeg подсчитывает суммы поступлений Positive и расходов Negative, указанные в массиве Sums:
Sub PosNeg(Positive As Integer, Negative As Integer, ParamArray Sums() As Variant)
Dim I As Integer
Positive = 0: Negative = 0
For I = 0 To UBound(Sums()) ' цикл по всем элементам массива
If Sums(I) > 0 Then
Positive = Positive + Sums(I)
Else
Negative = Negative - Sums(I)
End If
Next I
End Sub
Вызов процедуры PosNeg может иметь такой вид:
Public Sub TestPosNeg()
Dim Incomes As Integer, Expences As Integer
PosNeg Incomes, Expences, -20, 100, 25, -44, -23, -60, 120
Debug.Print Incomes, Expences
End Sub
В результате переменная Incomes получит значение 245, а переменная Expences - 147. Заметьте, преимуществом использования массива аргументов ParamArray является возможность непосредственного перечисления элементов массива в момент вызова.
Однако такое использование массива аргументов ParamArray не исчерпывает всех его возможностей. В более сложных ситуациях передаваемые аргументы могут иметь разные типы. Мы приведем сейчас пример, в котором, во-первых, действуют объекты Office 2000, а, во-вторых, используется передача параметров через массив аргументов ParamArray.
Задача о медиане
Для массива M и элемента Cand вычислить разность между числом элементов массива M, больших и меньших Cand.
Это вариация задачи о медиане - "среднем" элементе - массива. Медиану можно определить, например, таким алгоритмом: упорядочив массив, взять элемент, находящийся в середине. Есть и более эффективные алгоритмы. Но мы решили ограничиться более простой задачей - проверкой на "медианность". Заметим: если все элементы массива M различны и число их нечетно, то для медианы искомая в задаче разность равна 0. В общем случае, значение разности является мерой близости параметра Cand к медиане массива M. Но займемся программистскими аспектами этой задачи. У функции, ее реализующей, на входе - массив, а на выходе - скаляр. Мы хотели бы, чтобы эта функция могла вызываться в формулах рабочего листа, а в качестве фактического параметра ей могли быть переданы как объект Range, так и массив Visual Basic. Вот как мы реализовали эту функцию, назвав ее IsMediana:
Public Function IsMediana(M As Variant, Cand As Variant) As Integer
'Дан массив M и элемент Cand. В качестве результата возвращается
'разность между числом элементов массива M, больших и меньших Cand.
Dim i As Integer, j As Integer
Dim Pos As Integer, Neg As Integer
Pos = 0: Neg = 0
'Анализ типа параметра M
If TypeName(M) = "Range" Then
For i = 1 To M.Rows.Count
For j = 1 To M.Columns.Count
If M.Cells(i, j) > Cand Then
Pos = Pos + 1
ElseIf M.Cells(i, j) < Cand Then
Neg = Neg + 1
End If
Next j
Next i
IsMediana = Pos - Neg
ElseIf TypeName(M) = "Variant()" Then
'TypeName is "Variant()"
'Это массив, но не совсем настоящий, для него не определены,
'например, функции границ: LBound, UBound.
Dim Val As Variant
For Each Val In M
If Val > Cand Then
Pos = Pos + 1
ElseIf Val < Cand Then
Neg = Neg + 1
End If
Next Val
IsMediana = Pos - Neg
ElseIf TypeName(M) = "Integer()" Then
'Это настоящий массив целых VBA, для которого
'определены функции границ.
For i = LBound(M) To UBound(M)
If M(i) > Cand Then
Pos = Pos + 1
ElseIf M(i) < Cand Then
Neg = Neg + 1
End If
Next i
IsMediana = Pos - Neg
Else
MsgBox ("При вызове функции:IsMediana(M,Cand)" _
& "- M не является массивом или объектом Range!")
End If
End Function
Пример 9.1. (html, txt)
Прокомментируем работу функции IsMediana.
-
Функция IsMediana может (и будет) вызываться как из процедур VBA, так и из рабочих формул листа Excel. Обратите внимание, она работает с объектами Office 2000 - Range, Cells, Rows и другими.
-
Функции, чьи аргументы имеют универсальный тип Variant, целесообразно строить по принципу разбора случаев. Алгоритм обработки зависит от типа фактического параметра, задаваемого в момент вызова.
-
Стандартная функция TypeName(V) возвращает в качестве результата конкретный тип параметра V.
-
Работа функции IsMediana(M,Cand) начинается с вызова TypeName(M). Далее разбираются четыре возможных случая: M - объект Range, M - массив типа Variant(), M - настоящий целочисленный массив VBA, M имеет любой другой тип.
-
В первом случае функция IsMediana вызывается в формуле рабочего листа Excel и в качестве фактического параметра ей передается объект Range - интервал ячеек этого листа. Следовательно, функция TypeName возвратит строку "Range" в качестве результата. При обработке этого случая организуется цикл по числу строк и столбцов объекта Range, используя свойство Cells этого объекта.
-
Во втором случае обработка основана на том, что функции передан массив типа Variant(). Это возможно, когда при вызове нашей функции в формуле рабочего листа ей передается константа, задающая массив. Ниже мы приведем примеры подобного вызова. Для таких массивов не определены функции границ UBound и LBound. Поэтому обработка в этом случае основана на использовании цикла For Each.
-
В третьем случае функция получает при вызове обычный массив VBA и обработка идет стандартным для массивов способом. Мы приведем пример вызова нашей функции из обычной процедуры VBA, передающей в момент вызова целочисленный массив.
-
В четвертом случае, когда наш параметр не является ни массивом, ни объектом Range, в качестве результата по умолчанию выдается 0. Но выдается также и окно сообщений с предупреждением о возникшей ситуации.
Начнем с того, что приведем процедуру VBA, вызывающую нашу функцию. Вот ее текст:
Public Sub TestIsMediana()
Const Size = 7
Dim Mas(1 To Size) As Integer
Dim Cand As Integer
Dim i As Integer
Dim Res As Integer
'Инициализация массива целыми в интервале 1-20
Debug.Print TypeName(Mas)
Randomize
For i = 1 To Size
Mas(i) = Int(Rnd * 21)
Next i
Cand = Int(Rnd * 21)
Res = IsMediana(Mas, Cand)
Debug.Print "Массив:"
For i = 1 To Size
Debug.Print Mas(i)
Next i
Debug.Print "Кандидат:", Cand
Debug.Print "Результат:", Res
End Sub
Вот результаты ее работы:
Массив:
3 8 14 0 3 8 2
Кандидат: 2
Результат: 4
В данном варианте вызове анализ типа переданного параметра показал, что он является обычным массивом, соответственно был выбран третий вариант обработки, не требующий работы с объектами Office 2000.
Теперь покажем, что эту же функцию можно вызывать в формулах рабочего листа Excel, передавая ей в момент вызова объекты Range в разной форме, а также массивы, заданные константой - массивом. Посмотрим, как это выглядит на экране, и разберем примеры нескольких различных вызовов функции IsMediana в формулах рабочего листа:
увеличить изображение Рис. 9.1. Вызов функции IsMediana в формулах рабочего листа
На рабочем листе мы сформировали два массива: вектор M, вытянутый в виде столбца, и прямоугольную матрицу N. Вектор M записан в ячейках C6:C11, матрица N - в F5:I6. В ячейки E8:E15 мы поместили формулы, вызывающие функцию IsMediana. Они не являются формулами над массивами, несмотря на то, что параметром может быть массив рабочего листа. Важно, что результат - скаляр. Если бы результат, возвращаемый функцией, был массивом, формулу следовало бы вызывать как формулу над массивами. Для скалярного результата это не так.
В двух первых вызовах функции IsMediana (в ячейках E8, E9) передается в качестве параметров имя массива рабочего листа "M" и разные кандидаты: 7 и 8. Они оба годятся на роль медианы этого массива. В следующих двух вызовах проверяются кандидаты на медиану массива N. Как видите, оба кандидата 4 и 3 одинаково близки к медиане. Следующие два вызова в ячейках E12 и E13 демонстрируют возможность указания непосредственно диапазона ячеек в момент вызова, что позволяет, например, работать с частью массива. В следующем вызове вообще не используются в качестве входных данных элементы рабочего листа. Входным параметром M является массив - константа, заключенный в фигурные скобки, а его элементы разделяются символом ";". В этих случаях фактический параметр уже не является объектом Range, а имеет тип массива с элементами Variant. Поэтому и функция IsMediana будет работать по-другому, в отличие от предыдущих вызовов. Разбор случаев в зависимости от результата, возвращаемого функцией TypeName, приведет к выбору второго варианта. Наконец, вызов, записанный в формуле из ячейки E15, демонстрирует случай, когда входной параметр M - обычное число и, следовательно, не является ни объектом Range, ни массивом. Как следствие, разбор случаев в функции IsMediana приводит к четвертому варианту и появлению на экране окна сообщений.