- •Создание динамических структур данных
- •Встроенный динамический класс 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
На рисунке 5.4 отражен один из моментов проектирования класса. Можно видеть, что в окне кода список объектов содержит объекты Class, Личность и Машина. При выборе объекта Class правый раскрывающийся список покажет список стандартных событий класса, а при выборе объектов Личность и Машина будет раскрываться список наследуемых методов.
увеличить изображение Рис. 5.4. Проектирование класса ВладелецМашины
Определение семейства классов дано и нам осталось продемонстрировать работу с различными объектами этих классов. Главное, что хотелось показать, это совместную работу с объектами разных классов. Начнем с программного текста:
Option Explicit
'Модуль Примеры
Public FriendOne As New Личность
Public FriendTwo As New Личность
Public FriendThree As New Личность
Public carOne As New Машина
Public carTwo As New Машина
Public carThree As New Машина
Public OwnerOne As New ВладелецМашины
Public OwnerTwo As New ВладелецМашины
Public OwnerThree As New ВладелецМашины
Public FOne As New Личности
Public Sub Люди()
'Вызывается конструктор с параметрами
'и происходит знакомство с объектами
FriendOne.InitPerson FN:="Станислав", LN:="Федотов", _
DoB:="21.05.39"
FriendTwo.InitPerson FN:="Катя", LN:="Павлова", _
DoB:="22.03.79"
FriendThree.InitPerson FN:="Остап", LN:="Бендер", DoB:="23.07.1910"
FriendOne.PrintPerson
FriendTwo.PrintPerson
FriendOne.SayWhoIs
FriendTwo.SayWhoIs
'Связывание с двойниками.
'Теперь объекты могут реагировать на события!
FOne.Connect
End Sub
Public Sub Cars()
'Вызывается конструктор с параметрами
carOne.НоваяМашина "Антилопа", "12.12.12", "Неопределенный"
carTwo.НоваяМашина "Москвич", "12.11.98", "Морская волна"
carThree.НоваяМашина "Jeep", "23.05.97", "Orange"
End Sub
Public Sub CarOwners()
OwnerOne.ConnectOwnerAndCar FriendOne, carTwo
OwnerTwo.ConnectOwnerAndCar FriendThree, carOne
OwnerThree.InitCarOwner FN:="Юрий", LN:="Вегера", _
DoB:="21.08.34", Marka:="Газ69", DB:="20.01.76", Color:="Зеленый"
OwnerOne.PrintOwnerData
OwnerTwo.PrintOwnerData
OwnerThree.PrintOwnerData
End Sub
Public Sub CallEvents()
Dim DoB As Date
'Вызов методов приведет к возникновению событий!
'При замене фамилии возникнет событие ИзменениеФамилии
'Заметьте, не всегда фамилия будет изменена!
FriendOne.ВашаФамилия = "Фидотов"
FriendTwo.ВашаФамилия = "Волконская"
'При попытке узнать дату рождения
'может быть вызван обработчик события ДеньРождения.
DoB = FriendOne.ВашаДатаРождения
Debug.Print DoB
DoB = FriendTwo.ВашаДатаРождения
Debug.Print DoB
FriendOne.PrintPerson
FriendTwo.PrintPerson
'События не наследуются
Set FriendOne = OwnerTwo
'Нельзя связать теперь объект FriendOne с двойником
'FOne.Connect
FriendOne.ВашаФамилия = "Воробьянинов"
FriendOne.PrintPerson
End Sub
Public Sub Группа()
Const SizeGroup = 6
Const SizeGarage = 6
Dim i As Byte
Dim Group(1 To SizeGroup) As Личность
Dim Гараж(1 To SizeGarage) As Машина
Set Group(1) = FriendOne
Set Group(2) = FriendTwo
Set Group(3) = FriendThree
Set Group(4) = OwnerOne
Set Group(5) = OwnerTwo
Set Group(6) = OwnerThree
For i = 1 To SizeGroup
Group(i).SayWhoIs
Next i
Set Гараж(1) = carOne
Set Гараж(2) = carTwo
Set Гараж(3) = carThree
Set Гараж(4) = OwnerOne
Set Гараж(5) = OwnerTwo
Set Гараж(6) = OwnerThree
For i = 1 To SizeGarage
Гараж(i).PrintDataCar
Next i
End Sub
Public Sub ЛюдиИМашины()
Люди
Cars
CarOwners
Группа
PolyMorf FriendTwo
PolyMorf OwnerTwo
End Sub
Public Sub PolyMorf(One As Личность)
One.SayWhoIs
End Sub
Пример 5.6. (html, txt)
Дадим теперь комментарии к этому тексту:
-
В модуле Примеры вначале объявляются по три объекта классов Личность, Машина и ВладелецМашины. В методах Люди, Cars, CarOwners происходит инициализация этих объектов. Некоторые из этих личностей уже встречались в предыдущих примерах, но появился Остап Бендер и его знаменитая машина.
-
Обратите внимание на создание объектов Owner класса ВладелецМашины, они создаются разными конструкторами.
-
Основную работу выполняет метод Группа. Здесь создается массив элементов Group из трех объектов Friend и трех объектов Owner, принадлежащих разным классам. Тем не менее все эти объекты обрабатываются в едином цикле и для них вызывается метод SayWhoIs, который, как мы говорили ранее по-разному работает для объектов класса Личность и класса ВладелецМашины. Так что простой цикл по элементам Group демонстрирует реализацию полиморфизма.
-
В методе Группа создана и продемонстрирована работа с еще одной группой элементов, - массивом Garage. Эту группу составляют объекты класса Машина и объекты класса ВладелецМашины. Обратите внимание, одни и те же объекты Owner входят в обе группы элементов и могут быть обработаны нужным образом.
-
В процессе обработки массива Garage вызывается метод PrintDataCar, который унаследован объектами класса ВладелецМашины.
-
В модуле Примеры приведена полиморфная функция PolyMorf(One As Личность), с формальным параметром родительского класса Личность. Она дважды вызывается в процедуре ЛюдиИМашины с фактическими параметрами разных классов.
-
Процедура ЛюдиИМашины является процедурой, организующей всю работу, она поочередно запускает все перечисленные ранее процедуры. Сама она вызывается в обработчике события Click командной кнопки, встроенной в наш тестовый документ.
В процессе выполнения этой процедуры откроется серия диалоговых окон, где вначале будет сказано, кто такой Федотов и Катя, будет спрошено отчество Вегеры Юрия, затем опять будет рассказано о Федотове, Кате и Бендере, после чего появится информация о владельцах машин разных марок. На последнем этапе опять будет рассказано о Кате и владельце машины "Антилопа". Нам осталось привести результаты отладочной печати, появляющиеся при работе этой процедуры:
Станислав Федотов родился 21.05.39
Катя Павлова родилась 22.03.79
Станислав Федотов родился 21.05.39
владеет машиной:
Марка = Москвич
ДатаВыпуска = 12.11.98
Цвет = Морская волна
Остап Бендер родился 23.07.1910
владеет машиной:
Марка = Антилопа
ДатаВыпуска = 12.12.12
Цвет = Неопределенный
Юрий Алексеевич Вегера родился 21.08.34
владеет машиной:
Марка = Газ69
ДатаВыпуска = 20.01.76
Цвет = Зеленый
Марка = Антилопа
ДатаВыпуска = 12.12.12
Цвет = Неопределенный
Марка = Москвич
ДатаВыпуска = 12.11.98
Цвет = Морская волна
Марка = Jeep
ДатаВыпуска = 23.05.97
Цвет = Orange
Марка = Москвич
ДатаВыпуска = 12.11.98
Цвет = Морская волна
Марка = Антилопа
ДатаВыпуска = 12.12.12
Цвет = Неопределенный
Марка = Газ69
ДатаВыпуска = 20.01.76
Цвет = Зеленый
Пример 5.7. (html, txt)
Как мы уже отмечали, родительский класс Личность реагирует на события. Это не мешает наследовать его интерфейсы, но не означает, что потомки будут наследовать события своего родителя. Чтобы убедиться, что зажигание событий не мешает работе потомков и не влияет на их работу, мы включили процедуру CallEvents. В процессе ее работы возникают события у объектов родительского класса и они правильно обрабатываются. Но, заметьте, после того как объект FriendOne был связан с объектом - потомком FriendOwner, при смене фамилии соответствующее событие уже не возникало, так что ничто не помешало Остапу Бендеру сменить свою фамилию. По ходу работы этой процедуры в появляющихся диалоговых окнах будет сообщено об отказе смены фамилии Федотову и появится поздравление Кате Павловой в связи с замужеством. Приведем результаты отладочной печати при работе этой процедуры:
21.05.39
22.03.79
Станислав Федотов родился 21.05.39
Катя Волконская родилась 22.03.79
Остап Воробьянинов родился 23.07.1910
Этот пример демонстрирует основные возможности работы с семейством классов, появившиеся в связи с введением механизма наследования
Создание динамических структур данных
Говоря об объектах, мы не раз отмечали, что необходимо уметь работать не только с отдельными объектами, но и группами этих объектов. Конечно, можно во многих случаях использовать массив для представления группы объектов. Однако для решения многих, возникающих в программировании задач, необходимы более гибкие динамические структуры данных, позволяющие организовать сложные связи между элементами таких структур. Примеры таких структур хорошо известны, - это, например, списки, линейные и нелинейные, стеки, деки и очереди, деревья бинарные и сбалансированные. В современных языках программирования некоторые из таких структур стали такой же частью языка, как и массивы. Реализуется такая динамическая структура чаще всего в виде класса. Прекрасным примером является реализация самой системы Office 2000. Как мы не раз говорили, Office 2000 можно рассматривать как семейство классов объектов. Так вот, почти для каждого класса объектов, существует и класс, задающий коллекцию этих объектов. Классы - коллекции составляют почти половину классов Office 2000. Все они устроены похожим образом и имеют некоторый стандартный набор методов, позволяющих удалить или добавить новый элемент в коллекцию, получить элемент из коллекции, зная его порядковый номер или ключ элемента. Некоторые коллекции имеют специфические свойства и поведение, обусловленное спецификой самих элементов, хранящихся в коллекции. В ряде случаев, коллекции могут допускать хранение элементов разных классов. Обилие коллекций в Office 2000 первоначально раздражает, поскольку хотелось бы иметь единый класс со свойствами и методами, не зависящими от природы элементов, составляющих коллекцию. Но со временем ко всему привыкаешь и начинаешь принимать как должное, что у каждого класса объектов своя коллекция. Благодаря подсказкам по ходу написания программы и легко доступной справочной системе, не требуется держать в памяти специфику каждой коллекции.
Частью языка VBA является класс Collection. Он существенно облегчает работу с динамическими структурами данных во многих типичных ситуациях. Он позволяет хранить в коллекции данные разных типов и имеет хорошо продуманные свойства и методы. Не менее важно и то, что VBA позволяет создавать и собственные динамические структуры данных сколь угодно сложно организованные. К их рассмотрению мы сейчас и приступаем.
Встроенный динамический класс Collection
Коллекция в VBA - упорядоченная совокупность элементов, вообще говоря, разного типа. Этим коллекция отличается от массива, где объединяются только однотипные элементы. Все элементы индексированы, но некоторые могут иметь и ключ, связанный с элементом. Размер коллекции заранее не фиксируется и может динамически изменяться. Любой существующий элемент коллекции может быть удален, при этом не возникает "дыр" - элементы перенумеровываются, и непрерывность последовательности индексов не нарушается. Неприятным следствием этого факта, приводящего иногда к ошибке, является то, что индекс в отличие от ключа не является постоянной характеристикой элемента и может изменяться в процессе работы с коллекцией. Новый элемент может быть добавлен в произвольное место коллекции, - как перед некоторым элементом, так и после любого из элементов.
У класса Collection одно свойство - Count и 3 метода: Add, Item, Remove. Рассмотрим их подробнее:
-
Свойство Count возвращает число элементов коллекции. Доступно только для чтения, имеет тип возвращаемого значения Long.
-
Метод Add (item, key, before, after) добавляет элементы в коллекцию. Первый параметр Item является обязательным и задает добавляемый элемент. Параметр key - необязателен, он задается, когда элементу ставится в соответствие ключ. Два последних необязательных параметра уточняют позицию вставки, - задают индекс или ключ элемента, перед или после которого добавляется новый элемент. Только один из этих параметров может быть задан. Если не задан ни один, элемент добавляется в конец коллекции.
-
Метод Remove(key) удаляет элементы коллекции. Удаляется элемент с заданным ключом: заметьте, это может быть индекс элемента. После удаления происходит перенумерация элементов и уменьшается счетчик Count.
-
Метод Item(key) возвращает значение элемента списка с заданным ключом. И здесь в роли ключа может выступать обычный индекс элемента.
Обратите внимание на мощность методов класса. Они позволяют реализовать различные классические динамические структуры. Конечно же, совсем просто реализуется обычный односвязный список с возможностью добавления элементов в начало или конец списка. Но можно реализовать и значительно более мощную структуру, называемую словарем. Словарь представляет собой связанную совокупность пар элементов. Первый элемент пары называется ключом, второй - информационным полем. Особенностью словарей является то, что, зная ключ элемента, можно получить доступ к информационному полю. При этом, обратите внимание, в классе Collection на ключ не накладывается никаких ограничений, - это обычная строка. Немаловажно, что есть и альтернативный способ прямого доступа к элементам коллекции по индексу, когда в качестве ключа выступает порядковый номер элемента в коллекции. Таким образом, коллекция соединяет в себе достоинства списков и массивов.
Приведем теперь пример процедуры, подробнее демонстрирующий работу с коллекцией. Наша коллекция будет включать данные двух типов: целочисленные и строковые. Часть элементов будет иметь ключ, остальные - только индекс. Элементы будут добавляться в заданную позицию и удаляться. Отладочная печать позволит проследить за этим процессом:
Sub TestOfCollection()
'Так объявляются объекты (переменные) типа Collection
Dim MyCollection As New Collection
'Объявление обычных локальных переменных
Dim i As Integer
Dim N As Long
'Оператор With позволяет избежать многократного указания имени объекта
With MyCollection
N =.Count
Debug.Print" Число элементов пустой коллекции =", N
' Добавление элементов в конец списка.
'Элементы имеют индексы, но не имеют ключа.
.Add (2)
.Add (4)
.Add (6)
'Добавление нечетных элементов на свои места.
'Заметьте, как указывается позиция
'добавления c использованием параметров - before и after
' Добавляемые элементы имеют строковый тип и обладают ключом
.Add" один"," first", 1 ' before (перед первым элементом)
.Add" три"," third",, 2 'after (после второго)
.Add" пять"," fifth",, 4
N =.Count
Debug.Print" Число элементов после 6-и вызовов метода Add", N
Debug.Print" Элементы коллекции:"
' Отладочная печать созданной коллекции из шести элементов.
For i = 1 To MyCollection.Count
Debug.Print MyCollection(i)
Next
' Удаление 4-го и 5-го элементов по заданному индексу и ключу.
.Remove 4
.Remove" fifth"
N =.Count
Debug.Print" Число элементов после двух вызовов метода Remove=", N
Debug.Print" Элементы коллекции:"
'И снова печать коллекции, в которой теперь четыре элемента.
For i = 1 To MyCollection.Count
Debug.Print MyCollection(i)
Next
End With
End Sub
Пример 5.8. (html, txt)
Приведем теперь результаты отладочной печати:
Число элементов пустой коллекции = 0
Число элементов после 6-и вызовов метода Add = 6
Элементы коллекции: один 2 три 4 пять 6
Число элементов после двух вызовов метода Remove = 4
Элементы коллекции: один 2 три 6
Подчеркнем еще раз основные свойства класса Collection:
-
Класс позволяет объединять в коллекцию элементы разных типов, хотя чаще применяются однотипные коллекции.
-
Класс объединяет в себе свойства линейного списка, динамического массива и структуры, называемой словарем , или отображением (map).
-
Это список, поскольку определена операция Add, позволяющая динамически добавлять элементы в конец списка.
-
Это динамический массив, поскольку все элементы индексированы и к ним возможен прямой доступ по индексу. С другой стороны, размер массива не фиксируется и динамически изменяется при добавлении и удалении элементов.
-
Это словарь, поскольку добавляемые элементы могут иметь ключ. Прямой доступ к элементам возможен как по индексу, так и по ключу. Под "прямым" здесь понимается доступ, основанный на применении функций расстановки (хэширования). Конкретный вид этих функций, определяющий эффективность поиска, - "секрет фирмы".
-
В классе определены методы Add, Item, Remove и свойство Count.
Элементами коллекции могут быть данные любых типов, встроенных в VBA. Не менее важно, что допускается строить коллекцию из объектов, классы которых определены программистом. В предыдущих примерах этой лекции мы использовали для построения групп массивы только по той причине, что еще не ввели понятие коллекции. Более разумно было бы использование коллекций при работе с личностями, владельцами машин и самими машинами. Построим пример, в котором действует коллекция личностей:
Public Sub Collection()
'Создание и работа с коллекцией личностей
Dim Личности As New Collection
'Работа с коллекцией, как со списком
Dim Адам As New Личность
Адам.InitPerson "Адам", "Первый Человек", #1/1/100#
Личности.Add Адам
Dim Ной As New Личность
Ной.InitPerson "Ной", "Праведник", #1/1/100#
Личности.Add Ной
'Работа с коллекцией, как с динамическим массивом
Dim Шекспир As New Личность
Шекспир.InitPerson "Вильям", "Шекспир", #4/23/1564#
Личности.Add Item:=Шекспир, After:=2
Dim Гомер As New Личность
Гомер.InitPerson "Гомер", "Великий Слепой", #1/1/100#
Личности.Add Item:=Гомер, Before:=3
Личности(4).SayWhoIs
'Работа с коллекцией, как со словарем
Dim Пушкин As New Личность
Пушкин.InitPerson "Александр", "Пушкин", #6/6/1799#
Личности.Add Item:=Пушкин, Key:="Гений"
Dim Булгаков As New Личность
Булгаков.InitPerson "Михаил", "Булгаков", #1/23/1891#
Личности.Add Item:=Булгаков, Key:="Мастер"
Debug.Print Личности("Гений").ВашаФамилия, " - это Гений!"
Debug.Print Личности("Мастер").ВашаФамилия, " - это Мастер!"
'Печать всего списка
Dim I As Byte
For I = 1 To Личности.Count
Личности(I).PrintPerson
Next I
End Sub
Пример 5.9. (html, txt)
В процессе работы этой процедуры в диалоговом окне появится сообщение о Шекспире, а в отладочном окне Immediate появятся следующие результаты:
Пушкин - это Гений!
Булгаков - это Мастер!
Адам Первый Человек родился 01.01.100
Ной Праведник родился 01.01.100
Гомер Великий Слепой родился 01.01.100
Вильям Шекспир родился 23.04.1564
Александр Пушкин родился 06.06.1799
Михаил Булгаков родился 23.01.1891
При создании коллекции мы использовали все три возможности добавления элементов: с заданным ключом, с заданным индексом и в конец списка. Доступ к элементам осуществлялся либо по индексу, либо по ключу. Так что наш пример демонстрирует все достоинства работы с классом Collection. Но, говоря о достоинствах, не следует забывать и о недостатках. Заметьте: при работе с коллекцией потребовалось для каждой личности создать собственную переменную, так что, по существу, мы имеем копии всех элементов коллекции в виде переменных. Это связано с тем, что при добавлении элемента в коллекцию не создается копия элемента, а используется ссылка на существующий элемент, так что коллекция не имеет собственной памяти для хранения элементов. Позже мы покажем, как можно преодолеть этот недостаток коллекции, а пока же подчеркнем, что по этой причине и по многим другим следует уметь создавать собственные динамические структуры данных. К их рассмотрению мы и переходим.