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

Операции и встроенные функции

Разбить на страницы
Показывать лекцию целиком

Программный код большинства примеров данной лекции можно найти в проектах, доступных для просмотра: DocOne8, DocTwo8.

Операции

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

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

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

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

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

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

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

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

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

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

Арифметика в 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 достаточно стандартный. Перечислим их с краткими пояснениями:

  • 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)

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

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

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

    Обычные операции сравнения применимы и к строковым данным. Мы уже говорили ранее о том, что интерпретация этих операций зависит от установки опции 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.

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

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

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

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

    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

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

    255 
     0 
     255 
     0 
     255 
     0 
     255

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

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

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

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

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

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

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

    Результат работы функции 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, и производит разбор всех компонент этого пути:

    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

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

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

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

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

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

    Функция 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)

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

    Удаление вхождений подстроки в строку также является одной из основных операций над строками. Хотя специальной встроенной функции 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, поскольку, как нам кажется, она в большей степени отвечает сути дела.

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

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

    Преобразование строки в массив. Функция 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, на наш взгляд, есть один недостаток. Хотя в роли разделителя может выступать последовательность символов, в нашем примере это пара символов "запятая, пробел", но нельзя использовать разные разделители элементов. В задачах подобного рода типичной является ситуация, когда элементы отделяются разными разделителями, возможно, окруженными пробелами. Позже мы покажем, как можно справиться с этой задачей.

    Сборка элементов массива в строку. Функция 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

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

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

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

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

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

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

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

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

    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

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

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

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

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

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

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

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

    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

    Параметры этой функции имеют тот же смысл, что и для стандартной функции 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***

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

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

    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

    Параметры этой функции сохраняют смысл стандартной функции 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

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

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

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

    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

    Приведем также тестовую процедуру, в которой выполняется разбор строки функцией 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

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

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

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

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

    При присваивании значений переменным типа дата следует заключать дату в специальные ограничители " # " или задавать ее как строковую константу. При задании даты в ограничителях, например #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 года. Во втором случае, поскольку выражение в правой части не заключено в ограничители, оно вычисляется как обычное арифметическое выражение, полученное дробное число воспринимается как время от начала суток, - оно и печатается. Обратите внимание, на третье присваивание, первое число воспринимается как номер месяца, для российских программистов здесь кроется источник возможных ошибок, поэтому лучше задавать даты, используя названия месяцев, которые автоматически будут преобразованы в соответствующий формат.

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

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

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

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

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

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

    DateAdd(interval, number, date)

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

    Возможные временные интервалы
    Значение Описание
    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

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

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

    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

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

    Время вычислений над данными разных типов
    Тип / Номер Эксперимента 1 2 3 4 5
    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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Функции приведения к типу
    Функция Тип Диапазон аргумента выражение
    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.

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

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

    Функция 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

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

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

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

    Программный код большинства примеров данной лекции можно найти в проектах, доступных для просмотра: DocOne8, DocTwo8.

    Операции

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

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

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

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

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

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

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

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

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

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

    Арифметика в 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 достаточно стандартный. Перечислим их с краткими пояснениями:

  • 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)

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

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

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

    Обычные операции сравнения применимы и к строковым данным. Мы уже говорили ранее о том, что интерпретация этих операций зависит от установки опции 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.

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

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

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

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

    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

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

    255 
     0 
     255 
     0 
     255 
     0 
     255

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

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

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

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

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

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

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

    Результат работы функции 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, и производит разбор всех компонент этого пути:

    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

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

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

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

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

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

    Функция 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)

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

    Удаление вхождений подстроки в строку также является одной из основных операций над строками. Хотя специальной встроенной функции 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, поскольку, как нам кажется, она в большей степени отвечает сути дела.

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

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

    Преобразование строки в массив. Функция 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, на наш взгляд, есть один недостаток. Хотя в роли разделителя может выступать последовательность символов, в нашем примере это пара символов "запятая, пробел", но нельзя использовать разные разделители элементов. В задачах подобного рода типичной является ситуация, когда элементы отделяются разными разделителями, возможно, окруженными пробелами. Позже мы покажем, как можно справиться с этой задачей.

    Сборка элементов массива в строку. Функция 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

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

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

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

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

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

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

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

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

    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

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

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

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

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

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

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

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

    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

    Параметры этой функции имеют тот же смысл, что и для стандартной функции 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***

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

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

    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

    Параметры этой функции сохраняют смысл стандартной функции 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

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

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

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

    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

    Приведем также тестовую процедуру, в которой выполняется разбор строки функцией 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

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

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

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

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

    При присваивании значений переменным типа дата следует заключать дату в специальные ограничители " # " или задавать ее как строковую константу. При задании даты в ограничителях, например #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 года. Во втором случае, поскольку выражение в правой части не заключено в ограничители, оно вычисляется как обычное арифметическое выражение, полученное дробное число воспринимается как время от начала суток, - оно и печатается. Обратите внимание, на третье присваивание, первое число воспринимается как номер месяца, для российских программистов здесь кроется источник возможных ошибок, поэтому лучше задавать даты, используя названия месяцев, которые автоматически будут преобразованы в соответствующий формат.

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

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

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

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

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

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

    DateAdd(interval, number, date)

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

    Возможные временные интервалы
    Значение Описание
    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

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

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

    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

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

    Время вычислений над данными разных типов
    Тип / Номер Эксперимента 1 2 3 4 5
    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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Функции приведения к типу
    Функция Тип Диапазон аргумента выражение
    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.

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

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

    Функция 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

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

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

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