Тверской государственный университет
Опубликован: 13.09.2006 | Доступ: свободный | Студентов: 3491 / 369 | Оценка: 4.65 / 4.29 | Длительность: 30:37:00
Специальности: Программист, Менеджер
Лекция 6:

Семейство классов и наследование интерфейсов. Динамические классы

< Лекция 5 || Лекция 6: 123456 || Лекция 7 >

Создание динамических структур данных

Говоря об объектах, мы не раз отмечали, что необходимо уметь работать не только с отдельными объектами, но и группами этих объектов. Конечно, можно во многих случаях использовать массив для представления группы объектов. Однако для решения многих, возникающих в программировании задач, необходимы более гибкие динамические структуры данных, позволяющие организовать сложные связи между элементами таких структур. Примеры таких структур хорошо известны, - это, например, списки, линейные и нелинейные, стеки, деки и очереди, деревья бинарные и сбалансированные. В современных языках программирования некоторые из таких структур стали такой же частью языка, как и массивы. Реализуется такая динамическая структура чаще всего в виде класса. Прекрасным примером является реализация самой системы Office 2000. Как мы не раз говорили, Office 2000 можно рассматривать как семейство классов объектов. Так вот, почти для каждого класса объектов, существует и класс, задающий коллекцию этих объектов. Классы - коллекции составляют почти половину классов Office 2000. Все они устроены похожим образом и имеют некоторый стандартный набор методов, позволяющих удалить или добавить новый элемент в коллекцию, получить элемент из коллекции, зная его порядковый номер или ключ элемента. Некоторые коллекции имеют специфические свойства и поведение, обусловленное спецификой самих элементов, хранящихся в коллекции. В ряде случаев, коллекции могут допускать хранение элементов разных классов. Обилие коллекций в Office 2000 первоначально раздражает, поскольку хотелось бы иметь единый класс со свойствами и методами, не зависящими от природы элементов, составляющих коллекцию. Но со временем ко всему привыкаешь и начинаешь принимать как должное, что у каждого класса объектов своя коллекция. Благодаря подсказкам по ходу написания программы и легко доступной справочной системе, не требуется держать в памяти специфику каждой коллекции.

Частью языка VBA является класс Collection. Он существенно облегчает работу с динамическими структурами данных во многих типичных ситуациях. Он позволяет хранить в коллекции данные разных типов и имеет хорошо продуманные свойства и методы. Не менее важно и то, что VBA позволяет создавать и собственные динамические структуры данных сколь угодно сложно организованные. К их рассмотрению мы сейчас и приступаем.

Встроенный динамический класс Collection

Коллекция в VBA - упорядоченная совокупность элементов, вообще говоря, разного типа. Этим коллекция отличается от массива, где объединяются только однотипные элементы. Все элементы индексированы, но некоторые могут иметь и ключ, связанный с элементом. Размер коллекции заранее не фиксируется и может динамически изменяться. Любой существующий элемент коллекции может быть удален, при этом не возникает "дыр" - элементы перенумеровываются, и непрерывность последовательности индексов не нарушается. Неприятным следствием этого факта, приводящего иногда к ошибке, является то, что индекс в отличие от ключа не является постоянной характеристикой элемента и может изменяться в процессе работы с коллекцией. Новый элемент может быть добавлен в произвольное место коллекции, - как перед некоторым элементом, так и после любого из элементов.

У класса Collection одно свойство - Count и 3 метода: Add, Item, Remove. Рассмотрим их подробнее:

  1. Свойство Count возвращает число элементов коллекции. Доступно только для чтения, имеет тип возвращаемого значения Long.
  2. Метод Add (item, key, before, after) добавляет элементы в коллекцию. Первый параметр Item является обязательным и задает добавляемый элемент. Параметр key - необязателен, он задается, когда элементу ставится в соответствие ключ. Два последних необязательных параметра уточняют позицию вставки, - задают индекс или ключ элемента, перед или после которого добавляется новый элемент. Только один из этих параметров может быть задан. Если не задан ни один, элемент добавляется в конец коллекции.
  3. Метод Remove(key) удаляет элементы коллекции. Удаляется элемент с заданным ключом: заметьте, это может быть индекс элемента. После удаления происходит перенумерация элементов и уменьшается счетчик Count.
  4. Метод 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.

Приведем теперь результаты отладочной печати:

Число элементов пустой коллекции = 0
Число элементов после 6-и вызовов метода Add = 6
Элементы коллекции:	один	2	три	4	пять	6
Число элементов после двух вызовов метода Remove = 4
Элементы коллекции:	один	2	три	6

Подчеркнем еще раз основные свойства класса Collection:

  1. Класс позволяет объединять в коллекцию элементы разных типов, хотя чаще применяются однотипные коллекции.
  2. Класс объединяет в себе свойства линейного списка, динамического массива и структуры, называемой словарем , или отображением (map).
  3. Это список, поскольку определена операция Add, позволяющая динамически добавлять элементы в конец списка.
  4. Это динамический массив, поскольку все элементы индексированы и к ним возможен прямой доступ по индексу. С другой стороны, размер массива не фиксируется и динамически изменяется при добавлении и удалении элементов.
  5. Это словарь, поскольку добавляемые элементы могут иметь ключ. Прямой доступ к элементам возможен как по индексу, так и по ключу. Под "прямым" здесь понимается доступ, основанный на применении функций расстановки (хэширования). Конкретный вид этих функций, определяющий эффективность поиска, - "секрет фирмы".
  6. В классе определены методы 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.

В процессе работы этой процедуры в диалоговом окне появится сообщение о Шекспире, а в отладочном окне Immediate появятся следующие результаты:

Пушкин                - это Гений!
Булгаков              - это Мастер!
Адам                    Первый Человек           родился           01.01.100 
Ной                     Праведник                родился           01.01.100 
Гомер                   Великий Слепой           родился           01.01.100 
Вильям                  Шекспир                  родился           23.04.1564 
Александр               Пушкин                   родился           06.06.1799 
Михаил                  Булгаков                 родился           23.01.1891

При создании коллекции мы использовали все три возможности добавления элементов: с заданным ключом, с заданным индексом и в конец списка. Доступ к элементам осуществлялся либо по индексу, либо по ключу. Так что наш пример демонстрирует все достоинства работы с классом Collection. Но, говоря о достоинствах, не следует забывать и о недостатках. Заметьте: при работе с коллекцией потребовалось для каждой личности создать собственную переменную, так что, по существу, мы имеем копии всех элементов коллекции в виде переменных. Это связано с тем, что при добавлении элемента в коллекцию не создается копия элемента, а используется ссылка на существующий элемент, так что коллекция не имеет собственной памяти для хранения элементов. Позже мы покажем, как можно преодолеть этот недостаток коллекции, а пока же подчеркнем, что по этой причине и по многим другим следует уметь создавать собственные динамические структуры данных. К их рассмотрению мы и переходим.

Создание собственных динамических классов

VBA допускает создание динамических структур данных: списков, стеков, очередей, деревьев. Хоть мы и говорим о динамической структуре данных, речь фактически идет о создании динамических типов данных (классов), содержащих как данные, так и операции над ними.

Рассмотрим классический пример и создадим линейный односвязный список. Это можно делать по-разному, но одно из самых разумных решений - определить три класса, задающие

  1. информационную часть элемента списка;
  2. структуру элемента списка;
  3. сам список, в том числе и операции, над ним определенные.

Для определенности рассмотрим список, хранящий информацию о "личностях". Тогда можно считать первую часть нашей задачи решенной,- класс Личность уже создан, и мы его просто используем. Теперь создадим класс с именем ЭлементСпискаЛичностей, задающий структуру элемента (запись). Этот класс прост, поскольку элементы линейного односвязного списка состоят из двух полей - информационного и поля указателя соседнего элемента. Никакие методы над этим классом обычно не определяются. Его полное описание:

'Класс ЭлементСпискаЛичностей содержит только два поля
Public Сам As Личность
Public Друг As ЭлементСпискаЛичностей

В определении класса ЭлементСпискаЛичностей свойство Друг является объектом того же класса. VBA допускает такую рекурсию в определении - без нее списковую структуру не организовать.

Осталось определить класс СписокЛичностей, задающий сам список. Главное на этом этапе - спроектировать операции над списком. Мы ограничимся набором традиционных методов:

  1. AddFirst (F As Личность) - добавляет элемент в начало списка;
  2. AddLast (F As Личность) - добавляет элемент в конец списка;
  3. Initialize - конструктор по умолчанию, инициализирует список;
  4. PrintList() - печатает содержимое элементов списка;
  5. 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.

Теперь несколько замечаний по поводу реализации методов:

  1. При добавлении нового элемента в методах AddFirst и AddLast вначале создается новый пустой элемент списка; поскольку в объявлении элемента используется спецификатор New. Создается также новый элемент для информационного поля, куда копируется информация, переданная при вызове методов Add, - здесь пригодился метод CopyPerson класса Личность. Заметьте, что в отличие от класса Collection, создается копия элемента, а не используется ссылка. Поэтому состояние элементов списка не зависит от внешних изменений. Изменять содержимое элементов списка можно только с помощью методов списка. Хотя мы и не спроектировали такие методы, понятно, что это нетрудно сделать.
  2. При печати списка последовательно проходятся все его элементы, для каждого из них вызывается метод PrintPerson, определенный в классе Личность.
  3. Особо остановимся на методе 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 и собственного динамического класса.

< Лекция 5 || Лекция 6: 123456 || Лекция 7 >
полина есенкова
полина есенкова
Дмитрий Вологжин
Дмитрий Вологжин
Добрый день, прошел тесты с 1 по 9, 10 не сдал, стал читать лекцию и всё пройденные тесты с 1 по 9 сбросились, когда захотел пересдать 10 тест.