Основы офисного программирования и язык VBA

         

Цикл Do...Loop


Повторяет блок операторов, пока заданное условие является истинным или пока оно не станет истинным.

Синтаксис:

Имеется четыре варианта синтаксиса этого цикла. В двух первых вариантах условие проверяется в начале цикла:

Do [{While | Until} условие] тело цикла Loop

В других двух вариантах условие проверяется в конце цикла:

Do тело цикла Loop [{While | Until} условие]

Здесь условие является числовым или строковым выражением со значениями True или False. Вообще оно необязательно. Значение Null условия трактуется как False. Тело цикла - это последовательность операторов, которая будет выполняться, пока условие остается истинным, если перед ним стоит ключевое слово While или пока оно остается ложным - в вариантах цикла с ключевым словом Until. Таким образом, циклы вида While условие эквивалентны циклам вида Until Not условие. Кроме того, в тело цикла может входить оператор Exit Do, выполнение которого сразу прекращает цикл и передает управление оператору, непосредственно следующему за Loop. В случае нескольких вложенных циклов Do … Loop оператор Exit Do завершает лишь самый внутренний цикл, в теле которого он расположен.

Примеры.

В нашем примере реализованы три варианта поиска по образцу с проверкой условия в начале цикла, в конце цикла и в середине цикла для варианта поиска по образцу с барьером:

Пример 7.5.

(html, txt)



Цикл For Each...Next


Повторяет заданную последовательность операторов для каждого элемента массива или набора.

Синтаксис:

For Each элемент In группа тело цикла Next [элемент]

Здесь элемент - переменная, которая пробегает в качестве значений элементы коллекций или массива. Для коллекций элемент может быть переменной типа Variant, переменной типа Object или переменной (объектом) некоторого класса. В случае цикла по массиву элемент обязан быть переменной типа Variant. Группа - это имя набора объектов (чаще всего это коллекция объектов) или массива, для элементов которых выполняется цикл. Цикл не применим для массивов, тип элементов которых определен пользователем, так как такие элементы не могут быть значениями переменной типа Variant. В таких массивах можно использовать цикл вида For … Next. Тело цикла - последовательность операторов, выполняемая для каждого элемента набора или массива, - может содержать операторы Exit For, позволяющие прервать выполнение цикла и передать управление оператору, следующему за Next (обычно такой выход происходит при выполнении некоторого условия, проверяемого в операторе If…Then…Else). Указывать переменную элемент после ключевого слова Next не обязательно, но желательно.

Примеры.

В примере создается коллекция, число элементов которой и сами элементы выбираются случайным образом. Затем эта коллекция копируется в динамический массив, размерность которого увеличивается в процессе копирования. На последнем этапе массив распечатывается. Циклы типа For …Each прекрасно работают в подобных ситуациях:

Public Sub ForEach1() Dim X As New Collection Dim Y() As Integer Dim item As Variant Dim i As Integer, Size As Integer

'Инициализация коллекции Randomize Size = Int(21 * Rnd) For i = 1 To Size X.Add Int(11 * Rnd) Next i

'Копирование коллекции в динамический массив Size = 1 For Each item In X ReDim Preserve Y(1 To Size) Y(Size) = item Size = Size + 1 Next item

'Печать элементов динамического массива For Each item In Y Debug.Print item Next item End Sub

Кроме рассмотренных управляющих операторов VBA содержит доставшиеся в наследство от прежних версий операторы перехода по метке GoTo, перехода по метке с возвратом GoSub...Return и условные операторы перехода по меткам On...GoSub и On...GoTo. Мы никогда не пользуемся этими операторами.



Цикл While...Wend




Повторяет выполнение последовательности операторов, пока заданное условие не станет ложным.

Синтаксис:

While условие тело цикла Wend

Здесь условие и тело цикла такие же, как и для цикла Do…Loop. Только для этого вида цикла не предусмотрен оператор выхода Exit. Фактически цикл While…Wend - частный случай цикла Do…Loop - оставлен в языке для совместимости с предыдущими версиями.



Изменение текущего диска: оператор ChDrive


Синтаксис:

ChDrive диск

Обязательный параметр диск - это строка, задающая имя существующего диска, который должен стать текущим, например:

ChDrive "D"

сделает диск "D" текущим.



Изменение текущего каталога (папки): оператор ChDir


Синтаксис:

ChDir путь

Обязательный параметр путь - это строковое выражение, значение которого задает новый текущий каталог (папку). Если путь не содержит имени диска, меняется текущий каталог на текущем диске. Подчеркнем, что оператор ChDir меняет текущий каталог (папку), но не диск. Например, если текущим является диск С, команда:

ChDir "D:\TMP"

изменит текущий каталог на диске D, но диск C останется текущим.



Копирование файлов: оператор FileCopy


Синтаксис:

FileCopy файл-источник, файл-результат

Параметр файл-источник - строковое выражение, задающее имя копируемого файла, Файл-результат - строковое выражение, которое определяет имя результирующего файла. Оба имени могут включать имена дисков, каталогов или папок. Открытый в данный момент файл копировать нельзя.

Пример:

'Копирование файла FileCopy "Temp1\Example1.xls", "Temp2\Example1.xls"



Моделирование ввода с клавиатуры: оператор SendKeys


Посылает один или несколько кодов символов в текущее активное окно, как если бы соответствующие клавиши были нажаты.

Синтаксис:

SendKeys строка[, режим-ожидания]

Параметр строка - строковое выражение, задающее последовательность посылаемых символов. Необязательный параметр режим-ожидания - выражение с булевым значением. Если оно False (по умолчанию), управление возвращается в процедуру сразу же после посылки кодов; True - посланная последовательность кодов должна быть обработана, прежде чем управление возвратится в процедуру.

Большинство символов, набираемых на клавиатуре, входят в строку непосредственно. Например, чтобы послать последовательность из трех символов Y, E и S, возьмите в качестве строки " YES". Символы: +, ^, %, ~, скобки и некоторые другие следует помещать в фигурные скобки. Например, чтобы послать +, в параметр строка нужно поместить {+}. Для посылки кодов клавиш, не отображаемых на экране, имеются специальные коды. Некоторые представлены в таблице, остальные можно найти с помощью подсказки:

Таблица 7.2. Коды клавиш, не отображаемых на экране

КлавишаКод
BACKSPACE{BS}
BREAK{BREAK}
CAPS LOCK{CAPSLOCK}
DEL{DEL}
DOWN ARROW{DOWN}
END{END}
ENTER{ENTER} или ~
ESC{ESC}
HOME{HOME}
INS{INS}
LEFT ARROW{LEFT}
PAGE DOWN{PGDN}
PAGE UP{PGUP}
RIGHT ARROW{RIGHT}
TAB{TAB}
UP ARROW{UP}
Fn{Fn} (n=1,…, 12)

Чтобы послать комбинацию клавиш, для Shift, Ctrl и Alt используются следующие коды (В примере используется документ DocTwo7:

Shift - + Ctrl ^ Alt %

Пример:

В качестве примера рассмотрим открытие документа, требующего пароль при его открытии:

Public Sub OpenDocWithPassword() 'Открытие документа с паролем SendKeys "+^" 'Переключение раскладки клавиатуры SendKeys "don" & "{'}" & "t know", False 'пароль "don't know" Documents.Open "e:\O2000\CD2000\Ch7\DocTwo7.doc" Documents("DocTwo7.doc").Activate

End Sub

Обратите внимание на два момента:

Прежде, чем послать сам пароль, происходит переключение клавиатуры на другую раскладку, для чего используется комбинация символов "Shift + Ctrl".Символы клавиатуры посылаются в буфер ввода еще до того, как они потребуются. Если бы оператор Open предшествовал оператору SendKeys, то окно ввода пароля появилось бы до выполнения этого оператора. В данном же случае при открытии документа пароль автоматически появится в окне ввода, так что останется только щелкнуть по кнопке OK.



Операции с одним объектом. Оператор With


Если в одном блоке программы предстоит выполнить несколько операций с одним объектом, то, чтобы не повторять многократно имя этого объекта, можно ввести оператор With.

Синтаксис:

With объект [операторы] End With

Здесь объект - имя объекта или переменной, определенного пользователем типа, а операторы - последовательность операторов, которые могут действовать с указанным объектом. В этих операторах имена свойств и методов указанного объекта можно начинать с точки, опуская имя самого объекта.

Допустим, пользовательский тип Person и переменная MyFriend определены так.

Type Person Name As String Age As Integer Height As Single End Type

Dim MyFriend As Person

Тогда присвоение значений свойствам переменной MyFriend можно произвести с помощью оператора With:

With MyFriend Name = "Сергей" Age= 35 .Height = 178.5 End With

Подчеркнем, что в каждом блоке имя лишь одного объекта задается по умолчанию. При вложенности операторов With:

With объект1 операторы1 With объект2 операторы2 End With

End With

в блоке операторы2 имя объекта1 нужно указывать полностью. Если же объект2 - элемент (подобъект) объекта1, то, использовав оператор With объект2, можно получить во внутреннем блоке сокращенный доступ к свойствам объекта "объект1. объект2".

Например, если к типу Person добавить данные о встрече:

Type Meeting Place As String Date As Date End Type

Type Person Name As String Age As Integer Height As Single LastMeeting As Meeting End Type

Dim NewAcquaintance As Person

то задавать данные о новом знакомом NewAcquaintance можно, используя вложенные операторы With.

With NewAcquaintance .Name = "Елена" .Age= 40 .Height = 168 With.LastMeeting .Place= "библиотека" 'этот оператор эквивалентен: 'NewAcquaintance. LastMeeting.Place="библиотека" .Date= #08/03/99# End With End With



Оператор комментария


Комментарии на исполнение программы не влияют, но необходимы как признак "хорошего стиля". Офисные программы используются многократно и не раз модернизируются в процессе своей жизни. Вы можете сэкономить на комментариях и написать и отладить небольшой модуль без них. Но уже через неделю никто, в том числе и автор, не сможет понять его действие и модифицировать нужным образом. Затраченные на это усилия и время намного превзойдут "экономию". Любая строка текста программы может заканчиваться комментарием. Комментарий в VBA начинается апострофом (') и включает любой текст, расположенный правее в строке. Обычно в комментариях описывают задачи, решаемые модулями, функции, выполняемые процедурами, смысл основных переменных (если он неясен из имен), алгоритмы работы процедур. Полезно также комментировать операторы вызовов внешних для данного модуля процедур, объясняя их действия.

Другое применение комментарии находят при отладке программ. Если требуется исключить из программы некоторые операторы (например, вызовы еще не реализованных или сомнительных процедур), достаточно перед ними поместить апостроф. Например, при выполнении последовательности операторов

x=x+z 'z=fun(z,x) y=y*x

функция fun для вычисления нового значения z во второй строке не вызывается и не мешает проверить правильность значения y.

В VBA имеется еще один способ выделения комментариев с помощью ключевого слова Rem. Такой комментарий (в отличие от комментария, начинающегося апострофом) должен отделяться от предыдущего оператора в строке двоеточием:

weight= weight+z: Rem Увеличение веса value=weight*price 'Новая стоимость

При отладке VBA программ часто приходится временно комментировать целые участки текста, иногда страницы текста. В этом случае закомментировать или снять комментарии довольно утомительное дело. В этих случаях следует пользоваться двумя полезными командами меню Edit: "Comment Block" и "UnComment Block". Они позволяют автоматически закомментировать или снять комментарий с выделенного блока. Я всегда использую настройку (Customize) и выношу на панель редактора VBE кнопки, выполняющие эти команды.



Оператор Let


С помощью этого оператора происходит "обычное" присвоение значения выражения переменной или свойству.

Синтаксис:

[Let] переменная = выражение

Ключевое слово Let, как правило, опускается. Переменная является именем переменной или свойства; выражение задает значение, присваиваемое переменной. Его тип должен соответствовать типу переменной. Нарушение этого условия, например, при попытке присвоить числовой переменной строковое значение, приводит к тому, что при компиляции появится сообщение об ошибке. Переменным типа Variant можно присваивать значения разных типов, например, строковых или числовых выражений. Строковой переменной можно присваивать любое значение типа Variant, кроме Null. Числовой же переменной значение типа Variant можно присвоить, только если оно может быть преобразовано к числу.

Оператор Let можно применять для присвоения одной переменной типа "запись" значения другой такой переменной. Заметьте, это возможно только, если обе они - переменные одного определенного пользователем типа.

Примеры:

Public Sub Assign1() Dim MyStr As String, MyInt As Integer

Let MyStr = "Здравствуй, зайчик!" ' С ключевым словом Let MyInt = 5 ' Без него. Обычный вариант. Debug.Print MyStr, MyInt

End Sub



Оператор LSet


Этот оператор служит для присвоения строк с одновременным выравниванием слева, а также для копирования записи одного определенного пользователем типа в запись другого типа. Его синтаксис:

Lset СтрПеременная = СтрВыражение Lset переменная1 = переменная2

Здесь ключевое слово LSet обязательно, СтрПеременная - имя строковой переменной, СтрВыражение - выражение строкового типа. Во втором варианте переменная1 - имя переменной некоторого определенного пользователем типа, в которую выполняется копирование, переменная2 - имя переменной, возможно, другого пользовательского типа, значение которой копируется.

Результатом присвоения строк всегда является строка той же длины, что и у СтрПеременная. Если при этом СтрВыражение короче, добавляются пробелы справа; длиннее - лишние символы справа удаляются.

При втором варианте оператора двоичное представление записи из участка памяти, отведенного переменной2 копируется в участок памяти, отведенный переменной1, - При этом данные не преобразуются в соответствии с типами элементов (полей) записи, и если типы соответствующих элементов обеих записей не совпадают, результат операции трудно предсказать (часто выдается сообщение о несоответствии типов). Поэтому этот вариант следует использовать, лишь тогда, когда типы всех соответствующих элементов записей совпадают и имеют одинаковый размер.

Примеры.

Public Sub Assign2() Dim Str1 As String, Str2 As String

Str1 = "0123456789" ' Начальное значение Str2 = "abcd" Debug.Print Str1, Str2

LSet Str2 = Str1 ' Результат - "0123" LSet Str1 = " Влево" ' Результат - " Влево Debug.Print Str1, Str2

End Sub"

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

0123456789 abcd Влево 0123

В следующем примере происходит корректное копирование одной записи в другую; типы соответствующих элементов в определенных пользователем типах MyType1 и MyType2 совпадают:

Type MyType1 age As Integer cost As Long End Type

Type MyType2 year As Integer pay As Long 'pay As Integer End Type

Public Sub Assign3() Dim my1 As MyType1 Dim my2 As MyType2

my1.age = 50 my1.cost = 45666

my2.year = 1997 my2.pay = 22000 Debug.Print my1.age, my1.cost, my2.pay, my2.year

LSet my2 = my1 ' после этого присвоения my2.year=50 и my2.pay=45666 Debug.Print my1.age, my1.cost, my2.pay, my2.year

End Sub

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

50 45666 22000 1997 50 45666 45666 50

Если изменить тип элемента pay в MyType2 на Integer, то будет напечатано значение my2.pay, равное 0.



Оператор RSet


Этот оператор присваивает значение строковой переменной с выравниванием справа:

RSet СтрПеременная = СтрВыражение

СтрПеременная - имя строковой переменной, СтрВыражение - выражение строкового типа. В отличие от LSet оператор RSet нельзя использовать для копирования переменных записей. Результатом присвоения строк всегда является строка той же длины, что и СтрПеременная. Если при этом СтрВыражение короче, добавляются пробелы слева, длиннее - лишние символы слева удаляются.

Примеры:

Public Sub Assign4() Dim Str1, Str2, Str3

Str1 = "0123456789" ' Начальное значение Str2 = "abcd" Debug.Print Str1, Str2

RSet Str2 = Str1 ' Результат - "0123" RSet Str1 = "Вправо " ' Результат - " Вправо " RSet Str3 = Str1 ' Результат - пустая строка "" Debug.Print Str1, Str2, Str3 End Sub

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

0123456789 abcd Вправо 0123



Оператор Set


Этот оператор применим при работе с объектами, устанавливает ссылку на вновь созданный или существующий объект. Его синтаксис:

Set ОбПеременная = {[New] ОбВыражение| Nothing}

ОбПеременная - имя переменной или свойства, New - необязательное ключевое слово, используемое для явного вызова операции создания нового экземпляра класса (объекта). Если ОбПеременная содержала ссылку на объект, при присвоении эта ссылка освободится. ОбВыражение может быть именем объекта (класса), другой переменной того же типа, функцией или методом, возвращающими объект соответствующего типа. Выполнение оператора Set с правой частью Nothing прерывает связь между ОбПеременной и объектом, на который она ссылалась. Если при этом на него не осталось других ссылок, ресурсы системы и память, выделенные под этот объект, освобождаются. В общем случае, если ключевое слово New не указано, новая копия объекта не создается, а ОбПеременная как значение получает ссылку на существующий объект. При этом может оказаться, что несколько переменных ссылаются на один объект и изменение этого объекта через одну из них влияет на все остальные.

Примеры:

Определим класс объектов Child:

'Класс Child 'Свойства Public Age As Byte Public Name As String 'Другие свойства и методы пока не определены

А теперь введем объекты этого класса:

Пример 7.1.

(html, txt)

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

Имя: Александр Возраст: 10 Имя: Мария Возраст: 7 Имя: Саша Возраст: 10 Имя: Маша Возраст: 7 Имя: Саша Возраст: 10 Имя: Маша Возраст: 7



Оператор выбора Select Case


Этот оператор производит разбор случаев и в зависимости от значения анализируемого выражения выбирает и исполняет одну из последовательностей операторов.

Синтаксис.

Select Case Выражение-тест [Case СписокВыражений-n [операторы-n]] [Case Else [ИначеОператоры]] End Select

Выражение-тест должно присутствовать обязательно. Оно может быть произвольным выражением с числовым или строковым значением. СписокВыражений-n должен присутствовать в строке, начинающейся ключевым словом Case (Случай). Выражения в этом списке отделяются запятыми и могут иметь одну из форм:

выражение,выражение-нижняя-граница To выражение-верхняя-граница,Is оператор-сравнения выражение.

Первая форма задает отдельные значения, вторая и третья позволяют задавать сразу целые диапазоны (области) значений. Последовательность операторов операторы-n необязательна. Она будет исполнена, если соответствующий СписокВыражений-n является первым списком, сопоставимым с текущим значением Выражения-теста (т. е. он явно содержит это значение, либо оно попадает в один из заданных в списке диапазонов). После исполнения операторов последовательности операторы-n проверка на соответствие другим спискам выражений не производится, и управление передается на оператор, следующий за End Select. Необязательная последовательность ЕслиОператоры выполняется, если ни один из списков выражений несопоставим со значением Выражения-теста.

Пример:

Public Sub Case1() Dim Before As Integer Dim CurrentYear As Integer, Str As String

' Инициализация переменных: CurrentYear = 1999 Before = InputBox("Сколько лет тому назад?", "Когда", 10)

Select Case CurrentYear - Before Case 1954 To 1969, 1971 To 1974, 1982, Is < 1970 Str = " Годы учебы" Case 1972 To 1989 Str = "Годы воспитания" Case Else Str = "Прочие годы" End Select Debug.Print Str

End Sub

Здесь, если перед выполнением выбора Before = 20, значением тестового выражения будет 1979, и будет работать второй вариант ("Годы воспитания"). При Before = 25 значение 1974 сопоставимо с двумя списками, но для исполнения будет выбран лишь первый вариант ("Годы учебы").

Диапазоны значений можно задавать и для строк. При этом их значения считаются упорядоченными лексикографически. Например, возможен такой список выражений:

Case "everything", "nuts" To "soup"

Задаваемое им множество строк включает строку "everything" и все строки от "nuts" до "soup" (например, "onion").



Операторы


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

VBA - операторный язык. Это значит, что его программы (модули) представляют последовательности операторов. Набор операторов VBA весьма обширен и не уступает в этом "большим" языкам вроде Паскаля и С. Группу декларативных операторов VBA, служащих для описания объектов, с которыми работает программа (типов, переменных, констант, объектов приложений и др.), мы уже рассмотрели. Операторы другой группы обеспечивают присвоение и изменение значений этих объектов, операторы третьей группы управляют ходом вычислений, четвертой - работой с каталогами и файлами и т.д. Часть операторов досталась VBA в наследство от предыдущих версий Бейсика и без них вполне можно обойтись при написании новых программ.



Операторы и строки


При записи текста программ для упрощения чтения, отладки и модификации программы удобней каждый оператор располагать в отдельной строке текста. Следуйте правилу: "Один оператор - одна строка". Но, разрешается размещать на строке и несколько операторов, в отличие от общепринятого символа разделения операторов "точки с запятой", в VBA символом разделения двух операторов в одной строке служит двоеточие. Заметьте, некоторые операторы, например оператор If, могут стоять лишь на первом месте в строке. И по этой причине каждый оператор, как правило, следует начинать с новой строки, лишь иногда, разумно, группу операторов присваивания размещать в одной строке. Чаще возникает другая ситуация, - оператор слишком длинный и его текст не виден полностью на экране дисплея, что затрудняет чтение и понимание программы. В этом случае оператор следует продолжить в одной или нескольких строках. Чтобы продолжить (перенести) оператор на следующую строку, используется пара символов пробел-подчеркивание "_". Например,

MyAddress = "дом: " & Number & "улица: " & Street _ & "город: " & City

Перед оператором в строке может стоять метка - последовательность символов, начинающаяся с буквы и кончающаяся двоеточием ":". Метки можно размещать и в отдельных строках перед теми операторами, которые они должны помечать. Они нужны для операторов перехода типа GoTo, использование которых считается "дурным тоном" в структурном программировании. Но иногда без меток и переходов на них обойтись трудно - в частности, для указания входов в обработчики ошибок в некоторых процедурах.



Переименование каталогов (папок) и файлов: оператор Name


Позволяет переименовывать каталоги (папки) и файлы и перемещать файлы.

Синтаксис:

Name СтароеИмя As НовоеИмя

СтароеИмя и НовоеИмя - обязательные параметры, задающие старое и новое имя файла (каталога, папки). Они могут включать имя диска и путь. СтароеИмя должно быть именем существующего файла (каталога, папки), а НовоеИмя не должно быть именем уже существующего объекта. Оба имени должны использовать один и тот же диск. Если указанный новый путь существует и отличается от старого, оператор перемещает файл в новый каталог (папку) и, если требуется, переименовывает его. Каталоги и папки с помощью оператора Name перемещать нельзя, - лишь переименовывать. Перед переименованием файл должен быть закрыт.

В этом примере файл переименовывается, а затем и перемещается в другой каталог:

Public Sub DirsAndFiles() 'Сделать текущим диск F ChDrive "F" 'Переименование файла Name "Exam1E.xls" As "Example1.xls" 'Перемещение файла Name "Example1.xls" As "Temp1\Example1.xls" End Sub



Устанавливаем дополнительные ссылки


Public Sub Assign5() Dim Children( 1 To 2) As Child Dim Boy As Child, Girl As Child
'Создаем объекты Set Children(1) = New Child Set Children(2) = New Child 'Инициализируем их Children(1).Age = 10 Children(1).Name = "Александр" Children(2).Age = 7 Children(2).Name = "Мария" Debug.Print "Имя: ", Children(1).Name, "Возраст: ", Children(1).Age Debug.Print "Имя: ", Children(2).Name, "Возраст: ", Children(2).Age
'Утанавливаем дополнительные ссылки на существующие объекты Set Boy = Children(1) Set Girl = Children(2) Boy.Name = "Саша" Girl.Name = "Маша" 'Изменились сввойства объектов Debug.Print "Имя: ", Children(1).Name, "Возраст: ", Children(1).Age Debug.Print "Имя: ", Children(2).Name, "Возраст: ", Children(2).Age
'Удаляем одну из ссылок, но объект остается Set Children(1) = Nothing Set Children(2) = Nothing Debug.Print "Имя: ", Boy.Name, "Возраст: ", Boy.Age Debug.Print "Имя: ", Girl.Name, "Возраст: ", Girl.Age
End Sub
Пример 7.1.
Закрыть окно




Public Sub MinMax1(ByVal X As Integer, ByVal Y As Integer, _ Min As Integer, Max As Integer) 'Оператор If в одну строку If X > Y Then Max = X: Min = Y Else Max = Y: Min = X
End Sub
Public Sub MinMax2(ByVal X As Integer, ByVal Y As Integer, _ Min As Integer, Max As Integer) 'Оператор If в виде блока If X > Y Then Max = X Min = Y Else Max = Y Min = X End If
End Sub
Public Sub If1() Dim Max As Integer, Min As Integer
Call MinMax1(2 + 3, 2 * 3, Min, Max) Debug.Print Max, Min Call MinMax2(2 + 3, 2 * 3, Min, Max) Debug.Print Max, Min End Sub
Пример 7.2.
Закрыть окно




Public Sub BoyOrMan() Dim Boy As New Child Dim Person As New Man Dim SomeBody As Object Dim Answer As Integer
Boy.Age = 10: Boy.Name = "Александр" Person.Age = 21: Person.Name = "Александр" Answer = InputBox("Введите число от 10 до 20", "Возраст", 15) If Answer > 15 Then Set SomeBody = Person Else: Set SomeBody = Boy End If
If TypeOf SomeBody Is Child Then Debug.Print "Это мальчик!" ElseIf TypeOf SomeBody Is Man Then Debug.Print "Это Мужчина" Else Debug.Print "Не знаю, кто это" End If End Sub
Пример 7.3.
Закрыть окно




Public Sub For1() Dim A(1 To 5, 1 To 5) As Integer Dim B(1 To 5, 1 To 5) As Integer Dim C(1 To 5, 1 To 5) As Integer Dim I As Integer, J As Integer, K As Integer Dim Res As String ' Инициализация матриц A и B случайными числами в интервале [-10, +10] VBA.Randomize For I = 1 To 5 For J = 1 To 5 'Получение случайного числа Rnd и преобразование в целое A(I, J) = Int(21 * Rnd) - 10 Next J Next I For I = 1 To 5 For J = 1 To 5 B(I, J) = Int(21 * Rnd) - 10 Next J Next I
'Вычисление произведения матриц For I = 1 To 5 For J = 1 To 5 C(I, J) = 0 For K = 1 To 5 C(I, J) = C(I, J) + A(I, K) * B(K, J) Next K Next J Next I
Res = "No" C(2, 2) = 0 'Проверка на нулевое значение For I = 1 To 5 For J = 1 To 5 If C(I, J) = 0 Then Debug.Print "Индексы: ", I, J Res = "Yes" Exit For End If Next J Next I Debug.Print Res End Sub
Пример 7.4.
Закрыть окно




Public Sub Loop1() Const Size = 5 Dim X() As Integer Dim i As Integer Dim Found As Boolean Const pat = 7 'Инициализация случайными числами в интервале [1 - 10] ReDim X(1 To Size) Randomize For i = 1 To Size X(i) = Int(11 * Rnd) Next i
'Поиск по образцу с проверкой в начале цикла i = 1: Found = False Do While (i <= Size) And (Not Found) If X(i) = pat Then Found = True Else: i = i + 1 End If Loop If Found Then Debug.Print "Найден образец!" Else: Debug.Print "Образец не найден!" End If
'Поиск по образцу с проверкой в конце цикла i = 1: Found = False Do If X(i) = pat Then Found = True Else: i = i + 1 End If Loop Until Found Or (i = Size + 1) If Found Then Debug.Print "Найден образец!" Else: Debug.Print "Образец не найден!" End If
'Поиск с барьером ReDim Preserve X(1 To Size + 1) X(Size + 1) = pat i = 1 Do If X(i) = pat Then Exit Do i = i + 1 Loop If i = Size + 1 Then Debug.Print "Образец не найден!" Else: Debug.Print "Образец найден!" End If End Sub
Пример 7.5.
Закрыть окно



Присваивание


Операторы присваивания - основное средство изменения состояния программы (значений переменных и свойств объектов). Мы уже многократно использовали их в примерах из предыдущих лекций. В VBA несколько видов операторов присваивания.



Прочие операторы


И еще несколько полезных операторов VBA, не попавших ни в одну из предыдущих групп.



Работа с каталогами, папками и файлами


Операторы этой группы позволяют перемещать текущий каталог (папку) по дискам системы, создавать новые и удалять каталоги (папки): изменять атрибуты файлов, копировать и удалять файлы. Их набор по существу совпадает с набором соответствующих команд операционной системы.



Создание каталога (папки): оператор MkDir


Синтаксис:

MkDir путь

Обязательный параметр путь - это строковое выражение, значение которого задает новый создаваемый каталог (папку). Если путь не содержит имени диска, каталог создается на текущем диске. В следующем примере на диске F создаются три новых каталога (папки):

Public Sub Dirs() 'Сделать текущим диск F ChDrive "F" 'Создать 3 каталога на F MkDir "Temp1" MkDir "F:\Temp2" MkDir "F:Temp3" End Sub



Удаление файлов: оператор Kill


Синтаксис:

Kill файл

Параметр файл - строковое вырыжение, задающее имя удаляемого файла. Он может включать имя диска и путь по каталогам или папкам. Для удаления нескольких файлов можно использовать в образце имени файла символ '*' для обозначения произвольной последовательности букв и '?' - для обозначения одного символа. Нельзя удалять открытый в данный момент файл.

Пример.

Допустим, в текущем каталоге находятся файлы PROG.DOC, PROG.CPP и PROG.OBJ. Тогда оператор:

Kill "PROG.*"

удалит эти файлы с диска.



Удаление каталога (папки): оператор RmDir


Синтаксис:

RmDir путь

Аргумент путь - строковое выражение, задающее удаляемый каталог или папку. Нельзя удалять каталог или папку, содержащие файлы (иначе будет выдано сообщение об ошибке). Чтобы удалить все файлы из каталога (папки), используйте оператор Kill.

RmDir "Temp3"

удалит папку Temp3 с текущего диска.



Управляющие операторы


Набор управляющих операторов VBA делает честь любому хорошо структурированному языку программирования. Циклы с возможной проверкой условия в начале, в конце и в середине работы оператора, обычный оператор If и оператор разбора случаев Case - все эти средства позволяют организовать процесс вычислений надежно и эффективно в соответствии с лучшими традициями программирования.



Установка атрибутов файла: оператор SetAttr


Синтаксис:

SetAttr имяфайла, атрибуты

Первый параметр имяфайла - строковое выражение, задающее имя файла, у которого требуется изменить атрибуты. Оно может включать имя диска и путь по каталогам (папкам). Параметр атрибуты - константа или числовое выражение, определяющее новые атрибуты файла в соответствии с таблицей:

Таблица 7.1. Атрибуты файла

Имя константыЗначениеОписание
VbNormal 0Нормальный (по умолчанию)
vbReadOnly 1Только для чтения
VbHidden 2Скрытый
VbSystem 4Системный файл
VbArchive 32Файл изменен со времени последнего создания резервной копии

Нельзя менять атрибуты открытого файла (иначе будет выдано сообщение об ошибке).



Установка системного времени: оператор Time


Синтаксис оператора Time:

Time = время

Пример:

Time = #8:15:47 PM# ' Установка системного времени



Установка системной даты: оператор Date


Синтаксис:

Date = дата

Пример:

Date = #April 29, 1999# ' Изменение системной даты.



Звуковой сигнал: оператор Beep


Подает звуковой сигнал через динамик компьютера.

Синтаксис:

Beep

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



Другие функции форматирования


Функция Format является общей функцией применимой к произвольным выражениям. Остальные функции применимы к выражениям специального типа, предоставляя некоторые дополнительные возможности. Мы не станем их подробно рассматривать, ограничившись простым перечислением:

FormatCurrency - возвращает денежное выражение, используя денежный знак.FormatDataTime - возвращает дату или время.FormatNumber - возвращает выражение, отформатированное как число.FormatPercent - возвращает выражение, заданное в процентах, с указанием знака процента.



Фильтрация элементов массива. Функция Filter


Функция Filter является одним из вариантов поиска по образцу среди элементов массива. Здесь не требуется точного совпадения элемента и образца, достаточно, чтобы строка - образец содержалась в строке, заданной элементом массива. Поскольку совпадений может быть достаточно много, то результатом является массив отфильтрованных элементов. Некоторые детали будут пояснены при описании аргументов функции Filter. Вот ее синтаксис:

Filter(sourcearray, match[, include[, compare]])

Ее параметры имеют следующий смысл:

sourcearray - одномерный массив, элементы которого являются строками. Он может быть получен, например, как результат расщепления строки в массив.match - образец поиска. Строка, вхождение которой ищется в каждом элементе исходного массива.include - необязательный аргумент булевого типа. По умолчанию имеет значение True, означающее, что элементы, удовлетворяющие образцу, входят в результирующий массив. Если задано значение False, то результирующий массив составляется из элементов, не удовлетворяющих образцу.compare - имеет обычный смысл.

В качестве результата возвращается массив отфильтрованных элементов.

Наш пример будет представлять расширенный вариант уже приводившейся процедуры SplitAndJoin:

Пример 8.3.

(html, txt)

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

А это веселая птица - синица, которая часто ворует пшеницу, которая в темном чулане хранится в доме, который построил Джек которая часто ворует пшеницу, которая в темном чулане хранится в доме, который построил Джек А это веселая птица - синица



Фильтрация, основанная на шаблоне. Функция WildFilter


Во многих ситуациях есть необходимость производить фильтрацию элементов массива, основываясь на шаблоне, которому должны удовлетворять фильтруемые элементы. Здесь есть полная аналогия с тем, что есть потребность производить замену на основе шаблона, а не точного образца. Поэтому естественно наше желание расширить стандартные возможности функции Filter, тем более, что реализуется эта возможность достаточно просто. Рассмотрим реализацию нашей функции:

Пример 8.5.

(html, txt)

Параметры этой функции сохраняют смысл стандартной функции Filter. Единственное отличие состоит в том, что теперь аргумент match задает настоящий шаблон, допускающий специальные символы. Чтобы быть справедливым, заметим, что теперь речь идет о точном соответствии шаблону, в то время, как в стандартной функции речь идет о вхождении шаблона в элемент. Так что обе функции имеют свои сферы применения. Реализация достаточно очевидна и основана на прямом использовании операции Like. Приведем тестовый пример, в котором вызывается наша функция:

Public Sub testWildFilter() Dim Txt As String, resTxt As String Dim Artxt() As String, ResAr() As String

Txt = "123, 5, 117, 7, 189" Artxt = Split(Txt, ", ") ResAr = WildFilter(Artxt, "1##") resTxt = Join(ResAr, ", ") Debug.Print resTxt

ResAr = WildFilter(Artxt, "[3-57-9]") resTxt = Join(ResAr, ", ") Debug.Print resTxt

End Sub

Приведем результаты ее работы:

123, 117, 189 5, 7



Форматирование данных. Функции группы Format


Числовые и строковые данные, данные типа дата и данные типа время могут быть отформатированы в соответствии с предопределенными в языке форматами или форматами, определенными пользователем. Для этой цели используется группа функций форматирования.



Функции проверки типов данных


К этой группе относится функция TypeName, которая по имени переменной возвращает значение типа String, представляющее ее тип.

Вызов имеет вид:

TypeName(имяПеременной)

где параметр имяПеременной представляет выражение типа Variant, определяющее любую переменную, за исключением переменной с определяемым пользователем типом.

Строка, возвращаемая функцией TypeName, может быть любой из следующих: Byte, Integer, Long, Single, Double, Currency, Decimal, Date, String, Boolean, Error, Empty (если переменная не инициализирована), Null, Object, Unknown (тип неизвестен), Nothing (объектная переменная, не содержащая ссылки на объект).

Для проверки типа имеется также набор функций с булевыми значениями. Они применяются к переменным или выражениям (часто имеющим тип Variant) и определяют, имеют ли те заданный тип или значение. В следующей таблице перечислены функции этой группы и указаны определяемые ими типы.

Таблица 8.6. Функции проверки типов

ФункцияЧто проверяет
IsArray(переменная) Является ли переменная массивом.
IsDate(выражение) Может ли значение выражения быть преобразовано в значение даты.
IsEmpty(переменная) Была ли инициализирована переменная.
IsError(выражение) Представляет ли выражение значение ошибки.
IsNull(выражение) Является ли результатом выражения пустое значение (Null).
IsNumeric(выражение) Имеет ли выражение числовое значение. (Для выражений типа дата возвращает False).
IsObject(переменная) Представляет ли переменная объектную переменную. Возвращает True, даже для переменной со значением Nothing.



Функция Format.


Функция возвращает строку, отформатированную в соответствии с указаниями, заданными при вызове. Ее синтаксис:

Format(expression[, format[, firstdayofweek[, firstweekofyear]]])

Ее параметры:

expression - любое правильное выражение.Format - имя встроенного параметра или определение пользовательского формата. Если параметр опущен, то применяется формат, зависящий от типа первого аргументаFirstdayofweek и firstweekofyear - их смысл был описан, когда мы рассматривали работу с датами

Приведем ряд примеров, в которых используется форматирование чисел на основе форматов, определяемых по умолчанию и пользователем:

? VBA.Format(55) 55 ? VBA.Format(5.5) 5,5 ? VBA.Format(-52.125, "##0.#0") -52,13 ? VBA.Format(-52.125, "000.##0") -052,125 ? VBA.Format(1152.125, "#,##0.#0") 1 152,13 ? VBA.Format(0.52125, "0.##0%") 52,125%

Несколько примеров форматирования строк:

? VBA.Format("5.4") 05.04.99 ? VBA.Format("5,4") 5,4 ? VBA.Format("Мария", ">") МАРИЯ ? VBA.Format("Мария", "<") мария ? VBA.Format("Мария", "> Это ") Это МАРИЯ

Даты форматируются обычно с использованием встроенных форматов, но можно применять и собственные определения форматов. Вот несколько примеров:

? VBA.Format(VBA.Time, "Long Time") 16:24:57 ? VBA.Format(VBA.Time, "Short Time") 16:25 ? VBA.Format(VBA.Time, "hh/mm/ss") 16.26.03 ? VBA.Format(VBA.Date, "Long Date") 9 Май 1999 г. ? VBA.Format(VBA.Date, "Short Date") 09.05.99 ? VBA.Format(VBA.Date, "yy/mm/dd") 99.05.09



Функция InStrRev - поиск последнего вхождения подстроки


Функция InStrRev симметрично дополняет функцию InStr, аналогично тому, как функция Right дополняет функцию Left. Эта функция ищет вхождение подстроки в строку, но начинает свою работу с правого конца строки. Ее использование может существенно ускорить работу, если заранее известно, что искомая подстрока находится где- то в конце строки - источника. Если вхождение искомой подстроки единственно, то обе функции дают один и тот же результат. При множественном вхождении функция InStr возвращает первое вхождение, в то время как InStrRev - последнее. Ее синтаксис:

InstrRev(stringcheck, stringmatch[, start[, compare]])

Ее параметры имеют тот же смысл, что и у функции InStr, но, заметьте, порядок их задания изменен. Необязательный параметр Start теперь задается третьим по счету. Когда он опущен, то по умолчанию, его значение равно "-1", и поиск начинается с последнего символа. Если вернуться к нашему последнему примеру, то для решения нашей содержательной задачи требовалось определить первое и последнее вхождение символа "\" в строке, задающей путь к файлу. Поэтому обе функции были весьма кстати. Нужно отметить, что это весьма типичная ситуация при разборах текста.



Функция Replace - замена всех вхождений подстроки


Это удивительно, что в наборе встроенных функций не было до сих пор функции Replace. Замена одной подстроки на другую - это одна из основных операций при работе со строками. Поэтому в арсенале практически каждого из программистов была своя версия функции Replace. Теперь можно пользоваться стандартной реализацией. Ее несомненным достоинством является то, что она позволяет заменить не только первое вхождение искомой подстроки, но и все такие вхождения, не требуя организации цикла. С другой стороны, можно ограничиться только заменой первого или нескольких первых вхождений. Рассмотрим синтаксис появившейся функции:

Replace(expression, find, replace[, start[, count[, compare]]])

Первый аргумент expression задает строковое выражение, результат которого определяет строку - источник, в которой осуществляется замена. Аргументы find и Replace задают заменяемую подстроку и ее новое значение. Аргумент Count определяет число замен. Обычно он равен 1, когда речь идет о замене первого вхождения, или опускается, - в этом случае его значение по умолчанию равно "-1", означающее замену всех вхождений. Аргумент compare имеет обычный смысл.

В качестве примера приведем функцию Rep, которую мы постоянно используем в нашей работе в процессе работы над этой книгой. Все примеры в нашей книге проверены в реальной работе, они копируются из модулей VBA. При копировании программного текста в документ Word появляются последовательности пробелов, отражающие структурную запись процедур и функций. Но появление в документах Word двух пробелов подряд считается смертельным грехом. Заменять пробелы табуляцией можно и руками, но, конечно же, лучше написать макрос, решающий эту и другие задачи по приведению текста в форму, требуемую редакцией. Вот текст функции, используемой для этих целей:

Public Sub Rep() 'Эта процедура преобразует выделенный программный текст 'Заменяя пробелы табуляцией и конец абзаца мягким концом строки Dim TxtRange As String

TxtRange = Selection.Range.Text 'Замена пробелов: 4-х, 3-х и 2-х символом табуляции TxtRange = Replace(TxtRange, " ", vbTab) TxtRange = Replace(TxtRange, " ", vbTab) TxtRange = Replace(TxtRange, " ", vbTab)


'Замена концов абзаца Selection.Range.Text = Replace(TxtRange, VBA.Chr(13), VBA.Chr(11))

End Sub

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

Public Function MyReplace(ByVal Expr As String, ByVal find As String, _ ByVal rep As String, Optional ByVal start As Long = 1, Optional ByVal count As Long = -1, _ Optional ByVal compare As VbCompareMethod = vbBinaryCompare) As String

'Вызов стандартной функции Replace If start = 1 Then MyReplace = replace(Expr, find, rep, start, count, compare) Else MyReplace = VBA.Left(Expr, start - 1) & replace(Expr, find, rep, start, count, compare) End If

End Function

Вот результаты нескольких вызовов Replace и MyReplace в окне отладки:

? Replace("A+B *(D*B +B)","B","C",4,1) *(D*C +B) ? MyReplace("A+B *(D*B +B)","B","C",4,1) A+B *(D*C +B) ? MyReplace("A+B *(D*B +B)","B","C",4) A+B *(D*C +C)



'Замена концов абзаца Selection.Range.Text = Replace(TxtRange, VBA.Chr(13), VBA.Chr(11))

End Sub

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

Public Function MyReplace(ByVal Expr As String, ByVal find As String, _ ByVal rep As String, Optional ByVal start As Long = 1, Optional ByVal count As Long = -1, _ Optional ByVal compare As VbCompareMethod = vbBinaryCompare) As String

'Вызов стандартной функции Replace If start = 1 Then MyReplace = replace(Expr, find, rep, start, count, compare) Else MyReplace = VBA.Left(Expr, start - 1) & replace(Expr, find, rep, start, count, compare) End If

End Function

Вот результаты нескольких вызовов Replace и MyReplace в окне отладки:

? Replace("A+B *(D*B +B)","B","C",4,1) *(D*C +B) ? MyReplace("A+B *(D*B +B)","B","C",4,1) A+B *(D*C +B) ? MyReplace("A+B *(D*B +B)","B","C",4) A+B *(D*C +C)


Функция Timer и хронометраж вычислений


Функция Timer возвращает значение типа Single, представляющее число секунд, прошедших после полуночи. Эта функция широко используется при проведении хронометража вычислений в программах. Чтобы повысить эффективность выполнения VBA программ зачастую приходится прибегать к проведению хронометража, чтобы выявить наиболее критичные по времени выполнения участки программ, а затем уже применять специальные меры для ускорения вычислений. Приведем сейчас пример, в котором анализируется время, затрачиваемое для выполнения операций над арифметическими данными разных подтипов.

Пример 8.7.

(html, txt)

Представим результаты вычислений в виде таблицы. Они представляют интерес сами по себе. Заметьте, что наибольшее время потребовалось для вычислений над самым коротким типом - Byte, а в целом вычисления над разными типами выполняются примерно с одинаковой скоростью.

Таблица 8.5. Время вычислений над данными разных типов

Тип / Номер Эксперимента 12345
Byte 0.3515625 0.359375 0.3320313 0.3320313 0.34375
Integer 0.2421875 0.2382813 0.2421875 0.2421875 0.2382813
Long 0.2890625 0.28125 0.28125 0.28125 0.28125
Single 0.2929688 0.2695313 0.28125 0.28125 0.28125
Double 0.3085938 0.2890625 0.2929688 0.2929688 0.28125



Математические функции


Набор математических функций VBA достаточно стандартный. Перечислим их с краткими пояснениями:

Abs(число) - абсолютное значение числа.Atn(число)- арктангенс (в радианах) аргумента, задающего тангенс угла.Cos(число) - косинус угла. Аргумент число задает угол в радианах.Exp(число) - экспонента, т. е. результат возведение числа e (основание натуральных логарифмов) в указанную степень.Log(число) - натуральный логарифм числа.Rnd[(число)] - результат представляет равномерно распределенное случайное число в интервале [0 - 1]. Если аргумент число не задан или больше нуля, то порождается очередное случайное число, если он равен 0, то результатом будет предыдущее случайное число, а если число меньше нуля, то всякий порождается одно и то же число, определяемое аргументом. Перед тем, как получить последовательность случайных чисел необходимо вызвать функцию Randomize для инициализации последовательности. Заметим, для формирования значения случайных чисел используется таймер. Чтобы получить целочисленную последовательность равномерно распределенных случайных чисел в интервале [Min - Max], следует использовать следующее преобразование

Int((Max - Min +1)*Rnd)+ Min

В примерах предыдущей лекции мы неоднократно использовали эту функцию для инициализации массивов с числовыми данными.

Sgn(число) - знак числа (Если число больше нуля - 1, равно нулю - 0, меньше нуля - -1).Sin(число) - синус угла. Аргумент число задает угол в радианах.Sqr(число) - квадратный корень.Tan(число) - тангенс угла. Аргумент число задает угол в радианах.

Во всех этих описаниях под аргументом функции число понимается числовое выражение.

В справочной системе или в любом математическом справочнике Вы сможете найти указания, как на основе этого базиса, можно вычислить большое число других математических функций. Например, вычисление гиперболического синуса производится по формуле:

HSin(X) = (Exp(X) - Exp(-X))/2

а вычисление арккотангенса:

Arccotan(X) = Atn(X) + 2*Atn(1)



Некоторые встроенные функции


Как мы уже говорили, встроенных функций множество. Помнить их всех не требуется, поскольку под рукой всегда есть справочная система. Для общего знакомства рассмотрим еще некоторые группы функций.



Несколько модификаций встроенных функций


Какие бы новые возможности не появлялись в системе, программист всегда будет создавать свой собственный инструментарий, расширяющий стандартные возможности. В предыдущих разделах мы уже приводили разработанные нами функции MyReplace, DelStr, являющиеся частью такого инструментария. Сейчас мы хотим привести еще несколько функций из нашего инструментального набора. Их приведение полезно и по той причине, что эти функции используют новые средства разбора строк. Мы приведем вариации стандартных функций Replace, Filter и Split.

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



Новые функции для работы со строками


В VBA 2000, как мы уже говорили, добавлены полезные функции для работы со строками.



Операции


В любом языке программирования допустимы выражения. Нужно уметь выражаться корректно. Выражения строятся из переменных, констант, встроенных функций с использованием знаков операций и скобок. Запись выражения задает правило (алгоритм) вычисления его значения и его типа. Естественно, что тип и значения всех его переменных должны быть определены до момента вычисления выражения. Языки программирования различаются между собой тем, до какой степени они допускают автоматическое преобразование типов данных в процессе вычисления выражения. Считается, что язык является более надежным, предохраняющим от многих ошибок программиста, если он (язык) не допускает автоматического преобразования типов. Конечно, здесь необходим разумный компромисс. Так все языки допускают при вычислении выражения X+Y вещественный тип для переменной X и целочисленный тип для переменной Y, проводя автоматическое преобразование к вещественному типу и выполняя, затем уже, сложение вещественных чисел. Язык VBA в этом отношении занимает золотую середину. Он не столь строг, как классические языки, предложенные Никласом Виртом - Паскаль, Модула, Оберон, но и не допускает особых вольностей. Среди его встроенных функций есть большое число функций, предназначенных для явного преобразования типов, что позволяет программировать в лучших традициях надежных языков программирования, не доверяя неявным преобразованиям.

Тема построения выражений обширна, достаточно сказать, что число встроенных функций измеряется не десятками, а сотнями. Подробное их описание не входит в нашу задачу, для этого есть справочная система, но общий обзор и некоторые детали мы постараемся привести.

Приведем основные операции, которые можно выполнять над данными в языке VBA, классифицируя их по типу и приоритету:

Таблица 8.1. Операции и их приоритет

ПриоритетАрифметическиеСравненияЛогическиеОписание некоторых операций
1Возведение в степень - (^)Равенство - (=)Отрицание - (Not)При возведении в степень основание и показатель могут быть арифметическими выражениями любого типа. Результат имеет тип Double.
2Унарный минус - (-)Неравенство - (<>)Конъюнкция - (And)
3Умножение, Деление - (*, /)Меньше - (<)Дизъюнкция - (Or)
4Деление нацело - (\)Больше - (>)Исключительное Или - (Xor)Деление нацело определено над целочисленными данными (применимо и к вещественным данным) и дает результат целого типа. Исключительное Или требует, чтобы один из операндов имел значение, отличное от True
5Остаток от деления нацело - (mod)Меньше или равно - (<=)Эквивалентность - (Eqv)Операция mod определена над данными целого типа и возвращает результат целого типа - остаток от деления нацело.
6Сложение, вычитание - (+,-)Больше или равно - (>=)Импликация - (Imp)Среди логических операций определена операция следования (импликация), ложная в единственном случае, когда посылка истинна, а заключение ложно.
7Конкатенация строк - (&)Подобия - (Like), Равенство ссылок - (Is) Операция Like проверяет соответствие строки образцу. Операция Is, определенная над объектами, не проверяет равенство самих объектов, она проверяет совпадение ссылок. Ссылки должны задавать один и тот же объект.

Если выражение содержит операции разных категорий, то первыми выполняются арифметические операции, затем, операции сравнения и последними - логические.

Все операции сравнения имеют один и тот же приоритет. Арифметические и логические операции выполняются в соответствии с указанным приоритетом.

Одна и та же операция, записанная несколько раз подряд, или операции одного приоритета (умножение и деление, сложение и вычитание) выполняются слева направо, - из двух операций первой выполняется та, которая стоит левее в записи выражения.

Скобки позволяют изменить указанный порядок вычислений выражения, поскольку выражения в скобках имеют наивысший приоритет и вычисляются первыми. Внутри скобок действует обычный порядок вычисления.

Операция конкатенации не является арифметической, но при ее появлении в выражении она выполняется после всех арифметических операций, но до вычисления операций сравнения.



Определение текущей даты или времени.


Date - возвращает текущую дату.Time - возвращает текущее время по часам компьютера.Now - возвращает значение типа Variant (Date), содержащее текущую дату и время по системному календарю и часам компьютера.



Основные операции над строками


В классической математике давным - давно определен набор основных операций над числовыми и булевыми данными. Строковой арифметикой серьезно стали заниматься с появлением компьютеров. Жесткий стандарт на эти операции еще не сложился. Как правило, во всех языках есть только одна операция, называемая конкатенацией строк, обозначаемая, обычно, символом "&". Все остальные основные операции реализуются с помощью встроенных функций, имена которых и их аргументы могут варьироваться от языка к языку. Более того, варьируется и сам набор этих операций. Минимально, помимо конкатенации необходимы еще две операции, первая из которых позволяет обнаружить индекс вхождения одной строки в другую, вторая - выделить из строки ее подстроку. VBA имеет достаточно мощный набор операций, который в Office 2000 существенно был расширен принципиально новыми возможностями. Рассмотрим вначале те операции, которые и ранее существовали в языке, а уж потом, чуть более подробно поговорим о новых возможностях.

Функция Len(string) возвращает длину строки, заданной аргументом String. Заметьте, возвращается число символов строки, а не число байтов. Ранее мы уже говорили, что в Office 2000 для внутреннего представления строк используется Unicode кодировка, в которой каждый символ занимает два байта.

Функция InStr определяет позицию (индекс) первого вхождения одной строки внутри другой строки. Синтаксис

InStr([start, ]string1, string2[, compare])

Необязательный аргумент start задает позицию, с которой начинается поиск (по умолчанию - с первого символа строки). Аргумент string1 задает строку, в которой выполняется поиск, а string2 - подстроку, вхождение которой ищется. Необязательный аргумент compare имеет тот же смысл, что и для функции StrComp. Возвращаемые значения определяются следующей таблицей.

Таблица 8.3. Результат работы функции InStr

УсловияЗначение функции InStr
string1 - пустая строка 0
string1 или string2 равны Null Пустое значение - Null
string2 - пустая строка start
Вхождение string2 не найдено в string1 0
Вхождение string2 найдено в string1 Позиция обнаруженной подстроки
start >Len(string2) 0


Например, два вызова этой функции в окне отладки вернут следующие результаты:

? InStr(4, "XXпXXпXXПXXП", "П", 1) 6 ? InStr(4, "XXпXXпXXПXXП", "П", 0) 9

Функция Left(string, length) выделяет в строке string указанное число length символов слева, позволяя выделить префикс строки.

Функция Right(string, length) выполняет аналогичную операцию, выделяя символы справа, что позволяет получить суффикс (окончание) строки.

Более универсальная функция Mid(string, start[, length]) позволяет выделить из строки string подстроку длины length, начиная с позиции start.

Вот пример вызова этих функций непосредственно из окна отладки:

? VBA.Left("рококо",3) рок ? VBA.Right("рококо",3) око ? VBA.Mid("рококо",3,3) кок

Функции LTrim(string), RTrim(string), Trim(string) возвращают копию строки, из которой удалены пробелы, находившиеся в начале строки (LTrim), в конце строки (RTrim) или в начале и конце строки (Trim).

Функция String создает строку: содержащую заданное число повторяющихся символов.

Синтаксис:

String(number, character)

Аргумент number задает длину строки, а character - код символа или строковое выражение, первый символ которого используется при создании результирующей строки.

Функции LCase(string) и UCase(string) возвращают копию строки, символы которой приведены к нижнему (Low) или верхнему регистру (Upper).

Функции, возвращающие строки, существуют в двух вариантах, отличающихся именами. Функции, имена которых мы приводили, возвращают результат типа Variant. Во втором варианте имена функций оканчиваются знаком $, например, Mid$, UCase$. В этом случае результат возвращается типа String. Такие функции выполняются быстрее, но не могут корректно работать со строками, имеющими значение Null.

Приведем в качестве примера полезную функцию, которая находит путь к активному документу Word, и производит разбор всех компонент этого пути:

Пример 8.2.

(html, txt)

Вот результаты отладочной печати после запуска процедуры MyPath:

E O2000\VBA2000\Ch8\ Ch8.doc E:\O2000\VBA2000\Ch8\

Обратите внимание, в функции AppPath мы использовали новую функцию InStrRev, что облегчило решение нашей задачи. К описанию вновь введенных функций мы и переходим.



'Определяем полный путь к файлу, задающему активный документ Word Set MyDoc = ActiveDocument Path = MyDoc.FullName

'Выделяем имя диска - первый символ полного пути Disk = VBA.Left(Path, 1)

'Выделяем каталог, в котором хранится документ Start = VBA.InStr(1, Path, "\") Finish = VBA.InStrRev(Path, "\") Dir = VBA.Mid(Path, Start + 1, Finish - Start)

'Выделяем имя файла FileName = VBA.Mid(Path, Finish + 1)

'Возвращается результат - полный путь к каталогу AppPath = VBA.Left(Path, Finish) End Function

Public Sub MyPath() Dim Path As String Dim Dir As String Dim Disk As String Dim FileName As String Path = AppPath(Disk, Dir, FileName) Debug.Print Disk, Dir, FileName, Path

End Sub

Пример 8.2.

Вот результаты отладочной печати после запуска процедуры MyPath:

E O2000\VBA2000\Ch8\ Ch8.doc E:\O2000\VBA2000\Ch8\

Обратите внимание, в функции AppPath мы использовали новую функцию InStrRev, что облегчило решение нашей задачи. К описанию вновь введенных функций мы и переходим.


Преобразование строки в массив. Функция Split


Предполагается, что исходная строка состоит из элементов (подстрок), разделенных специальными символами - разделителями. Функция Split возвращает одномерный массив из элементов строки. Ее синтаксис:

Split(expression[, delimiter[, limit[, compare]]])

Ее параметры:

expression - строковое выражение, результат которого задает строку - источник, состоящую из элементов и разделителей.delimeter - строка, задающая последовательность символов, используемых в качестве разделителя. Если этот параметр опущен, то по умолчанию предполагается, что в роли разделителя выступает пробел.limit - необязательный параметр, позволяющий ограничить число возвращаемых элементов. По умолчанию его значение равно -1, означающее выделение всех элементов.compare - необязательный параметр, имеющий стандартный смысл во всех операциях над строками. По умолчанию предполагается двоичное побитовое сравнение.

Приведем пример, в котором сложноподчиненное предложение разделяется на простые предложения:

Public Sub SplitString() 'В этой процедуре сложное предложение разделяется на простые 'Объявляем динамический массив Dim Simple() As String, i As Byte

'Размерность массива Simple устанавливается автоматически 'в соответствии с размерностью массива, возвращаемого функцией Split Simple = Split("А это пшеница, которая в темном чулане хранится в доме, " _ & "который построил Джек", ", ") For i = LBound(Simple) To UBound(Simple) Debug.Print Simple(i) Next i

End Sub

В результате работы этой процедуры создается массив из трех элементов:

А это пшеница которая в темном чулане хранится в доме который построил Джек

Обратите внимание, для использования результатов работы функции Split создается динамический массив строк. О размерности его нет нужды заботиться, поскольку в момент присвоения ему результата выполнения Split, он заполняется элементами и у него появляется нижняя и верхняя граница. Далее с этим массивом можно работать обычным образом.

У функции Split, на наш взгляд, есть один недостаток. Хотя в роли разделителя может выступать последовательность символов, в нашем примере это пара символов "запятая, пробел", но нельзя использовать разные разделители элементов. В задачах подобного рода типичной является ситуация, когда элементы отделяются разными разделителями, возможно, окруженными пробелами. Позже мы покажем, как можно справиться с этой задачей.



Преобразование типов данных


Для приведения данных к нужному типу в VBA включен обширный набор функций: CBool, CByte, CCur, CDate, CDbl, CDec, CInt, CLng, CSng, CStr, CVar, CVErr, Fix, Int.

Все они имеют следующий синтаксис:

ИмяФункции(выражение)

Обязательный аргумент выражение является любым строковым выражением или числовым выражением. В следующей таблице указаны типы возвращаемых значений и их диапазоны.

Таблица 8.7. Функции приведения к типу

ФункцияТипДиапазон аргумента выражение
CBool Boolean Любая допустимая строка или числовое выражение. Например, Cbool(0)= Cbool("00")= False, Cbool(1)=Cbool("-6")=True.
CByte Byte От 0 до 255.
CCur Currency От -922 337 203 685 477,5808 до 922 337 203 685 477,5807.Например, CCur("25,878755")= 25.8788, а CCur("25.878755") - ошибочный вызов, т. к. в строке отделять дробную часть должна запятая.
CDate Date Любое допустимое выражение даты. Например, Cdate(32444.5)=28.10.88 12:00:00, Cdate("17/08/1972")=17.08.72, Cdate("6:45:28 PM")= 18:45:28.
CDbl Double -4,94065645841247E-324 для отрицательных чисел; от 4,94065645841247E-324 до 1,79769313486232E308 для положительных чисел.
CDec Decimal +/-7,9228162514264337593543950335. Минимальное ненулевое число 0,0000000000000000000000000001.
CInt Integer От -32 768 до 32 767 с округлением дробной части.
CLng Long От -2 147 483 648 до 2 147 483 647 с округлением дробной части.
CSng Single От -3,402823E38 до -1,401298E-45 для отрицательных чисел; от 1,401298E-45 до 3,402823E38 для положительных чисел.
CVar Variant Диапазон значений Double для числовых значений. Диапазон значений String для нечисловых значений.
CStr String Возвращаемые значения функции CStr зависят от аргумента выражение. Например, CStr(True) = "Истина", CStr(122.344) = "122.344", CStr(#10/24/47#) = 24.10.47

Если переданное в функцию значение аргумента выражение находится вне допустимого диапазона для соответствующего типа данных, возникает ошибка.

Функция CVErr возвращает значение типа Variant с подтипом Error, содержащее код ошибки, указанный пользователем. Она используется для создания определяемых пользователем ошибок.

Если дробная часть числа равна 0,5, то функции CInt и CLng всегда округляют число до ближайшего четного числа. Например, Cint(0,5)= 0, а Cint(1,5)= 2.

Функции Fix и Int вычисляют целую часть числа (без округления). Они отличаются на отрицательных числах: Fix (-7.6) = -7, а Int(-7.6) = -8.



Dim res As Byte Dim


Public Sub LikeOperation() Const pat1 = "[A-Z]" Const pat2 = "[a-z]" Const pat3 = "[!a-z]" Const pat4 = "[3-5]" Dim res As Byte Dim Sym As String
res = "Кук" Like "К[аоу]к" Debug.Print res
res = "f" Like pat1 Debug.Print res res = "f" Like pat2 Debug.Print res res = "f" Like pat3 Debug.Print res
res = "5" Like pat4 Debug.Print res Sym = "3" res = Sym Like pat1 & pat4 Debug.Print res res = Sym Like pat1 Or Sym Like pat4 Debug.Print res
End Sub
Пример 8.1.
Закрыть окно




Public Function AppPath( Disk As String, Dir As String, FileName As String) As String 'Эта Функция возвращает в качестве результата полный путь активного документа 'Ее параметры содержат компоненты этого пути - имя диска, каталог на диске и имя файла Dim MyDoc As Document Dim Path As String Dim Start As Byte, Finish As Byte
'Определяем полный путь к файлу, задающему активный документ Word Set MyDoc = ActiveDocument Path = MyDoc.FullName
'Выделяем имя диска - первый символ полного пути Disk = VBA.Left(Path, 1)
'Выделяем каталог, в котором хранится документ Start = VBA.InStr(1, Path, "\") Finish = VBA.InStrRev(Path, "\") Dir = VBA.Mid(Path, Start + 1, Finish - Start)
'Выделяем имя файла FileName = VBA.Mid(Path, Finish + 1)
'Возвращается результат - полный путь к каталогу AppPath = VBA.Left(Path, Finish) End Function
Public Sub MyPath() Dim Path As String Dim Dir As String Dim Disk As String Dim FileName As String Path = AppPath(Disk, Dir, FileName) Debug.Print Disk, Dir, FileName, Path
End Sub
Пример 8.2.
Закрыть окно




Public Sub SplitAndJoin() ' В этой процедуре сложное предложение разделяется на простые 'А затем после обработки строка восстанавливается 'Здесь же демонстрируется фильтрация элементов массива 'Объявляем динамический массив Dim Simple() As String, i As Byte Dim Simple1() As String, Res As String Dim Simple2() As String 'Размерность массива Simple устанавливается автоматически 'в соответствии с размерностью массива, возвращаемого функцией Split Simple = Split("А это пшеница, которая в темном чулане хранится в доме, " _ & "который построил Джек", ", ") 'Создаем новый массив ReDim Simple1(1 To UBound(Simple) + 2) Simple1(1) = "А это веселая птица - синица" Simple1(2) = "которая часто ворует пшеницу" For i = 3 To UBound(Simple1) Simple1(i) = Simple(i - 2) Next i 'Создаем строку из массива Simple1
Res = Join(Simple1, ", ") Debug.Print Res 'Фильтрация элементов массива Simple2 = Filter(Simple1, "котор") Res = Join(Simple2, ", ") Debug.Print Res
Simple2 = Filter(Simple1, "котор", False) Res = Join(Simple2, ", ") Debug.Print Res End Sub
Пример 8.3.
Закрыть окно




Public Function WildReplace( ByVal expr As String, ByVal find As String, _ ByVal Rep As String, Optional ByVal delimiter As String = " ") As String
'Эта функция применима в том частном случае, когда строка - источник, заданная аргументом Expr 'представляет совокупность элементов, отделяемых разделителями. 'Типичный пример - строка представляет совокупность слов, разделенных пробелами. 'Как и обычная функция Replace эта функция производит замену вхождений элемента (подстроки) новым значением 'Отличие от стандартной функции состоит в том, что заменяемый элемент (подстрока) find 'задается шаблоном. В результате разные элементы могут быть заменены на новое значение. 'Для этого частного случая WildReplace существенно расширяет стандартные возможности Replace
'Алгоритм основан на том факте, что строка допускает разбор ее на элементы, 'а к элементам применима операция Like - сравнения с шаблоном.
Dim Words() As String, i As Long, Res As String
'Разбор строки на элементы Words = Split(expr, delimiter)
'Сравнение элементов с шаблоном и замена в случае совпадения For i = LBound(Words) To UBound(Words) If Words(i) Like find Then Words(i) = Rep Next i
'Сборка строки Res = Join(Words, delimiter) WildReplace = Res End Function
Пример 8.4.
Закрыть окно




Public Function WildFilter(sourcearray() As String, ByVal match As String, _ Optional ByVal include As Boolean = True) As Variant
'Эта функция фильтрует элементы исходного массива, проверяя их на соответствие шаблону match 'В отличие от стандартной функции Filter элементы проверяются на точное соответствие 'но шаблон match может включать специальные символы шаблона. 'Возвращается массив отобранных элементов. Если булев параметр include равен True 'то отбираются элементы, совпадающие с шаблоном, в противном случае - не совпадающие.
Dim Sel() As String Dim i As Long, j As Long j = 1 If include Then For i = LBound(sourcearray) To UBound(sourcearray) If sourcearray(i) Like match Then ReDim Preserve Sel(1 To j) Sel(j) = sourcearray(i): j = j + 1 End If Next i Else For i = LBound(sourcearray) To UBound(sourcearray) If Not (sourcearray(i) Like match) Then Sel(j) = sourcearray(i): j = j + 1 End If Next i End If WildFilter = Sel End Function
Пример 8.5.
Закрыть окно




Public Function WildSplit( expr As String, Optional ByVal delimiter As String = " ", _ Optional ByVal limit As Long = -1, Optional ByVal compare As VbCompareMethod = vbBinaryCompare) As Variant
'Также, как и стандартная функция Split, эта функция расщепляет строку - источник expr 'на элементы, используя разделители, заданные аргументом delimiter 'Отличие состоит в том, что при выделении элементов предполагается, что разделителем 'может быть любой из символов множества delimeter, возможно, окруженный пробелами
Dim Source As String, ResAr() As String Dim find As String, Rep As String Dim i As Long
Source = expr find = VBA.Mid$(delimiter, 2) Rep = VBA.Left$(delimiter, 1) 'Заменяем в строке все разделители на один из них Source = CharSetReplace(Source, find, Rep)
'Теперь используем стандартную функцию Split ResAr = Split(Source, Rep, limit, compare)
'Удаляем пробелы For i = LBound(ResAr) To UBound(ResAr) ResAr(i) = VBA.Trim$(ResAr(i)) Next i WildSplit = ResAr End Function
Пример 8.6.
Закрыть окно




Public Sub Speed() ' Эта программа выполняет замеры времени вычислений над данными разного типа
Dim Start As Single, Finish As Single Dim i As Long, j As Long Dim bx As Byte, by As Byte, bz As Byte Dim ix As Integer, iy As Integer, iz As Integer Dim lx As Long, ly As Long, lz As Long Dim sx As Single, sy As Single, sz As Single Dim dx As Double, dy As Double, dz As Double
For i = 1 To 5 'Внешний цикл для повторения замеров времени Start = Timer For j = 1 To 100000 bx = 99: by = 101 bz = by * (by - bx) \ by Next j Finish = Timer Debug.Print "Тип Byte: Время ", i, " = ", Finish - Start Debug.Print "bz = ", bz
Start = Timer For j = 1 To 100000 ix = 99: iy = 101 iz = iy * (iy - ix) \ iy Next j Finish = Timer Debug.Print "Тип Integer: Время ", i, " = ", Finish - Start Debug.Print "iz = ", iz
Start = Timer For j = 1 To 100000 lx = 99: ly = 101 lz = ly * (ly - lx) \ ly Next j Finish = Timer Debug.Print "Тип Long: Время ", i, " = ", Finish - Start Debug.Print "lz = ", lz
Start = Timer For j = 1 To 100000 sx = 99: sy = 101 sz = sy * (sy - sx) / sy Next j Finish = Timer Debug.Print "Тип Single: Время ", i, " = ", Finish - Start Debug.Print "sz = ", sz
Start = Timer For j = 1 To 100000 dx = 99: dy = 101 dz = dy * (dy - dx) / dy Next j Finish = Timer Debug.Print "Тип Double: Время ", i, " = ", Finish - Start Debug.Print "dz = ", dz
Next i
End Sub
Пример 8.7.
Закрыть окно



Присваивание значений


При присваивании значений переменным типа дата следует заключать дату в специальные ограничители "#" или задавать ее как строковую константу. При задании даты в ограничителях, например #9, May, 99 # она автоматически преобразуется к стандартному формату #5/9/99# (месяц/день/год). Вот пример некоторых действий над датами:

Public Sub WorkWithDates()

'Работа с датами Dim dat1 As Date, dat2 As Date, dat3 As Date

'Присваивание дат dat1 = 12 dat2 = 9 / 5 / 99 dat3 = #9/5/1999# Debug.Print dat1, dat2, dat3 dat1 = "15/7/99" dat2 = #5/9/1999# dat3 = dat3 + 100 Debug.Print dat1, dat2, dat3 If dat3 > dat2 Then Debug.Print dat3 - dat2 Else Debug.Print dat2 - dat3 End If

End Sub

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

11.01.1900 0:26:11 05.09.99 15.07.99 09.05.99 14.12.99 219

Прокомментируем результаты, поскольку некоторые из них могут вызывать недоумение. Остановимся на первых двух присваиваниях. Чтобы понять их, нужно вспомнить внутренне представление дат. В первом случае к начальной дате прибавляется 12 дней, отсюда и получается 11 января 1900 года. Во втором случае, поскольку выражение в правой части не заключено в ограничители, оно вычисляется как обычное арифметическое выражение, полученное дробное число воспринимается как время от начала суток, - оно и печатается. Обратите внимание, на третье присваивание, первое число воспринимается как номер месяца, для российских программистов здесь кроется источник возможных ошибок, поэтому лучше задавать даты, используя названия месяцев, которые автоматически будут преобразованы в соответствующий формат.

Далее в программе продемонстрированы некоторые возможности выполнения операций над датами - прибавление целого числа дней, сравнение дат, вычитание дат. Заметьте, вычитание дат дает разницу между ними, выраженную в днях.



Работа с числовыми данными


Арифметика в VBA представлена достаточно полно, Напомним, что арифметический тип подразделяется на подтипы:

Byte, Integer, Long - для представления целочисленных данных.Single, Double - для представления вещественных данных.Decimal - для представления чисел в форме с фиксированной точкой, что важно, в частности, для финансовых вычислений.Currency - специальный тип для представления денежных данных.Variant - обобщенный тип, позволяющий хранить и обрабатывать данные разного типа.

Возможные арифметические операции мы уже упомянули.

Пример работы с числовыми данными:

Public Sub WorkWithArithmetic() Dim X As Integer, Y As Integer Dim U As Single, V As Single Dim Z As Double U = 15.8: V = -6.5 Z = U / V: X = CInt(U / V): Y = U \ V Debug.Print X, Y, Z, U, V, X \ Y, X Mod Y, U Mod V

End Sub

Вот результаты печати в окне отладки:

-2 -2 -2,43076926011306 15,8 -6,5 1 0 4

Заметим, что хотя целочисленные операции возможны над вещественными данными, применять их не следует, поскольку это один из тех случаев, когда выполняются внутренние преобразования, точная интерпретация которых сложна, что и может, в конечном итоге, служить источником программистских ошибок.

Рассмотрим основные встроенные математические функции.



Работа с датами и временем


Для того, чтобы обеспечить программисту возможность корректно работать с датами и временем, VBA предоставляет специальный тип данных Date, хранящий дату и время. Над данными этого типа можно выполнять некоторые операции, но, конечно же, при работе с ними чаще всего используются специальные встроенные функции. Попытаемся коротко рассмотреть основные возможности работы с датами. Прежде всего, заметим, что возможный диапазон дат охватывает даты от 1.1.100 года до 1-го января 9999 года. Если говорить о внутреннем представлении дат, занимающих 4 байта памяти, то целая часть хранит число дней от некоторой начальной даты, дробная часть хранит время от полуночи. Начальной датой является 30-е декабря 1899 года. Благодаря такому внутреннему представлению сложение и вычитание целого числа воспринимается как прибавление или вычитание дней.



Работа со строками


Работать с текстами программисту, как правило, приходится значительно чаще, чем работать с числами. Поэтому следует хорошо представлять основные базисные операции над строками, которые в большинстве случаев реализуются с помощью встроенных функций. Заметим, что в VBA Office 2000 добавлены новые операции над строками, расширяющие возможности эффективной работы по преобразованию текста.



Разбор строки, допускающей разные разделители ее элементов. Функция WildSplit


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

Алгоритм реализации прост. Он использует ранее написанную функцию замены CharSetReplace, заменяя все разделители одним. После этого становится возможным применить стандартную функцию разбора. На заключительном этапе у элементов убираются пробелы слева и справа. Следует отметить и недостаток этой функции, - преобразование необратимо и последующее применение Join не позволяет восстановить исходную строку в первозданном виде. Приведем текст этой функции:

Пример 8.6.

(html, txt)

Приведем также тестовую процедуру, в которой выполняется разбор строки функцией WildSplit:

Public Sub testWildSplit() Dim Txt As String, Res As String Dim Items() As String

Txt = "a * b - (c+d)/v" Items = WildSplit(Txt, "+-*/") Res = Join(Items) Debug.Print Res End Sub

Вот результаты ее работы:

a b (c d) v

На этом мы закончим наш затянувшийся разговор о работе со строковыми данными.



Разбор строки. Функции Split, Join и Filter


Важную группу новых функций составляют функции, предназначенные для разбора текста. Их появление позволяет эффективно решать целый класс часто встречающихся задач при работе с текстами. Суть дела такова: текст, заданный строкой, зачастую, представляет совокупность структурированных элементов - абзацев, предложений, слов, скобочных выражений и так далее. При работе с таким текстом необходимо разделить его на элементы, пользуясь тем, что есть специальные разделители элементов, - это могут быть пробелы, скобки, знаки препинания. Практически подобная задача возникает постоянно при работе со структурированными текстами. Новые функции существенно облегчают решение подобных задач. Функция Split позволяет разделить строку на элементы и создать массив из этих элементов. Функция Filter позволяет выделить нужные элементы в этом массиве, а функция Join решает обратную задачу, преобразуя массив в строку. Рассмотрим эти функции подробнее.



Сборка элементов массива в строку. Функция Join


Функция Join решает обратную задачу, - она восстанавливает строку по ее элементам, хранящимся в массиве, добавляя разделители в момент их объединения. Ее синтаксис:

Join(sourcearray[, delimiter])

Параметры ее понятны без особых пояснений. Заметим, что если необязательный аргумент delimiter опущен, то элементы разделяются пробелами. В качестве примера, приведем обратную сборку сложного предложения:

Public Sub SplitAndJoin() 'В этой процедуре сложное предложение разделяется на простые 'А затем после обработки строка восстанавливается 'Объявляем динамический массив Dim Simple() As String, i As Byte Dim Simple1() As String, Res As String 'Размерность массива Simple устанавливается автоматически 'в соответствии с размерностью массива, возвращаемого функцией Split Simple = Split("А это пшеница, которая в темном чулане хранится в доме, " _ & "который построил Джек", ", ") 'Создаем новый массив ReDim Simple1(1 To UBound(Simple) + 2) Simple1(1) = "А это веселая птица - синица" Simple1(2) = "которая часто ворует пшеницу" For i = 3 To UBound(Simple1) Simple1(i) = Simple(i - 2) Next i 'Создаем строку из массива Simple1

Res = Join(Simple1, ", ") Debug.Print Res End Sub

Вот результат отладочной печати:

А это веселая птица - синица, которая часто ворует пшеницу, которая в темном чулане хранится в доме, который построил Джек



Сравнение с образцом


Мощным и весьма полезным средством при работе с текстами является операция Like, задающая сравнение с образцом. Необходимость нахождения в наборе всех строк, удовлетворяющих некоторому шаблону (образцу), возникает в самых разнообразных задачах. VBA позволяет решать ее в одну операцию. Приведем таблицу специальных символов, допустимых при задании образца.

Таблица 8.2. Специальные символы, используемые при задании шаблона

СимволыИнтерпретацияПримеры
* Любой текст - произвольное число символов Шаблону Agent* соответствуют все тексты, начинающиеся со слова Agent. Строки Agent007 и Agent Майор Пронин удовлетворяют шаблону.
? Один любой символ Шаблону К?к удовлетворяют, в частности строки Кок и Кук.
# Любая цифра от 0 до 9 Шаблону Agent### соотвествуют 1000 различных строк, среди которых и Agent007, но, конечно же, не Agent Майор Пронин.
[множество_символов] Любой символ, принадлежащий множеству Задать множество можно с помощью перечисления и интервалов. Шаблону К[аоу]к удовлетворяют слова "Как", "Кок", "Кук". Чувствительность к регистру зависит от установки опции Option Compare.
[!множество_символов] Любой не принадлежащий множеству символ Шаблону [!а-я] удовлетворяет символ, не являющийся буквой русского алфавита.

Приведем пример работы с операцией Like:

Пример 8.1.

(html, txt)

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

255 0 255 0 255 0 255

Обратите внимание на последние два результата, демонстрирующие некорректный и корректный способы работы с объединением множеств проверяемых символов.



Сравнение строк


Обычные операции сравнения применимы и к строковым данным. Мы уже говорили ранее о том, что интерпретация этих операций зависит от установки опции Option Compare.

Если эта опция установлена как Text, то сравнение на "больше - меньше" представляет лексикографическое сравнение, когда строки сравниваются по их расположению в словаре. Заметьте, это сравнение не чувствительно к регистру, так что большие и малые буквы не различаются. Программисты, конечно, понимают, что сравнение строк означает сравнение кодов их символов, так что лексикографический порядок определяется кодировкой символов алфавита.Если эта опция установлена как Binary, то сравнение идет побитно. В этом случае сравнение естественно, чувствительно к регистру.При работе со строками в Access по умолчанию применяется сортировка, заданная на строках базы данных Access. Заметьте, при создании модуля в Access по умолчанию вставляется опция Option Compare Database. Конечно, эта опция применима только при работе в Access.

Если нужно локально переопределить вид сравнения, заданный опцией для всего модуля, то можно использовать встроенную функцию StrComp, которая возвращает результат сравнения строк. Ее синтаксис:

StrComp(string1, string2[, compare])

Аргументы string1 и string2 - сравниваемые строки. Необязательный аргумент compare указывает способ сравнения строк: значение по умолчанию 0 используется, чтобы выполнить двоичное сравнение, 1 задает посимвольное сравнение без учета регистра.

Если string1 меньше чем string2, то результат равен -1, если строки равны, то - 0, если вторая меньше, то - 1, если хоть одна из строк имеет значение Null, то результат также равен Null.



Удаление подстроки


Удаление вхождений подстроки в строку также является одной из основных операций над строками. Хотя специальной встроенной функции DelStr не появилось, но написать ее реализацию, имея Replace, совсем просто. Удалить подстроку эквивалентно замене ее пустой подстрокой. Вот текст, написанной нами функции DelStr:

Public Function DelStr(ByVal Expr As String, ByVal find As String, _ Optional ByVal start As Long = 1, Optional ByVal count As Long = -1, _ Optional ByVal compare As VbCompareMethod = vbBinaryCompare)

'Вызов функции MyReplace с пустой строкой для замены DelStr = MyReplace(Expr, find, "", start, count, compare) End Function

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

? DelStr("T* + U** - V*","*") T + U - V ? DelStr("T* + U** - V*","*",5) T* + U - V ? DelStr("T* + U** - V*","*",5,1) T* + U* - V*

Заметьте, при определении DelStr мы предпочли пользоваться собственной модификацией MyReplace, поскольку, как нам кажется, она в большей степени отвечает сути дела.



Встроенные функции для работы с датами


Как мы уже говорили, основная работа с датами выполняется с использованием встроенных функций. Рассмотрим коротко их описание и назначение.



Вычисления над датами


Функция DateAdd предназначена для добавления или вычитания указанного временного интервала из значения даты. Заметьте, с ее помощью можно задавать временной интервал не только в днях, но и в месяцах, годах и так далее.

DateAdd(interval, number, date)

Аргумент interval - строка, указывающая тип добавляемого временного интервала, number - число временных интервалов, на которое следует изменить дату, date - дата, к которой добавляется указанный временной интервал. Допустимые значения аргумента interval: приведены в следующей таблице.

Таблица 8.4. Возможные временные интервалы

ЗначениеОписание
yyyy Год.
Q Квартал.
m Месяц.
Y День года.
D День месяца.
w День недели.
ww Неделя.
H Часы.
N Минуты.
S Секунды.

Для примера, приведем два вызова этой функции в окне отладки:

? DateAdd("m", 1, "31-янв-95") 28.02.95 ? DateAdd("m", -1, "31-янв-95") 31.12.94

Функция DateDiff предназначена для определения времени, прошедшего между двумя датами. Например, с помощью этой функции можно вычислить число дней между двумя датами или число недель между текущей датой и концом года. Синтаксис:

DateDiff(interval, date1, date2[, firstdayofweek[, firstweekofyear]]) Аргумент interval задает тип временного интервала при вычислении разности между датами date1 и date2, - его возможные значения те же, что и для функции DateAdd,date1 и date2 - две даты, разность между которыми следует вычислить.firstdayofweek - константа, указывающая первый день недели (по умолчанию считается, что неделя начинается с воскресенья).firstweekofyear - константа, указывающая первую неделю года (по умолчанию первой неделей считается неделя, содержащая 1 января).

Приведем примеры вызова этой функции в окне отладки:

? DateDiff("m", "18.10.55", "31-янв-95") 471 ? DateDiff("Y", "18.11.97", "01.01.97") -321

Функция DatePart предназначена для определения указанного компонента даты. Например, с помощью этой функции можно определить день недели или текущий час.
Синтаксис:

DatePart(interval, date[,firstdayofweek[, firstweekofyear]]) Аргумент interval задает тип возвращаемого временного интервала,date - дата, подлежащая обработке.Необязательные аргументы Firstdayofweek и Firstweekofyear имеют тот же смысл, что и для функции DateDiff.

Примеры вызова:

? DatePart("ww", "02.09.89") 35 ? DatePart("w", "02.09.89") 7

Функция DateSerial позволяет вычислить значение даты типа Variant (Date) по ее компонентам, - году, месяцу и дню. Ее синтаксис:

DateSerial(year, month, day)

Значение каждого аргумента должно лежать в соответствующем диапазоне: 100 -- 9999 для года, 1 - 31 для дней и 1 - 12 для месяцев. Можно также использовать для аргументов числовые выражения для описания относительной даты.

Примеры:

? DateSerial(1997 - 25, 10 - 2, 18 - 1) 17.08.72 ? DateSerial(Year(Now) - 3,Month(Now) - 2, Day(Now) - 1) 08.03.96

Функция DateValue переводит аргумент-строку в дату. В отличие от функции преобразования CDate, функция DateValue правильно обрабатывает допустимые даты, содержащие полные или краткие названия месяцев.

Ее синтаксис:

DateValue(date)

Аргумент date может задавать как дату, так и время. Возможные разные форматы задания даты, в том числе и с названиями месяцев:

Примеры:

? DateValue ("25.04.1997") 25.04.97 ? DateValue ("04.25.1997") 25.04.97 ? DateValue ("25 апреля 1997") 25.04.97 ? DateValue ("25 - апр.-97") 25.04.97 ? DateValue ("Апрель, 25, 97") 25.04.97 ? DateValue ("25/04/1997") 25.04.97 ? DateValue ("25.04") 25.04.99

Последнее равенство связано с тем: что при отсутствии года, функция DateValue использует текущий год по системному календарю компьютера.

Функции Day(date), Month(date), и Year(date) являются, в некотором смысле, обратными к двум предыдущим. Они по аргументу-дате определяют номер дня, месяца и года. Функция Weekday(date, [firstdayofweek]) возвращает значение типа Variant (Integer), содержащее целое число, представляющее день недели.


Второй аргумент задает первый день недели (по умолчанию - воскресенье). Значение 2 соответствует понедельнику.

Функции Hour(время), Minute(время) и Second(время) по аргументу, являющемуся числовым или строковым выражением, представляющим время, возвращает содержащее целое число, которое представляет, соответственно, часы, минуты и секунды в значении времени.

Функция TimeSerial(hour, minute, second) вычисляет результат Variant (Date), содержащее значение времени, соответствующее указанным часу, минуте и секунде.

Функция TimeValue(время) возвращает значение типа Variant (Date), содержащее время. Аргумент время обычно задается строковым выражением, представляющим время от 0:00:00 (12:00:00 A.M.) до 23:59:59 (11:59:59 P.M.) включительно. Задавать его нужно корректно, чтобы не возникало ошибок, как в последних двух примерах:

? Year(Now) 1999 ? Month(now) 5 ? Day(Now) 9 ? Hour(Now) 11 ? Minute (Now) 57 ? Second(Now) 28 ? TimeSerial(Hour(Now), Minute(now), Second(Now)) 11:59:00 ? TimeValue("12:30 PM") 12:30:00 ? TimeValue("12.30") 0:00:00 ? TimeValue("12,5") 0:00:00


Замена, основанная на шаблоне. Функция WildReplace


Важным частным случаем, когда легко реализовать замену, основанную на шаблоне, представляют строки, допускающие разбор их на элементы. В этом случае алгоритм замены понятен, - строка разбирается на элементы, используя новые возможности, предоставляемые функцией Split; затем к каждому элементу применяется операция Like для сравнения с шаблоном; на последнем шаге применяется Join для восстановления строки. Приведем текст нашей функции:

Пример 8.4.

(html, txt)

Параметры этой функции имеют тот же смысл, что и для стандартной функции Replace. Существенное отличие состоит в том, что специальные символы шаблона допустимы в строке поиска find. Приведем теперь тестовую процедуру, осуществляющую вызов функции WildReplace:

Public Sub testWildReplace() Dim Inf As String, SecretInf As String Inf = "Agent001, Agent007, Агент Майор Пронин, Agent008" SecretInf = WildReplace(Inf, "Agent###", "Agent***", ", ") Debug.Print SecretInf End Sub

Вот результаты ее работы:

Agent***, Agent***, Агент Майор Пронин, Agent***



Замена разных символов строки. Функция CharSetReplace


Другим важным случаем, допускающим простую реализацию, является случай, когда замене подлежат вхождения отдельного символа, а не целой подстроки, как в общем случае. Наша функция производит замену разных символов из заданного множества на один и тот же символ. Алгоритм основан на обычном линейном просмотре и посимвольном сравнении. Сама замена производится путем выделения и конкатенации.

Эта функция понадобилась нам для реализации нового варианта функции расщепления строки, хотя она полезна и сама по себе. Приведем ее текст:

Public Function CharSetReplace(ByVal expr As String, ByVal find As String, _ ByVal Rep As String)

'Эта функция применима в том частном случае, когда в строке - источнике Expr 'требуется произвести замену символов, входящих в множество, заданное аргументом find 'на символ (последовательность символов), заданных аргументом rep

Dim Source As String Dim i As Long, Sym As String Dim N As Long

N = Len(expr) Source = expr For i = 1 To N Sym = VBA.Mid$(Source, i, 1) If InStr(1, find, Sym) Then Source = VBA.Left$(Source, i - 1) & Rep & VBA.Mid$(Source, i + 1) End If Next i CharSetReplace = Source End Function

Приведем теперь тестовую процедуру, организующую вызов функции CharSetReplace:

Public Sub testCharSetReplace() Dim Text As String Text = "test123" Text = CharSetReplace(Text, "1234567890", "*") Debug.Print Text Text = "Шла кошка по крыше" Text = CharSetReplace(Text, "аоеиуыяюэ", "*") Debug.Print Text End Sub

В результате ее работы в окне отладки появляются следующие данные:

Шл* к*шк* п* кр*ш* Agent***, Agent***, Агент Майор Пронин, Agent***