- •Создание динамических структур данных
- •Встроенный динамический класс 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
Создание собственных динамических классов
VBA допускает создание динамических структур данных: списков, стеков, очередей, деревьев. Хоть мы и говорим о динамической структуре данных, речь фактически идет о создании динамических типов данных (классов), содержащих как данные, так и операции над ними.
Рассмотрим классический пример и создадим линейный односвязный список. Это можно делать по-разному, но одно из самых разумных решений - определить три класса, задающие
-
информационную часть элемента списка;
-
структуру элемента списка;
-
сам список, в том числе и операции, над ним определенные.
Для определенности рассмотрим список, хранящий информацию о "личностях". Тогда можно считать первую часть нашей задачи решенной,- класс Личность уже создан, и мы его просто используем. Теперь создадим класс с именем ЭлементСпискаЛичностей, задающий структуру элемента (запись). Этот класс прост, поскольку элементы линейного односвязного списка состоят из двух полей - информационного и поля указателя соседнего элемента. Никакие методы над этим классом обычно не определяются. Его полное описание:
'Класс ЭлементСпискаЛичностей содержит только два поля
Public Сам As Личность
Public Друг As ЭлементСпискаЛичностей
В определении класса ЭлементСпискаЛичностей свойство Друг является объектом того же класса. VBA допускает такую рекурсию в определении - без нее списковую структуру не организовать.
Осталось определить класс СписокЛичностей, задающий сам список. Главное на этом этапе - спроектировать операции над списком. Мы ограничимся набором традиционных методов:
-
AddFirst (F As Личность) - добавляет элемент в начало списка;
-
AddLast (F As Личность) - добавляет элемент в конец списка;
-
Initialize - конструктор по умолчанию, инициализирует список;
-
PrintList() - печатает содержимое элементов списка;
-
ClearList() - очищает список.
Методы диктуют и состав данных (свойств) класса СписокЛичностей. Нужны минимум два указателя на начало и конец списка: First и Last. Эти свойства разумно закрыть от внешнего использования. Мы добавим к ним открытую переменную Count, следящую за числом элементов списка. Приведем теперь описание свойств и методов этого класса:
Option Explicit
'Определение класса СписокЛичностей
'Свойства
Private First As ЭлементСпискаЛичностей
Private Last As ЭлементСпискаЛичностей
Public Count As Integer
'Методы
Private Sub Class_Initialize()
Set First = Nothing
Set Last = Nothing
Count = 0
End Sub
Public Sub AddFirst(F As Личность)
Dim Elem As New ЭлементСпискаЛичностей
Dim Info As New Личность
'Создаем копию переменной F. В списке будем использовать копию, а не ссылку.
Info.CopyPerson F
Set Elem.Сам = Info
Set Elem.Друг = First
If First Is Nothing Then
Set Last = Elem
End If
Set First = Elem
Count = Count + 1
End Sub
Public Sub PrintList()
Dim P As ЭлементСпискаЛичностей
Dim Q As Личность
Set P = First
While Not (P Is Nothing)
Set Q = P.Сам
Q.PrintPerson
Set P = P.Друг
Wend
End Sub
Public Sub AddLast(F As Личность)
Dim Elem As New ЭлементСпискаЛичностей
Dim Info As New Личность
'Создаем копию переменной F. В списке будем использовать копию, а не ссылку.
Info.CopyPerson F
Set Elem.Сам = Info
Set Elem.Друг = Nothing
If First Is Nothing Then
Set First = Elem
Else
Set Last.Друг = Elem
End If
Set Last = Elem
Count = Count + 1
End Sub
Public Sub ClearList()
'Попытка освободить память не достигает успеха из-за отсутствия
'соответствующего оператора.
Dim P As ЭлементСпискаЛичностей, R As ЭлементСпискаЛичностей
Dim Q As Личность
Set P = First
While Not (P Is Nothing)
Set Q = P.Сам
'Unload Q
Set R = P
Set P = P.Друг
'Unload R
Wend
'Обнуление указателей
Set First = Nothing
Set Last = Nothing
Count = 0
End Sub
Пример 5.10. (html, txt)
Теперь несколько замечаний по поводу реализации методов:
-
При добавлении нового элемента в методах AddFirst и AddLast вначале создается новый пустой элемент списка; поскольку в объявлении элемента используется спецификатор New. Создается также новый элемент для информационного поля, куда копируется информация, переданная при вызове методов Add, - здесь пригодился метод CopyPerson класса Личность. Заметьте, что в отличие от класса Collection, создается копия элемента, а не используется ссылка. Поэтому состояние элементов списка не зависит от внешних изменений. Изменять содержимое элементов списка можно только с помощью методов списка. Хотя мы и не спроектировали такие методы, понятно, что это нетрудно сделать.
-
При печати списка последовательно проходятся все его элементы, для каждого из них вызывается метод PrintPerson, определенный в классе Личность.
-
Особо остановимся на методе ClearList, в котором можно установить нулевые значения указателей (Nothing), но нельзя освободить память, занятую элементами списка. Мы специально делаем такую попытку, не приводящую к успеху, чтобы заострить Ваше внимание на этом вопросе. В большинстве языков программирования, позволяющих создавать динамические структуры данных, всегда есть пара взаимосвязанных операций над памятью: "Выделить память" и "Освободить память" (New-Delete или, например, Get-Free). Операции эти выполняются динамически, и выделение и освобождение памяти производится по требованию программиста при выполнении программы. В VBA дело обстоит не так,- здесь есть New, но нет Delete. Связано это, конечно, с особенностями VBA, который, не будучи классическим языком компиляторного типа, представляет нечто среднее между компилятором и интерпретатором. Поэтому здесь освобождение памяти происходит в соответствии с обычными для переменных правилами, определяющими их время жизни. Так что в VBA программист не должен заботиться об освобождении памяти, занятой его динамическими структурами, - это делается автоматически без его участия. Так не проходит освобождение динамической памяти с помощью метода Unload, выполняющего операцию освобождения памяти, но над объектами другого рода.
Приведем теперь достаточно простой пример, позволяющий построить список в процессе диалога с пользователем и в конце распечатать данные, хранящиеся в построенном списке:
Public Sub ВводСписка()
'Создание списка в диалоге с пользователем
Dim Личности As New СписокЛичностей
Dim ЭтоЛичность As New Личность
Dim Имя As String, Фамилия As String, Дата As Date
If MsgBox("Начнем создавать список личностей?", vbYesNo) = vbYes Then
Do
ЭтоЛичность.ВашеИмя = InputBox("Введите имя личности")
ЭтоЛичность.ВашаФамилия = InputBox("Введите Фамилию")
ЭтоЛичность.ВашаДатаРождения = InputBox("Введите дату рождения ")
Личности.AddLast ЭтоЛичность
Loop Until MsgBox("Продолжим создавать список личностей?", vbYesNo) = vbNo
Личности.PrintList
End If
End Sub
Мы не станем приводить реализации других динамических структур, поскольку для тех, кто имеет опыт работы с ними, никаких принципиально новых средств языка не привлекается. Если в языке можно построить список, то можно построить и любую другую динамическую структуру. Но на одном вопросе полезно остановиться. Мы постараемся сейчас показать, как можно объединить достоинства двух подходов: встроенного класса Collection и собственного динамического класса.