Программный код большинства примеров данной лекции можно найти в проектах, доступных для просмотра: doc3One.
Одни из первых вопросов, возникающих при изучении языка программирования: "Как в нем устроена система типов данных? Какие есть простые типы, как создаются сложные, структурные типы, есть ли возможность определения собственных типов,
Вначале договоримся о терминах. Когда мы говорим, что Т - это тип данных, то понимаем, что определение типа Т задает:
Исторически сложилось так, что, при определении типа опускают операции, разрешенные над ним, подразумевая их неявно. Например, определяя тип Integer, говорят, что он задает целые числа в некотором диапазоне. Конечно, где-то позже обязательно будет сказано, какие операции разрешены над целыми. В связи с развитием объектно-ориентированного программирования определение типа стали давать "по-настоящему" и включать в него все три компонента. Правда, теперь такие типы называются классами. Но для нас "класс" - синоним понятия "тип данных".
Типы данных принято разделять на простые и сложные в зависимости от того, как устроены их данные. У простых (
Есть и другие критерии классификации типов. Так, типы разделяются на встроенные типы и типы, определенные программистом (пользователем) . Встроенные типы изначально принадлежат языку программирования и составляют его базис. В основе системы типов любого языка программирования всегда лежит базисная система типов, встроенных в язык. На основе встроенных типов программист может строить собственные, им определенные типы данных. Но способы (правила) создания таких типов являются базисными, встроенными в язык.
Типы данных разделяются также на статические и динамические типы . Для данных
Мощность языка во многом определяется тем, позволяется ли программисту определять динамические и собственные типы данных.
Замечание:
Среди языков программирования выделяются два крайних случая, - строго Variant языка VBA является примером такого обобщающего типа. Бестиповые языки обеспечивают более быстрое исполнение программ, предоставляют программистам большую гибкость, но плата за это высока, она достигается это за счет надежности программ. Одним из первых бестиповых языков был Lisp, где единственным типом данных был список. "Свежим" примером бестипового языка является HTML. "Хорошие" языки программирования, как, например, VBA допускают бестиповость в ограниченных пределах.
Как и всякий порядочный язык, VBA содержит все привычные встроенные простые типы данных: логические, арифметические и строковые:
| Имя типа | Возможные значения | Требуемая память |
|---|---|---|
Boolean |
True, False | 2 байта |
Byte |
0…255 | 1 байт |
Integer |
-32768 …+32767 | 2 байта |
Long |
Примерно: -2000 000 000…+2000 000 000 | 4 байта |
Decimal |
Примерно 30 десятичных цифр. Можно указать число цифр после десятичной точки. | 12 байтов |
Single |
- 3,4E38 …-1,4 E-45 для отрицательных значений 1,4E-45 … 3,4E38 для положительных значений | 4 байта |
Double |
-1,7E308 … -4,9E-324 для отрицательных значений4,9E-324 … 1,7E308 для положительных значений | 8 байтов |
Currency |
Десятичные числа с фиксированной позицией запятой. Возможны 15 цифр до запятой и 4 после. | 8 байтов |
String |
Есть два вида строк: Строки фиксированной длины имеют до 216 символов. Строки переменной длины имеют до 231 символов. | 10 байтов +1 байт на символ в обычной кодировке и 2 байта в кодировке Unicode |
Date |
Даты изменяются в диапазоне от 1 января 100 г. до 31 декабря 9999 г. | 8 байтов |
Object |
Ссылка на объект (указатель) | 4 байта |
Variant |
Универсальный тип, значением которого могут быть данные любого из перечисленных выше типов, объекты, значения NULL и значения ошибок ERROR |
Зависит от контекста, но не менее 16 байтов |
Как видите, в VBA имеется не меньше простых типов, чем в других известных языках программирования. Сделаем несколько замечаний. Переменные типа Decimal нельзя объявлять так, как переменные других типов, - например, оператором Dim. Этот тип является одним из вариантов типа Variant и для его задания используется функция CDec. Тип Currency используется при денежных расчетах.
Замечание:
Иногда, тип Currency используют вместо вещественного типа Single для ускорения расчетов. Вычисления над данными этого типа идут быстрее, чем в случае, когда для проведения вычислений приходится привлекать сопроцессор.
Особо стоит сказать о типе Variant. Такой универсальный тип позволяет превратить язык в бестиповый,- все данные могут иметь один тип ( Variant ). Конечно, это удобно (думать не надо!), а иногда и полезно, но чревато неприятными ошибками, да и память расходуется не эффективно. VBA провоцирует чрезмерное использование этого типа, так как позволяет, объявляя переменную, не указывать ее тип, и тогда по умолчанию он устанавливается как Variant. Правильно всегда объявлять тип переменной, например, пишите Byte, если Вы знаете, что возможные значения не выходят из этого диапазона. Контроль типов поможет избежать ошибок при выходе значения за возможные пределы. Особого внимания требует работа с массивами, - плата за неэффективное использование памяти здесь может быть чрезмерно высока.
Переменные типа Variant могут получать значения любого типа в зависимости от контекста. Кроме того, они могут принимать и некоторые специальные значения:
Empty - переменная не была инициализирована;NULL - данные ошибочны;ERROR - значение содержит код ошибки, который может быть использован для ее обработки в программе;Nothing - переменная типа Object ни на что не ссылается: связь между ней и конкретным объектом прервана или не установлена.В своем развитии Visual Basic прошел сложный путь - от бестипового языка с примитивным способом объявления переменных к Option Explicit (Опция "Явно"), то явное объявление переменных в этом модуле становится обязательным. Мы рекомендуем придерживаться следующей стратегии:
Всегда используйте явное объявление типа. Всегда помещайте объявления переменных в начало блока. Включите в Редакторе VBA в меню Tools на вкладке Editor флажок Require Variable Declaration. В этом случае во всех модулях Ваших проектов будет автоматически появляться оператор Option Explicit. Тем самым, Вы принуждаете себя следить за соблюдением правил хорошего тона в программировании.
При объявлении переменной определяется ее тип и область видимости - область, где имя переменной видимо и, значит, возможен доступ к ее значению. Важно понимать, что переменные можно объявлять на двух уровнях - уровне процедуры и уровне модуля . Для объявления переменных используются операторы Dim, Public, Private и Static. Первый можно использовать на обоих уровнях, Public и Private - на уровне модуля, Static - только на уровне процедуры.
Переменные, объявленные на уровне процедуры, называются локальными по отношению к данной процедуре. Их областью видимости является только та процедура, в которой они объявлены. Переменные уровня модуля являются глобальными . Они объявляются в разделе Declarations, который есть у каждого модуля. Область видимости глобальных переменных может распространяться:
Private ), должны быть объявлены на уровне модуля либо оператором Private либо оператором Dim ;Public ), должны быть объявлены оператором Public.Локальные переменные уровня процедуры могут быть объявлены оператором Static, что делает их статическими. У таких переменных увеличивается время жизни. Обычные локальные переменные рождаются при
Empty (значение, указывающее на отсутствие инициализации) - для типа Variant ;Объявление простых переменных имеет следующий синтаксис:
{Dim | Private | Public | Static }<имя переменной> [ As <имя типа>]
[, <имя переменной> [ As <имя типа>]]…
Вначале идет имя оператора, а потом список объявлений переменных, где роль разделителя играет запятая. Каждое объявление связывает имя переменной с ее типом, заданным конструкцией As. Будьте внимательны, VBA неприятно отличается в этом вопросе от других языков программирования. Здесь, как обычно, одним оператором можно объявить произвольное число переменных, но следует в каждом объявлении указывать конструкцию As, иначе переменным без As будет приписан тип Variant. На наш взгляд, то, что одним As нельзя объявить Variant.
Заметьте, есть и приятное отклонение, - имена переменных в VBA можно задать, используя русский алфавит.
Приведем пример, где действуют модули Father и Mother, в каждом из которых объявлены глобальные (общие и закрытые) переменные. В каждом из модулей объявлены две процедуры, взаимно вызывающие друг друга. Father и Mother - объявления глобальных переменных и процедур:
'Option Explicit Public Fx As Byte, Fz As Integer Private Fy As Integer Public Sub Start() 'Инициализация глобальных переменных Fx = 10: Fy = 11: Fz = 12 Mx = 20: My = 21: Mz = 22 Father1 End Sub Public Sub Father1() Dim Fz As Byte 'Локальная переменная Fx = Fx + 2 Fy = Mx - 2 Fz = 1 Debug.Print "Father1: Fx=", Fx, " Fy =", Fy, "Fz =", Fz 'Вызов процедуры другого модуля Mother1 End Sub
Здесь мы приводим тексты модуля Mother:
'Option Explicit Public Mx As Byte Private My As Integer Public Sub Mother1() 'Объявление статической переменной Static Count As Byte Count = Count + 1 Mx = Mx - 2: Fz = My + 2 Debug.Print "Mother: Статическая переменная Count =", Count 'Вызов процедуры Father другого модуля или заключительной - Finish If Fx < Mx Then Father1 Else Finish End Sub Public Sub Finish() 'Заключительная печать Debug.Print "Finish: Fx = ", Fx, "Fy =", Fy, "Fz =", Fz Debug.Print "Mx =", Mx, "My =", My, "Mz =", Mz 'Объявления разных типов и печать значений, полученных при объявлении Dim B As Byte, I As Integer, L As Long Dim Sng As Single, D As Double, C As Currency Dim SF As String * 7, SV As String, Dat As Date Dim O As Object, V Debug.Print "B =", B, "I=", I, "L=", L Debug.Print "Sng =", Sng, "D =", D; "C=", C Debug.Print "SF =", SF, "SV =", SV, "Dat=", Dat If O Is Nothing Then Debug.Print "Объект не определен" If V = Empty Then Debug.Print "Variant переменные не инициализированы" End Sub
Запустив процедуру Start модуля Father, мы получили такие результаты отладочной печати:
Father1: Fx= 12 Fy = 18 Fz = 1 Mother: Статическая переменная Count = 1 Father1: Fx= 14 Fy = 16 Fz = 1 Mother: Статическая переменная Count = 2 Father1: Fx= 16 Fy = 14 Fz = 1 Mother: Статическая переменная Count = 3 Finish: Fx =16 Fy = Fz = 2 Mx = 14 My = 0 Mz = B = 0 I= 0 L= 0 Sng = 0 D = 0 C= 0 SF = SV = Dat= 0:00:00 Объект не определен Variant переменные не инициализированы
Дадим краткий комментарий:
Father и Mother трижды взаимно вызывают друг друга. Статическая переменная Count подсчитывает число сделанных обращений.Father печатает значения глобальных переменных Fx, Fy и локальной переменной Fz. Заметьте, локальное объявление "сильнее" глобального.Finish печатает заключительные значения переменных Fx, Fy, Fz, Mx, My и Mz. Здесь печатается значение глобальной переменной Fz, а значения двух переменных - Fy и Mz - не определены. Дело в том, что Fy - закрытая переменная модуля Father, а Mz вообще не объявлялась. И все же ошибки не возникает, так как действуют объявления по умолчанию и обе эти переменные считаются объявленными в модуле Mother и по умолчанию имеют тип Variant. Именно здесь кроется причина возможных ошибок, во избежание которых мы и советовали включить оператор Option Excplicit. Будь это сделано, - появились бы предупреждающие сообщения. У нас эти опции написаны, но специально закомментированы для демонстрации эффекта их отсутствия. Если их включить, то ошибка сразу встретится в процедуре Start -- ведь переменные My и Mz не определены в модуле Father.Finish носит самостоятельный характер, - она демонстрирует объявления всех простых типов и печать значений, получаемых переменными при объявлении. Заметьте, как проверяется неопределенность объекта и пустота значений переменных.Настоятельно рекомендуем: не используйте средств, о которых мы сейчас расскажем. Мы и приводим их в первую очередь для того, чтобы дать подобный совет. Все необъявленные явно переменные по умолчанию считаются объявленными и имеют тип Variant. В VBA есть старомодные средства Бэйсика, позволяющие не объявлять переменную явно, но устанавливать ее тип по первому или последнему символу имени переменной. Имена переменных VBA могут заканчиваться специальным символом, позволяющим установить тип этой переменной:
Integer ;Long ;Single ;Double ;Currency ;String.Есть еще одна возможность определения типа по первой букве имени. С этой целью в язык введена серия операторов DefType (по одному на каждый тип DefBool, DefInt и т. д.), определяющих свой диапазон букв для каждого типа. Если первая буква имени необъявленной переменной входит в тот или иной диапазон, ей приписывается соответствующий тип. Эти операторы устанавливаются на уровне модуля и действуют на все его процедуры.
Концевой символ установления типа сильнее, чем DefType, а тот сильнее стандартного "умолчания" Variant. Но все они - архаизмы, не приличествующие современному стилю программирования.
Одними переменными не обойтись, - нужны и константы. Для каждого простого типа есть соответствующие ему константы. Синтаксис построения констант задается так, чтобы по ее значению однозначно можно было приписать ей тип. Поэтому в отличие от переменных константы могут не объявляться, не иметь имени и появляться там, где надо, заданные своими значениями.
Но в VBA можно объявлять именованные константы, задавая в момент объявления значение константы и, возможно, ее тип. Вообще объявление константы во многом напоминает объявление переменной. Однако в этот момент задается значение, которое уже нельзя изменить. Рассмотрим синтаксис оператора Const, используемого
[Public | Private] Const <имя константы> [As type] = <константное выражение>
Вот пример определения классической константы:
Public Const pi As Double = 3.141593
Как и переменные, именованные константы можно объявлять на уровне процедуры или модуля. В первом случае используется только ключевое слово Const, во втором - дополнительно можно задать спецификаторы Public или Private, позволяющие объявить константу общей для всех модулей или закрытой. По умолчанию глобальные константы имеют статус Private. У локальных констант, объявленных в процедурах, областью видимости является процедура. Если не определить тип константы, он будет восстановлен по значению константного выражения. Но иногда лучше это делать самому.
Встроенных констант огромное количество. Есть, например, встроенные константы, связанные с тем или иным приложением, например, Excel или Word. Обычно они имеют соответствующий префикс ( xl, wd и т. д.). Но есть и встроенные константы самого VBA. Они позволяют задавать тип календаря, дни недели, цвет, клавиши и могут применяться в конкретной ситуации, скажем, при работе с окном сообщений. Большинство таких констант начинается с префикса vb, например:
vbWhite, vbSunday, vbKeyEscape, vbOKOnly.
Простейший и самый распространенный структурный тип - массив - упорядоченная совокупность данных одного типа. Порядок на элементах массива задается индексами его элементов. В VBA массивы могут быть одномерными и многомерными. Удивительно, сколь высока допустимая размерность массива, - число измерений доходит до 60. За всю свою многолетнюю практику не припомню случая использования более чем трехмерных массивов.
Конечно, все, что было сказано о глобальных и локальных переменных, общих и закрытых, двух уровней объявления, - относится и к массивам. Появляется только единственное дополнение, связанное с необходимостью указать размерность массива и границы изменения индексов. Поэтому
{Dim | Private | Public | Static }<имя переменной> (<список размерностей>)
[ As <имя типа>]
Синтаксически каждое измерение в списке отделяется запятой и определяется заданием нижней и верхней границы изменения индексов. По историческим причинам в Бейсике нижняя граница была фиксирована и равна 0. Затем ввели специальную опцию OptionBase, позволяющую устанавливать эту границу равную 1 или 0. Подумав еще, разрешили задавать нижнюю и верхнюю границу, причем и та и другая могут быть выражениями при одном ограничении - это должны быть константные выражения, не содержащие переменных. Вот "классическое" объявление одномерного массива и работа с ним:
Public Sub MyArray() Const LowBound As Integer = -5, HighBound As Integer = 5 Dim MyArr(LowBound To HighBound) As Byte Dim I As Integer Debug.Print "Элементы массива MyArr:" For I = LowBound To HighBound MyArr(I) = I + 6 Debug.Print MyArr(I) Next I End Sub
При печати элементов этого массива будут напечатаны числа от 1 до 11 по числу его элементов.
При объявлении массива всегда задавайте нижнюю границу, исходите из того, что граничная пара, задающая размерность, должна удовлетворять синтаксису:
<граничная пара> ::= <константное выражение> To <константное выражение>
Не используйте Option Base . Это ненадежное средство.
Программист Бэдли (не "наш" программист), зная, что массивы положено объявлять, дал следующее объявление:
Dim BadArr(10)
Запись получилась короткой, он написал ее быстро и был горд собой. Могу подтвердить, Бэдли действительно быстро пишет программы, жаль только, что они не всегда правильно работают. Вот еще один пример его работы:
'Option Base 0
Option Base 1
Dim Vector(10)
Dim A1(5), A2(5) As Integer
'Бэдли добавил новое объявление массива Matrix
'При работе с матрицей ему удобно было, чтобы индексы начинались с 1,
'поэтому он изменил опцию Base, отменив предыдущее соглашение
'Ранее работавшая процедура War перестала работать
Dim Matrix(10, 20) As Double
Public Sub War()
Dim i
For i = 0 To 5
A1(i) = i: A2(i) = i + 5
Vector(i) = A1(i): Vector(i + 5) = A2(i)
Next i
End Sub
Каждая строчка этого быстро написанного текста должна быть переписана при хорошем программировании.
Динамические массивы VBA это мощное средство, отсутствующее в таких известных языках как, например, С++ и Паскаль. Массив считается динамическим , если при первоначальном объявлении не указывается его размерность, но она может быть определена и переопределена в последующем оператором ReDim. Размерность определяется динамически в той процедуре и в тот момент, когда она становится фактически известной. Обратите внимание, в этом операторе границы изменения индексов можно задать не только как константы, но и как выражения, зависимые от переменных.
Если затем нужно изменить границы или размерность массива, Вы можете снова задать оператор переопределения ReDim и начать новый цикл работы с массивом. И еще одна "приятность" - можно сохранить все ранее полученные элементы и расширить массив, добавив новые элементы. Для этого надо просто задать ключевое слово при переопределении массива.
Рассмотрим пример. На уровне модуля объявим глобальный Vector:
'Объявление динамического массива Public Vector() As Integer
В момент объявления размерность динамического массива не указывается, соответственно не выделяется память. Все это произойдет позже, в процессе выполнения программы. Вот одна из возможных процедур, работающая с этим массивом Vector. Его размерность в момент работы с ним определяется в диалоге с пользователем. По ходу дела массив расширяется, сохраняя старые значения.
Public Sub DynArray()
'Определяется фактическая размерность массива Vector
Dim N As Byte, I As Byte
N = InputBox("Введите число элементов вектора")
ReDim Vector(1 To N)
For I = 1 To N
Vector(I) = 2 * I + 1
Next I
' Массив расширяется с сохранением ранее вычисленных элементов
ReDim Preserve Vector(1 To 2 * N + 1)
For I = N + 1 To 2 * N + 1
Vector(I) = 2 * I
Next I
'А теперь печать
Debug.Print "Элементы Vector:" Chr(13)
For I = 1 To 2 * N + 1
Debug.Print Vector(I)
Next I
End Sub
При печати элементов этого массива будут напечатаны, как нетрудно понять вначале нечетные числа от 3 до 21, а затем четные от 22 до 42.
Динамические массивы с успехом можно применять там, где необходимы динамические структуры данных, например списки, стеки, очереди.
Наряду с массивами, представляющими объединение элементов одного типа, существует еще один способ создания Т, а затем в нужном месте и в нужном количестве объявлять переменные типа Т, представляющие собой конкретные записи.
Определение каждой записи можно рассматривать, как определение дерева, с корнем которого связана сама запись, а с вершинами - потомками связаны поля этой записи. Поскольку каждое поле может быть элементом произвольного типа, в том числе записью, такие вершины имеют потомков. Благодаря вложенности запись может задавать структуру сколь угодно сложно построенного дерева, у каждой вершины которого может быть произвольное число потомков. Это статическая структура, полностью определяемая в момент объявления записи.
Что же мы имеем в VBA? Формально здесь нет понятия запись или структура, или какого-либо эквивалента. Здесь есть только тип, определенный программистом. Но поскольку он позволяет определять запись, ничего больше и не нужно. Так что записи в VBA есть, но называются они типом, определенным программистом.
При определении типа программист дает ему имя, что позволяет затем определять переменные этого типа обычным способом. Поскольку все элементы совокупности именованы, возможен прямой доступ по имени к каждому из элементов. Ниже мы будем называть переменные такого типа записями, а элементы записи - полями. Такая терминология общепринята и соответствует программистской традиции.
Синтаксис определения типа в VBA достаточно прозрачен:
[Private | Public] Type <имя типа> <имя элемента> [([<размерность массива>])] As <тип элемента> [<имя элемента> [([<размерность массива>])] As <тип элемента>] ... End Type
Определение типа дается на уровне модуля и, если оно является закрытым ( Private ), распространяется на один модуль, а для общих ( Public ) типов - на все. Мы ограничимся довольно стандартным примером, в котором определяются два типа (записи): Fam и Person, где одно из полей Person имеет тип Fam.
'Определение записей - пользовательских типов Type Fam firstName As String lastName As String End Type Type Person Fio As Fam Birthdate As Date End Type
Эти объявления мы поместили в раздел объявлений модуля Father. Вот одна из его процедур, использующих эти типы данных:
Public Sub UserType()
Dim Петров As Person
Dim Козлов As Person
Петров.Fio.firstName = "Петр"
Петров.Fio.lastName = "Петров"
Петров.Birthdate = #1/23/1961#
Козлов.Fio.firstName = Петров.Fio.firstName
Козлов.Fio.lastName = "Козлов"
Козлов.Birthdate = #7/21/1966#
MsgBox (Петров.Fio.firstName " " Петров.Fio.lastName _
" родился " Петров.Birthdate)
MsgBox (Козлов.Fio.firstName " " Козлов.Fio.lastName _
" родился " Козлов.Birthdate)
End Sub
Вот что будет напечатано в результате ее работы:
Петр Петров родился 23.01.61 Петр Козлов родился 21.07.66
VBA старается помочь при работе с записями, предоставляя возможность выбора полей записи из открывающегося списка. Пусть Вас не смущает форма записи дат рождения - при определении константы для дат можно задавать в более человечной форме. Например, здесь мы задавали их как #January 23, 1961# и #21 July, 66#, - они автоматически были преобразованы в формальный вид.
Итак, пусть определен пользовательский тип T и объявлены переменные этого типа, например X и Y. Что можно с ними сделать? Допустимо ли присвоение ( X=Y ) или сравнение ( If (X=Y) Then Fun ), допустимы ли операции над записями? Ответ: нет, за одним исключением - присвоение допустимо, все остальное нет, в том числе и сравнение. Так что можно написать:
Петров = Козлов
Но нельзя написать:
If (Петров = Козлов) Then Debug.Print "Записи идентичны"
Отсутствие разрешенных операций над записями не означает, что с ними нельзя работать. Главное, что определен прямой доступ к полям записи, и этого достаточно, - с полями можно работать, как с переменными. Наш пример ( Sub UserType ) демонстрирует работу с полями записей.
Итак, еще раз напомним, что объявления можно давать на двух уровнях - модуля и процедуры. На уровне модуля раздел объявлений идет первым и автоматически отделяется чертой от раздела методов. На уровне процедуры объявления и операторы могут быть перемешаны, требуется лишь, чтобы объявление переменной предшествовало ее использованию. Хорошим тоном считается и в процедурах иметь два четко выделенных раздела и все объявления размещать в начале процедуры, так чтобы они предшествовали исполняемой части процедуры - разделу операторов.
В классически надежном языке программирования Паскаль, созданном Никласом Виртом, раздел объявлений имел четкую структуру и в свою очередь подразделялся на разделы, содержащие объявления констант, типов, процедур, переменных. В языке VBA такого формального разделения нет, поэтому возможно, что объявление переменных пользовательского типа T предшествует объявлению самого типа. Опять-таки наша рекомендация состоит в том, что программист должен самостоятельно поддерживать структуру раздела объявлений и разделять объявления констант, типов и переменных. Давайте четко выделим основные части раздела объявлений:
Declare.Опции являются указаниями для транслятора. Они могут задаваться только на уровне модуля и должны начинать раздел объявлений. Заметьте, это не пожелание, а синтаксическое требование. Опции задаются ключевым словом Option, после которого идет
Explicit - Об этой опции мы уже подробно говорили. При ее указании транслятор требует, чтобы все переменные модуля были явно описаны. Правильно включить эту опцию раз и навсегда в опциях Редактора VBA.Base - Эта опция имеет два значения: 0 и 1, указывающие нижнюю границу индекса массивов, задаваемую по умолчанию. Правильно не пользоваться этой опцией, а самому и всегда указывать нижнюю границу. К сожалению, нет флажка, который бы заставлял нас следовать этому разумному правилу.Private - Эту опцию, достаточно поместить в один из модулей проекта, обычно, в главный модуль проекта, который неявно всегда выделяется программистом. При ее задании проект делается закрытым и недоступен для других проектов в системе документов.Compare - Опция говорит транслятору, как он должен выполнять сравнение строк в процедурах модуля. Параметр опции может принимать одно из трех возможных значений:{Binary | Text | DataBase}
По умолчанию VBA применяет метод Binary, при котором строки сравниваются по внутренним кодам соответствующих символов. В Windows порядок сортировки определяется кодовой страницей. Вот часть типичного такого порядка:
A < Z < a < z z < А < Я < а < я
Метод сравнения Text основан на сравнении, не чувствительном к регистру, так что при сравнении заглавные и строчные буквы не различаются. Для тех же символов порядок при этом сравнении будет такой:
(A = a) < (Z = z) (Z = z) < (А = а) < (Я = я)
Метод DataBase допустим лишь при работе с Access. Сравнение при этом базируется на порядке, задаваемом локализацией той БД, для которой проводится сравнение.
О том, как объявляются константы, типы и переменные мы говорили достаточно подробно. Сейчас же отметим, что разумно создавать такие разделы явно, отделяя комментариями один раздел от другого. Желательно, чтобы эти разделы шли в описанном нами порядке. Тогда уже объявленные константы будут появляться при описании границ массивов, типы переменных будут предшествовать объявлению самих переменных.
Для констант и типов ключевые слова Const и Type однозначно определяют описываемый объект. Для переменных ключевые слова могут быть разными. Заметим, что для переменных уровня модуля не целесообразно использовать ключевое слово Dim, лучше использовать спецификаторы Public или Private, явно указывающие область действия переменной.
Этот раздел появляется в тех случаях, когда модули проекта используют DLL. Если DLL имеет библиотеку типов TypeLib и она доступна проекту, то нет необходимости описывать компоненты библиотеки, они будут найдены автоматически. Но если TypeLib недоступна или не определена, то в этой ситуации каждая из функций и процедур библиотеки, вызываемая в модуле, должна быть предварительно описана специальным оператором Declare. Вот его синтаксис:
[Public | Private] Declare {Sub | Function} имя Lib "имя_библиотеки" _
[Alias "псевдоним"] [([параметры])] [As возвращаемый_тип]
Здесь указывается имя библиотеки, имя процедуры или функции, возможный псевдоним, параметры и возвращаемое значение для функций. Подробно о DLL, операторе Declare с примерами на эту тему будет рассказано в одной из лекций этой книги.
Есть несколько простых правил, которые следует выполнять, чтобы быть цивилизованным программистом. Заметьте, можно быть умным, грамотным, но хорошо бы еще быть и цивилизованным. Многим из нас, я это отношу и к себе, не хватает цивилизации. Правила, о которых мы сейчас поговорим, касаются оформления текста программ. Большинство из них достаточно просто и по ходу дела мы о них неоднократно говорили. Вот основные:
Сейчас мы поговорим об одном техническом, но практически важном вопросе, - как правильно давать имена константам, переменным и другим объектам в наших программах. Казалось бы, какая разница, - "хоть горшком называй, только в печь не сажай". Это, однако, не так. Имя играет большую роль в понимании программ, и я верю даже в некоторую мистику, с плохо названной переменной всегда случаются какие то истории.
Правило написания имен переменных, которому мы обычно следовали в своих примерах и повседневной практике, состоит в следующем: имя должно отражать содержательный смысл, и состоит из одного или нескольких слитно написанных слов, каждое из которых начинается с большой буквы.
Разработчики от Microsoft рекомендуют придерживаться более строгих правил. Имя должно отражать не только смысл, но и тип переменной и ее область действия. Поэтому имя должно состоять из префикса и собственного имени. Префикс также является составным, две его части отражают область действия и тип переменной. В идущих ниже таблицах показаны возможные значения префикса:
| Первая часть префикса | Область действия |
|---|---|
| g | Global - Весь проект |
| m | Module - Для Private переменных модуля отсутствует |
| p | Procedure - Для локальных переменных |
| Вторая часть префикса | Тип переменной |
|---|---|
| str | String |
| int | Integer |
| byt | Byte |
| lng | Long |
| sng | Single |
| dbl | Double |
| cur | Currency |
| var | Variant |
| obj | Object |
| bln | Boolean |
Вот несколько примеров правильно построенных имен: gstrOneWord, mintNumberOne, strAnswer, curSalary. Также как по значению константы можно восстановить ее тип, по правильно построенным именам можно однозначно восстановить их объявление. Сделаем это в нашем примере:
Public gstrOneWord As String Private mintNumberOne As Integer Dim strAnswer As String Dim curSalary As Currency
Согласно этим же рекомендациям имена констант следует строить только из заглавных букв. Если имя константы состоит из нескольких слов, то для их объединения используется знак подчеркивания, например: MY_DIRECTORY_PATH. Обратите внимание, при построении констант Office 2000 используется префикс, указывающий, какому из приложений принадлежит константа.
| Приложение | Префикс констант |
|---|---|
| Access | ac |
| Excel | xl |
| FrontPage | fp |
| Office | mso |
| OfficeBinder | bind |
| Outlook | ol |
| Power Point | pp |
| Word | wd |
| VBA | vb |
Префиксы следует использовать и при именовании объектов, для форм обычно используется префикс frm, для командных кнопок - cmd и так далее. Следует понимать, что, если уж пользоваться префиксами, то обще употребительными, не следует заниматься самодеятельностью в этом вопросе. Мы закончим призывом, - "будьте цивилизованными".
Программный код большинства примеров данной лекции можно найти в проектах, доступных для просмотра: doc3One.
Одни из первых вопросов, возникающих при изучении языка программирования: "Как в нем устроена система типов данных? Какие есть простые типы, как создаются сложные, структурные типы, есть ли возможность определения собственных типов,
Вначале договоримся о терминах. Когда мы говорим, что Т - это тип данных, то понимаем, что определение типа Т задает:
Исторически сложилось так, что, при определении типа опускают операции, разрешенные над ним, подразумевая их неявно. Например, определяя тип Integer, говорят, что он задает целые числа в некотором диапазоне. Конечно, где-то позже обязательно будет сказано, какие операции разрешены над целыми. В связи с развитием объектно-ориентированного программирования определение типа стали давать "по-настоящему" и включать в него все три компонента. Правда, теперь такие типы называются классами. Но для нас "класс" - синоним понятия "тип данных".
Типы данных принято разделять на простые и сложные в зависимости от того, как устроены их данные. У простых (
Есть и другие критерии классификации типов. Так, типы разделяются на встроенные типы и типы, определенные программистом (пользователем) . Встроенные типы изначально принадлежат языку программирования и составляют его базис. В основе системы типов любого языка программирования всегда лежит базисная система типов, встроенных в язык. На основе встроенных типов программист может строить собственные, им определенные типы данных. Но способы (правила) создания таких типов являются базисными, встроенными в язык.
Типы данных разделяются также на статические и динамические типы . Для данных
Мощность языка во многом определяется тем, позволяется ли программисту определять динамические и собственные типы данных.
Замечание:
Среди языков программирования выделяются два крайних случая, - строго Variant языка VBA является примером такого обобщающего типа. Бестиповые языки обеспечивают более быстрое исполнение программ, предоставляют программистам большую гибкость, но плата за это высока, она достигается это за счет надежности программ. Одним из первых бестиповых языков был Lisp, где единственным типом данных был список. "Свежим" примером бестипового языка является HTML. "Хорошие" языки программирования, как, например, VBA допускают бестиповость в ограниченных пределах.
Как и всякий порядочный язык, VBA содержит все привычные встроенные простые типы данных: логические, арифметические и строковые:
| Имя типа | Возможные значения | Требуемая память |
|---|---|---|
Boolean |
True, False | 2 байта |
Byte |
0…255 | 1 байт |
Integer |
-32768 …+32767 | 2 байта |
Long |
Примерно: -2000 000 000…+2000 000 000 | 4 байта |
Decimal |
Примерно 30 десятичных цифр. Можно указать число цифр после десятичной точки. | 12 байтов |
Single |
- 3,4E38 …-1,4 E-45 для отрицательных значений 1,4E-45 … 3,4E38 для положительных значений | 4 байта |
Double |
-1,7E308 … -4,9E-324 для отрицательных значений4,9E-324 … 1,7E308 для положительных значений | 8 байтов |
Currency |
Десятичные числа с фиксированной позицией запятой. Возможны 15 цифр до запятой и 4 после. | 8 байтов |
String |
Есть два вида строк: Строки фиксированной длины имеют до 216 символов. Строки переменной длины имеют до 231 символов. | 10 байтов +1 байт на символ в обычной кодировке и 2 байта в кодировке Unicode |
Date |
Даты изменяются в диапазоне от 1 января 100 г. до 31 декабря 9999 г. | 8 байтов |
Object |
Ссылка на объект (указатель) | 4 байта |
Variant |
Универсальный тип, значением которого могут быть данные любого из перечисленных выше типов, объекты, значения NULL и значения ошибок ERROR |
Зависит от контекста, но не менее 16 байтов |
Как видите, в VBA имеется не меньше простых типов, чем в других известных языках программирования. Сделаем несколько замечаний. Переменные типа Decimal нельзя объявлять так, как переменные других типов, - например, оператором Dim. Этот тип является одним из вариантов типа Variant и для его задания используется функция CDec. Тип Currency используется при денежных расчетах.
Замечание:
Иногда, тип Currency используют вместо вещественного типа Single для ускорения расчетов. Вычисления над данными этого типа идут быстрее, чем в случае, когда для проведения вычислений приходится привлекать сопроцессор.
Особо стоит сказать о типе Variant. Такой универсальный тип позволяет превратить язык в бестиповый,- все данные могут иметь один тип ( Variant ). Конечно, это удобно (думать не надо!), а иногда и полезно, но чревато неприятными ошибками, да и память расходуется не эффективно. VBA провоцирует чрезмерное использование этого типа, так как позволяет, объявляя переменную, не указывать ее тип, и тогда по умолчанию он устанавливается как Variant. Правильно всегда объявлять тип переменной, например, пишите Byte, если Вы знаете, что возможные значения не выходят из этого диапазона. Контроль типов поможет избежать ошибок при выходе значения за возможные пределы. Особого внимания требует работа с массивами, - плата за неэффективное использование памяти здесь может быть чрезмерно высока.
Переменные типа Variant могут получать значения любого типа в зависимости от контекста. Кроме того, они могут принимать и некоторые специальные значения:
Empty - переменная не была инициализирована;NULL - данные ошибочны;ERROR - значение содержит код ошибки, который может быть использован для ее обработки в программе;Nothing - переменная типа Object ни на что не ссылается: связь между ней и конкретным объектом прервана или не установлена.В своем развитии Visual Basic прошел сложный путь - от бестипового языка с примитивным способом объявления переменных к Option Explicit (Опция "Явно"), то явное объявление переменных в этом модуле становится обязательным. Мы рекомендуем придерживаться следующей стратегии:
Всегда используйте явное объявление типа. Всегда помещайте объявления переменных в начало блока. Включите в Редакторе VBA в меню Tools на вкладке Editor флажок Require Variable Declaration. В этом случае во всех модулях Ваших проектов будет автоматически появляться оператор Option Explicit. Тем самым, Вы принуждаете себя следить за соблюдением правил хорошего тона в программировании.
При объявлении переменной определяется ее тип и область видимости - область, где имя переменной видимо и, значит, возможен доступ к ее значению. Важно понимать, что переменные можно объявлять на двух уровнях - уровне процедуры и уровне модуля . Для объявления переменных используются операторы Dim, Public, Private и Static. Первый можно использовать на обоих уровнях, Public и Private - на уровне модуля, Static - только на уровне процедуры.
Переменные, объявленные на уровне процедуры, называются локальными по отношению к данной процедуре. Их областью видимости является только та процедура, в которой они объявлены. Переменные уровня модуля являются глобальными . Они объявляются в разделе Declarations, который есть у каждого модуля. Область видимости глобальных переменных может распространяться:
Private ), должны быть объявлены на уровне модуля либо оператором Private либо оператором Dim ;Public ), должны быть объявлены оператором Public.Локальные переменные уровня процедуры могут быть объявлены оператором Static, что делает их статическими. У таких переменных увеличивается время жизни. Обычные локальные переменные рождаются при
Empty (значение, указывающее на отсутствие инициализации) - для типа Variant ;Объявление простых переменных имеет следующий синтаксис:
{Dim | Private | Public | Static }<имя переменной> [ As <имя типа>]
[, <имя переменной> [ As <имя типа>]]…
Вначале идет имя оператора, а потом список объявлений переменных, где роль разделителя играет запятая. Каждое объявление связывает имя переменной с ее типом, заданным конструкцией As. Будьте внимательны, VBA неприятно отличается в этом вопросе от других языков программирования. Здесь, как обычно, одним оператором можно объявить произвольное число переменных, но следует в каждом объявлении указывать конструкцию As, иначе переменным без As будет приписан тип Variant. На наш взгляд, то, что одним As нельзя объявить Variant.
Заметьте, есть и приятное отклонение, - имена переменных в VBA можно задать, используя русский алфавит.
Приведем пример, где действуют модули Father и Mother, в каждом из которых объявлены глобальные (общие и закрытые) переменные. В каждом из модулей объявлены две процедуры, взаимно вызывающие друг друга. Father и Mother - объявления глобальных переменных и процедур:
'Option Explicit Public Fx As Byte, Fz As Integer Private Fy As Integer Public Sub Start() 'Инициализация глобальных переменных Fx = 10: Fy = 11: Fz = 12 Mx = 20: My = 21: Mz = 22 Father1 End Sub Public Sub Father1() Dim Fz As Byte 'Локальная переменная Fx = Fx + 2 Fy = Mx - 2 Fz = 1 Debug.Print "Father1: Fx=", Fx, " Fy =", Fy, "Fz =", Fz 'Вызов процедуры другого модуля Mother1 End Sub
Здесь мы приводим тексты модуля Mother:
'Option Explicit Public Mx As Byte Private My As Integer Public Sub Mother1() 'Объявление статической переменной Static Count As Byte Count = Count + 1 Mx = Mx - 2: Fz = My + 2 Debug.Print "Mother: Статическая переменная Count =", Count 'Вызов процедуры Father другого модуля или заключительной - Finish If Fx < Mx Then Father1 Else Finish End Sub Public Sub Finish() 'Заключительная печать Debug.Print "Finish: Fx = ", Fx, "Fy =", Fy, "Fz =", Fz Debug.Print "Mx =", Mx, "My =", My, "Mz =", Mz 'Объявления разных типов и печать значений, полученных при объявлении Dim B As Byte, I As Integer, L As Long Dim Sng As Single, D As Double, C As Currency Dim SF As String * 7, SV As String, Dat As Date Dim O As Object, V Debug.Print "B =", B, "I=", I, "L=", L Debug.Print "Sng =", Sng, "D =", D; "C=", C Debug.Print "SF =", SF, "SV =", SV, "Dat=", Dat If O Is Nothing Then Debug.Print "Объект не определен" If V = Empty Then Debug.Print "Variant переменные не инициализированы" End Sub
Запустив процедуру Start модуля Father, мы получили такие результаты отладочной печати:
Father1: Fx= 12 Fy = 18 Fz = 1 Mother: Статическая переменная Count = 1 Father1: Fx= 14 Fy = 16 Fz = 1 Mother: Статическая переменная Count = 2 Father1: Fx= 16 Fy = 14 Fz = 1 Mother: Статическая переменная Count = 3 Finish: Fx =16 Fy = Fz = 2 Mx = 14 My = 0 Mz = B = 0 I= 0 L= 0 Sng = 0 D = 0 C= 0 SF = SV = Dat= 0:00:00 Объект не определен Variant переменные не инициализированы
Дадим краткий комментарий:
Father и Mother трижды взаимно вызывают друг друга. Статическая переменная Count подсчитывает число сделанных обращений.Father печатает значения глобальных переменных Fx, Fy и локальной переменной Fz. Заметьте, локальное объявление "сильнее" глобального.Finish печатает заключительные значения переменных Fx, Fy, Fz, Mx, My и Mz. Здесь печатается значение глобальной переменной Fz, а значения двух переменных - Fy и Mz - не определены. Дело в том, что Fy - закрытая переменная модуля Father, а Mz вообще не объявлялась. И все же ошибки не возникает, так как действуют объявления по умолчанию и обе эти переменные считаются объявленными в модуле Mother и по умолчанию имеют тип Variant. Именно здесь кроется причина возможных ошибок, во избежание которых мы и советовали включить оператор Option Excplicit. Будь это сделано, - появились бы предупреждающие сообщения. У нас эти опции написаны, но специально закомментированы для демонстрации эффекта их отсутствия. Если их включить, то ошибка сразу встретится в процедуре Start -- ведь переменные My и Mz не определены в модуле Father.Finish носит самостоятельный характер, - она демонстрирует объявления всех простых типов и печать значений, получаемых переменными при объявлении. Заметьте, как проверяется неопределенность объекта и пустота значений переменных.Настоятельно рекомендуем: не используйте средств, о которых мы сейчас расскажем. Мы и приводим их в первую очередь для того, чтобы дать подобный совет. Все необъявленные явно переменные по умолчанию считаются объявленными и имеют тип Variant. В VBA есть старомодные средства Бэйсика, позволяющие не объявлять переменную явно, но устанавливать ее тип по первому или последнему символу имени переменной. Имена переменных VBA могут заканчиваться специальным символом, позволяющим установить тип этой переменной:
Integer ;Long ;Single ;Double ;Currency ;String.Есть еще одна возможность определения типа по первой букве имени. С этой целью в язык введена серия операторов DefType (по одному на каждый тип DefBool, DefInt и т. д.), определяющих свой диапазон букв для каждого типа. Если первая буква имени необъявленной переменной входит в тот или иной диапазон, ей приписывается соответствующий тип. Эти операторы устанавливаются на уровне модуля и действуют на все его процедуры.
Концевой символ установления типа сильнее, чем DefType, а тот сильнее стандартного "умолчания" Variant. Но все они - архаизмы, не приличествующие современному стилю программирования.
Одними переменными не обойтись, - нужны и константы. Для каждого простого типа есть соответствующие ему константы. Синтаксис построения констант задается так, чтобы по ее значению однозначно можно было приписать ей тип. Поэтому в отличие от переменных константы могут не объявляться, не иметь имени и появляться там, где надо, заданные своими значениями.
Но в VBA можно объявлять именованные константы, задавая в момент объявления значение константы и, возможно, ее тип. Вообще объявление константы во многом напоминает объявление переменной. Однако в этот момент задается значение, которое уже нельзя изменить. Рассмотрим синтаксис оператора Const, используемого
[Public | Private] Const <имя константы> [As type] = <константное выражение>
Вот пример определения классической константы:
Public Const pi As Double = 3.141593
Как и переменные, именованные константы можно объявлять на уровне процедуры или модуля. В первом случае используется только ключевое слово Const, во втором - дополнительно можно задать спецификаторы Public или Private, позволяющие объявить константу общей для всех модулей или закрытой. По умолчанию глобальные константы имеют статус Private. У локальных констант, объявленных в процедурах, областью видимости является процедура. Если не определить тип константы, он будет восстановлен по значению константного выражения. Но иногда лучше это делать самому.
Встроенных констант огромное количество. Есть, например, встроенные константы, связанные с тем или иным приложением, например, Excel или Word. Обычно они имеют соответствующий префикс ( xl, wd и т. д.). Но есть и встроенные константы самого VBA. Они позволяют задавать тип календаря, дни недели, цвет, клавиши и могут применяться в конкретной ситуации, скажем, при работе с окном сообщений. Большинство таких констант начинается с префикса vb, например:
vbWhite, vbSunday, vbKeyEscape, vbOKOnly.
Простейший и самый распространенный структурный тип - массив - упорядоченная совокупность данных одного типа. Порядок на элементах массива задается индексами его элементов. В VBA массивы могут быть одномерными и многомерными. Удивительно, сколь высока допустимая размерность массива, - число измерений доходит до 60. За всю свою многолетнюю практику не припомню случая использования более чем трехмерных массивов.
Конечно, все, что было сказано о глобальных и локальных переменных, общих и закрытых, двух уровней объявления, - относится и к массивам. Появляется только единственное дополнение, связанное с необходимостью указать размерность массива и границы изменения индексов. Поэтому
{Dim | Private | Public | Static }<имя переменной> (<список размерностей>)
[ As <имя типа>]
Синтаксически каждое измерение в списке отделяется запятой и определяется заданием нижней и верхней границы изменения индексов. По историческим причинам в Бейсике нижняя граница была фиксирована и равна 0. Затем ввели специальную опцию OptionBase, позволяющую устанавливать эту границу равную 1 или 0. Подумав еще, разрешили задавать нижнюю и верхнюю границу, причем и та и другая могут быть выражениями при одном ограничении - это должны быть константные выражения, не содержащие переменных. Вот "классическое" объявление одномерного массива и работа с ним:
Public Sub MyArray() Const LowBound As Integer = -5, HighBound As Integer = 5 Dim MyArr(LowBound To HighBound) As Byte Dim I As Integer Debug.Print "Элементы массива MyArr:" For I = LowBound To HighBound MyArr(I) = I + 6 Debug.Print MyArr(I) Next I End Sub
При печати элементов этого массива будут напечатаны числа от 1 до 11 по числу его элементов.
При объявлении массива всегда задавайте нижнюю границу, исходите из того, что граничная пара, задающая размерность, должна удовлетворять синтаксису:
<граничная пара> ::= <константное выражение> To <константное выражение>
Не используйте Option Base . Это ненадежное средство.
Программист Бэдли (не "наш" программист), зная, что массивы положено объявлять, дал следующее объявление:
Dim BadArr(10)
Запись получилась короткой, он написал ее быстро и был горд собой. Могу подтвердить, Бэдли действительно быстро пишет программы, жаль только, что они не всегда правильно работают. Вот еще один пример его работы:
'Option Base 0
Option Base 1
Dim Vector(10)
Dim A1(5), A2(5) As Integer
'Бэдли добавил новое объявление массива Matrix
'При работе с матрицей ему удобно было, чтобы индексы начинались с 1,
'поэтому он изменил опцию Base, отменив предыдущее соглашение
'Ранее работавшая процедура War перестала работать
Dim Matrix(10, 20) As Double
Public Sub War()
Dim i
For i = 0 To 5
A1(i) = i: A2(i) = i + 5
Vector(i) = A1(i): Vector(i + 5) = A2(i)
Next i
End Sub
Каждая строчка этого быстро написанного текста должна быть переписана при хорошем программировании.
Динамические массивы VBA это мощное средство, отсутствующее в таких известных языках как, например, С++ и Паскаль. Массив считается динамическим , если при первоначальном объявлении не указывается его размерность, но она может быть определена и переопределена в последующем оператором ReDim. Размерность определяется динамически в той процедуре и в тот момент, когда она становится фактически известной. Обратите внимание, в этом операторе границы изменения индексов можно задать не только как константы, но и как выражения, зависимые от переменных.
Если затем нужно изменить границы или размерность массива, Вы можете снова задать оператор переопределения ReDim и начать новый цикл работы с массивом. И еще одна "приятность" - можно сохранить все ранее полученные элементы и расширить массив, добавив новые элементы. Для этого надо просто задать ключевое слово при переопределении массива.
Рассмотрим пример. На уровне модуля объявим глобальный Vector:
'Объявление динамического массива Public Vector() As Integer
В момент объявления размерность динамического массива не указывается, соответственно не выделяется память. Все это произойдет позже, в процессе выполнения программы. Вот одна из возможных процедур, работающая с этим массивом Vector. Его размерность в момент работы с ним определяется в диалоге с пользователем. По ходу дела массив расширяется, сохраняя старые значения.
Public Sub DynArray()
'Определяется фактическая размерность массива Vector
Dim N As Byte, I As Byte
N = InputBox("Введите число элементов вектора")
ReDim Vector(1 To N)
For I = 1 To N
Vector(I) = 2 * I + 1
Next I
' Массив расширяется с сохранением ранее вычисленных элементов
ReDim Preserve Vector(1 To 2 * N + 1)
For I = N + 1 To 2 * N + 1
Vector(I) = 2 * I
Next I
'А теперь печать
Debug.Print "Элементы Vector:" Chr(13)
For I = 1 To 2 * N + 1
Debug.Print Vector(I)
Next I
End Sub
При печати элементов этого массива будут напечатаны, как нетрудно понять вначале нечетные числа от 3 до 21, а затем четные от 22 до 42.
Динамические массивы с успехом можно применять там, где необходимы динамические структуры данных, например списки, стеки, очереди.
Наряду с массивами, представляющими объединение элементов одного типа, существует еще один способ создания Т, а затем в нужном месте и в нужном количестве объявлять переменные типа Т, представляющие собой конкретные записи.
Определение каждой записи можно рассматривать, как определение дерева, с корнем которого связана сама запись, а с вершинами - потомками связаны поля этой записи. Поскольку каждое поле может быть элементом произвольного типа, в том числе записью, такие вершины имеют потомков. Благодаря вложенности запись может задавать структуру сколь угодно сложно построенного дерева, у каждой вершины которого может быть произвольное число потомков. Это статическая структура, полностью определяемая в момент объявления записи.
Что же мы имеем в VBA? Формально здесь нет понятия запись или структура, или какого-либо эквивалента. Здесь есть только тип, определенный программистом. Но поскольку он позволяет определять запись, ничего больше и не нужно. Так что записи в VBA есть, но называются они типом, определенным программистом.
При определении типа программист дает ему имя, что позволяет затем определять переменные этого типа обычным способом. Поскольку все элементы совокупности именованы, возможен прямой доступ по имени к каждому из элементов. Ниже мы будем называть переменные такого типа записями, а элементы записи - полями. Такая терминология общепринята и соответствует программистской традиции.
Синтаксис определения типа в VBA достаточно прозрачен:
[Private | Public] Type <имя типа> <имя элемента> [([<размерность массива>])] As <тип элемента> [<имя элемента> [([<размерность массива>])] As <тип элемента>] ... End Type
Определение типа дается на уровне модуля и, если оно является закрытым ( Private ), распространяется на один модуль, а для общих ( Public ) типов - на все. Мы ограничимся довольно стандартным примером, в котором определяются два типа (записи): Fam и Person, где одно из полей Person имеет тип Fam.
'Определение записей - пользовательских типов Type Fam firstName As String lastName As String End Type Type Person Fio As Fam Birthdate As Date End Type
Эти объявления мы поместили в раздел объявлений модуля Father. Вот одна из его процедур, использующих эти типы данных:
Public Sub UserType()
Dim Петров As Person
Dim Козлов As Person
Петров.Fio.firstName = "Петр"
Петров.Fio.lastName = "Петров"
Петров.Birthdate = #1/23/1961#
Козлов.Fio.firstName = Петров.Fio.firstName
Козлов.Fio.lastName = "Козлов"
Козлов.Birthdate = #7/21/1966#
MsgBox (Петров.Fio.firstName " " Петров.Fio.lastName _
" родился " Петров.Birthdate)
MsgBox (Козлов.Fio.firstName " " Козлов.Fio.lastName _
" родился " Козлов.Birthdate)
End Sub
Вот что будет напечатано в результате ее работы:
Петр Петров родился 23.01.61 Петр Козлов родился 21.07.66
VBA старается помочь при работе с записями, предоставляя возможность выбора полей записи из открывающегося списка. Пусть Вас не смущает форма записи дат рождения - при определении константы для дат можно задавать в более человечной форме. Например, здесь мы задавали их как #January 23, 1961# и #21 July, 66#, - они автоматически были преобразованы в формальный вид.
Итак, пусть определен пользовательский тип T и объявлены переменные этого типа, например X и Y. Что можно с ними сделать? Допустимо ли присвоение ( X=Y ) или сравнение ( If (X=Y) Then Fun ), допустимы ли операции над записями? Ответ: нет, за одним исключением - присвоение допустимо, все остальное нет, в том числе и сравнение. Так что можно написать:
Петров = Козлов
Но нельзя написать:
If (Петров = Козлов) Then Debug.Print "Записи идентичны"
Отсутствие разрешенных операций над записями не означает, что с ними нельзя работать. Главное, что определен прямой доступ к полям записи, и этого достаточно, - с полями можно работать, как с переменными. Наш пример ( Sub UserType ) демонстрирует работу с полями записей.
Итак, еще раз напомним, что объявления можно давать на двух уровнях - модуля и процедуры. На уровне модуля раздел объявлений идет первым и автоматически отделяется чертой от раздела методов. На уровне процедуры объявления и операторы могут быть перемешаны, требуется лишь, чтобы объявление переменной предшествовало ее использованию. Хорошим тоном считается и в процедурах иметь два четко выделенных раздела и все объявления размещать в начале процедуры, так чтобы они предшествовали исполняемой части процедуры - разделу операторов.
В классически надежном языке программирования Паскаль, созданном Никласом Виртом, раздел объявлений имел четкую структуру и в свою очередь подразделялся на разделы, содержащие объявления констант, типов, процедур, переменных. В языке VBA такого формального разделения нет, поэтому возможно, что объявление переменных пользовательского типа T предшествует объявлению самого типа. Опять-таки наша рекомендация состоит в том, что программист должен самостоятельно поддерживать структуру раздела объявлений и разделять объявления констант, типов и переменных. Давайте четко выделим основные части раздела объявлений:
Declare.Опции являются указаниями для транслятора. Они могут задаваться только на уровне модуля и должны начинать раздел объявлений. Заметьте, это не пожелание, а синтаксическое требование. Опции задаются ключевым словом Option, после которого идет
Explicit - Об этой опции мы уже подробно говорили. При ее указании транслятор требует, чтобы все переменные модуля были явно описаны. Правильно включить эту опцию раз и навсегда в опциях Редактора VBA.Base - Эта опция имеет два значения: 0 и 1, указывающие нижнюю границу индекса массивов, задаваемую по умолчанию. Правильно не пользоваться этой опцией, а самому и всегда указывать нижнюю границу. К сожалению, нет флажка, который бы заставлял нас следовать этому разумному правилу.Private - Эту опцию, достаточно поместить в один из модулей проекта, обычно, в главный модуль проекта, который неявно всегда выделяется программистом. При ее задании проект делается закрытым и недоступен для других проектов в системе документов.Compare - Опция говорит транслятору, как он должен выполнять сравнение строк в процедурах модуля. Параметр опции может принимать одно из трех возможных значений:{Binary | Text | DataBase}
По умолчанию VBA применяет метод Binary, при котором строки сравниваются по внутренним кодам соответствующих символов. В Windows порядок сортировки определяется кодовой страницей. Вот часть типичного такого порядка:
A < Z < a < z z < А < Я < а < я
Метод сравнения Text основан на сравнении, не чувствительном к регистру, так что при сравнении заглавные и строчные буквы не различаются. Для тех же символов порядок при этом сравнении будет такой:
(A = a) < (Z = z) (Z = z) < (А = а) < (Я = я)
Метод DataBase допустим лишь при работе с Access. Сравнение при этом базируется на порядке, задаваемом локализацией той БД, для которой проводится сравнение.
О том, как объявляются константы, типы и переменные мы говорили достаточно подробно. Сейчас же отметим, что разумно создавать такие разделы явно, отделяя комментариями один раздел от другого. Желательно, чтобы эти разделы шли в описанном нами порядке. Тогда уже объявленные константы будут появляться при описании границ массивов, типы переменных будут предшествовать объявлению самих переменных.
Для констант и типов ключевые слова Const и Type однозначно определяют описываемый объект. Для переменных ключевые слова могут быть разными. Заметим, что для переменных уровня модуля не целесообразно использовать ключевое слово Dim, лучше использовать спецификаторы Public или Private, явно указывающие область действия переменной.
Этот раздел появляется в тех случаях, когда модули проекта используют DLL. Если DLL имеет библиотеку типов TypeLib и она доступна проекту, то нет необходимости описывать компоненты библиотеки, они будут найдены автоматически. Но если TypeLib недоступна или не определена, то в этой ситуации каждая из функций и процедур библиотеки, вызываемая в модуле, должна быть предварительно описана специальным оператором Declare. Вот его синтаксис:
[Public | Private] Declare {Sub | Function} имя Lib "имя_библиотеки" _
[Alias "псевдоним"] [([параметры])] [As возвращаемый_тип]
Здесь указывается имя библиотеки, имя процедуры или функции, возможный псевдоним, параметры и возвращаемое значение для функций. Подробно о DLL, операторе Declare с примерами на эту тему будет рассказано в одной из лекций этой книги.
Есть несколько простых правил, которые следует выполнять, чтобы быть цивилизованным программистом. Заметьте, можно быть умным, грамотным, но хорошо бы еще быть и цивилизованным. Многим из нас, я это отношу и к себе, не хватает цивилизации. Правила, о которых мы сейчас поговорим, касаются оформления текста программ. Большинство из них достаточно просто и по ходу дела мы о них неоднократно говорили. Вот основные:
Сейчас мы поговорим об одном техническом, но практически важном вопросе, - как правильно давать имена константам, переменным и другим объектам в наших программах. Казалось бы, какая разница, - "хоть горшком называй, только в печь не сажай". Это, однако, не так. Имя играет большую роль в понимании программ, и я верю даже в некоторую мистику, с плохо названной переменной всегда случаются какие то истории.
Правило написания имен переменных, которому мы обычно следовали в своих примерах и повседневной практике, состоит в следующем: имя должно отражать содержательный смысл, и состоит из одного или нескольких слитно написанных слов, каждое из которых начинается с большой буквы.
Разработчики от Microsoft рекомендуют придерживаться более строгих правил. Имя должно отражать не только смысл, но и тип переменной и ее область действия. Поэтому имя должно состоять из префикса и собственного имени. Префикс также является составным, две его части отражают область действия и тип переменной. В идущих ниже таблицах показаны возможные значения префикса:
| Первая часть префикса | Область действия |
|---|---|
| g | Global - Весь проект |
| m | Module - Для Private переменных модуля отсутствует |
| p | Procedure - Для локальных переменных |
| Вторая часть префикса | Тип переменной |
|---|---|
| str | String |
| int | Integer |
| byt | Byte |
| lng | Long |
| sng | Single |
| dbl | Double |
| cur | Currency |
| var | Variant |
| obj | Object |
| bln | Boolean |
Вот несколько примеров правильно построенных имен: gstrOneWord, mintNumberOne, strAnswer, curSalary. Также как по значению константы можно восстановить ее тип, по правильно построенным именам можно однозначно восстановить их объявление. Сделаем это в нашем примере:
Public gstrOneWord As String Private mintNumberOne As Integer Dim strAnswer As String Dim curSalary As Currency
Согласно этим же рекомендациям имена констант следует строить только из заглавных букв. Если имя константы состоит из нескольких слов, то для их объединения используется знак подчеркивания, например: MY_DIRECTORY_PATH. Обратите внимание, при построении констант Office 2000 используется префикс, указывающий, какому из приложений принадлежит константа.
| Приложение | Префикс констант |
|---|---|
| Access | ac |
| Excel | xl |
| FrontPage | fp |
| Office | mso |
| OfficeBinder | bind |
| Outlook | ol |
| Power Point | pp |
| Word | wd |
| VBA | vb |
Префиксы следует использовать и при именовании объектов, для форм обычно используется префикс frm, для командных кнопок - cmd и так далее. Следует понимать, что, если уж пользоваться префиксами, то обще употребительными, не следует заниматься самодеятельностью в этом вопросе. Мы закончим призывом, - "будьте цивилизованными".
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.