Visual2000 · Архив статей А.Колесова & О.Павловой
Андрей Колесов
© 1999, Андрей КолесовVBP-проекты примеров, приведенных в данной статье, вы можете найти на Web-узле по адресу: www.visual.2000.ru/develop/vb/source/.
Visual Basic включает достаточно большой набор встроенных функций обработки строковых переменных: преобразование, сравнение, поиск и пр. Многие из них обсуждались в первой части статьи и целом ряде наших Советов, опубликованных ранее. В VB 6 появилась большая группа дополнительных функций, о которых и пойдет речь ниже. Но сначала вспомним об одном операторе, который появился в VB уже довольно давно (еще в версии 3), но о нем почему-то знают далеко не все пользователи VB.
Cинтаксис этого оператора выглядит следующим образом:
result = expression Like pattern
где expression — символьное выражение, а pattern — символьный шаблон. Если выражение "соответствует" шаблону, то результат равен истине (True), в противном случае — лжи (False). Если выражение или шаблон равны Null (нулевая строка), то и результат будет равнен Null. Для создания шаблонов можно использовать набор символов обобщения:
Символ | Операция соответствия |
? | любой одиночный символ |
* | множество (в том числе и пустое) символов |
# | любой одиночный цифровой символ (0-9) |
[НаборСимволов] | любой набор одиночных символов |
[!НаборСимволов] | любой символ, который не входит в указанный набор |
Для задания набора символов можно использовать знак дефиса для определения диапазона (но обязательно в порядке возрастания ANSI-кодов). При этом допускается применение нескольких диапазонов. Например, [a-zA-Z0-9] позволит выбрать все алфавитно-цифровые символы для английского языка. Варианты использования разных типов сравнения приведены в таблице 1.
Оператор Like можно использовать также в SQL-запросах, например, так:
SELECT * FROM Employees WHERE LastName Like "[A-D]"
В этом случае будут выбраны записи о сотрудниках, фамилии которых начинаются с букв A, B, C, D.
Сортировка символов и учет чувствительности регистра (строчные или прописные буквы) выполняется на основе установки оператора Option Compare (по умолчанию он равен 0 — Binary) в данном программном модуле. Это нужно иметь в виду, потому что другие функции сравнения (InStr, StrComp) позволяют указывать режим сравнения (режим поиска: двоичный, текстовый, специальный) непосредственно в качестве параметра функции. Поэтому, если вы хотите управлять такими режимами самостоятельно (например, в одном модуле желательно иметь и тот, и другой вариант), то можно предложить такое решение.
Сделайте два отдельных BAS-модуля следующего содержания:
Option Compare 0 ' Binary, двоичный Public Function LikeBinary(expression$, pattern$) As Boolean LikeBinary = expression$ Like pattern$) End Function
Option Compare 1 ' Text, текстовый Public Function LikeText(expression$, pattern$) As Boolean LikeText = expression$ Like pattern$) End Function
Далее обращайтесь соответственно к функциями LikeBinary или LikeText. Следует иметь в виду, что правила сравнения для национальных языков определяются текущей кодовой таблицей (для VB до версии 5 включительно) или региональными установками Windows (для VB 6). Подробнее об этом — см. 1-ю часть статьи. Для реализации "нечувствительного к регистру" режиму поиска можно использовать и такой простой вариант:
result = UCase$(expression) Like pattern
Но при этом нужно отметить, что для сортировки символьных переменных с помощью функции StrConv такой вариант не очень подходит, если вы используете русскую букву "ё". При двоичном сравнении "ё" попадает в конец списка, после "я". При текстовом же сравнении обеспечивается не только нечувствительность к регистру, но и перемещение "ё" на свое законное место — между "е" и "ж". Для оператора Like это также актуально, если вы используете поиск по шаблону с использованием диапазонов. В частности, выполнение строки
Print "ёж" Like "[а-я]*
приведет к получению результата True для режима Option Compare Text и результата False для Option Compare Binary.
Это тем более существенно, если вы работаете с расширенными символами европейских языков (разные "а" с диакрическими знаками — крышкой, точкой и прочие знаки над основным символом.), Более того, для некоторых европейских языков в режиме текстового поиска определенные двухсимвольные комбинации обрабатываются как одна уникальная буква (например, в испанском языке ch является одной буквой и ставится при сортировке между c и d). И наоборот, один символ преобразуется в два знака, в частности в немецком языке символ eszett эквивалентен ss:
В VB 6 появились три группы дополнительных функций по обработке символьных переменных. Их краткое описание приведено в таблицах 2-4, а с более подробными сведениями о них можно познакомиться в документации о системе.
Конечно, эти новые функции будет полезны при разработке программ, хотя лично я довольно скептически отношусь к идее подобного расширения состава функций. На самом деле все эти новшества могут быть довольно легко реализованы средствами VB, имеющимися даже в версии 3 (а может быть, даже в более ранней — с версиями 1 и 2 я не знаком). Их появление в составе VB может произвести впечатление на новичка, но не на опытного программиста. Проблема-то заключается в том, что для более сложных операций обработки строк все равно потребуется "ручное кодирование" собственных процедур.
В этом плане было бы гораздо полезнее, если бы Microsoft вместо довольно бессмысленной гонки по расширению встроенных функций реализовала простой механизм подключения повторно используемых процедур на уровне объектных OBJ- библиотек (что было сделано в свое время еще 15 лет назад в MS QuickBasic для DOS). Но эти рассуждения, конечно, из области ностальгических воспоминаний: Microsoft — "большой, ему видней". Корпорация категорически не желает давать VB- программистам возможность использования OBJ-библиотек.
Далее мы попробуем реализовать некоторые альтернативные варианты новых символьных функций VB 6, имея в виду следующие цели:
Попробуем написать программу для решения следующей задачи. Имеется символьная строка с полями данных и нужно сформировать новую строку, собрав в нее из исходной только те поля, которые содержат заданный ключ. Например, эта задача может выглядеть так:
' Исходная строка: список животных Animals$ = "Белая Собака,Черная Лошадь,Красная Кошка,_ черная птица,Черный ризеншнауцер,голубая кошка" Delim1$ = "," ' разделитель в исходной строке Search$ = "черн" ' ключ поиска vbComp% = vbTextCompare ' режим поиска — текстовый Delim2$ = "//" ' разделитель в результирующей строке 'обращение к процедуре, решающей поставленную задачу: Result$ = Test$(Animals$, Delim1$, Search$, vbComp%, Delim2$) Print Result$ ' должно быть напечатано: ' Черная Лошадь//черная птица//Черный ризеншнауцер
С использованием новых функций VB 6 написать нужную процедуру достаточно просто:
Public Function Test1$ _ (Source$, Delim1$, Search$, vbComp%, Delim2$) Dim arr1$(), arr2$() ' преобразуем строку в массив. ' разделитель — Delim1$ arr1$ = Split(Source$, Delim1$) ' Из массива выделяем поля со словом Search$ ' в режиме сравнения vbComp% arr2$ = Filter(arr1$, Search$, , vbComp%) ' объединение массива в одну строку Test1$ = Join(arr2, Delim2$) End Function
При использовании традиционные возможности Basic, которые имелись еще в версиях 15-летней давности, реализация такого алгоритма будет выглядеть посложнее (см. листинг 1). Однако самое удивительное, что второй вариант работает в 1,5 раза быстрее, чем первый (см. рис.1)!
Рис. 1
Почему это происходит, понятно — сами функции Split, Filter и Join работают достаточно быстро, но постоянное создание динамических массивов требует довольно много времени. Так что, если скорость обработки действительно важна (например, при работе с большими объемами данных), то может быть имеет смысл потратить лишних 10 минут, чтобы написать не 4, а 16 строк программного кода.
Для иллюстрации идеи реализации различных алгоритмов преобразования символьных строк, предлагаю посмотреть на несколько вариантов процедур, которые являются аналогами новых встроенных функций StrReverse, InstrRev и Replace (соответственно листинги 2, 3 и 4). Время их выполнения приведено в таблице 5.
В качестве комментария к этим программам нужно отметить следующие моменты:
1. Замена кода (функция ReverseString):
For i = Len(Source$) To 1 Step -1 Reverse$ = Reverse$ + Mid$(Source$, i, 1) Next
на код (функция ReverseStringMy):
Reverse$ = Space$(Len(Source$)) For i = Len(Source$) To 1 Step -1 j = j + 1: Mid$(Reverse$, j, 1) = Mid$(Source$, i, 1) Next
приводит к увеличению быстродействия на 25% (использование коррекции переменной оператором Mid, вместо слияния двух переменных). Однако быстрее всего работает вариант с использование байтового массива ReverseStringByteMy. Но тут надо обратить внимание, что инверсия строки обеспечивается переносом пары байтов (один символ занимает два байта).
2. Замена кода (RevInstrByteMy)
bytArray = Source$ iBytes = Len(Source$) * 2 - 2 For i = iBytes To 0 Step -2 If Chr(bytArray(i)) = SubString Then RevInstrByteMy = i / 2 + 1: Exit Function End If Next
на код (RevInstrByteMy2):
bytArray = Source$: bytSub = Asc(SubString) iBytes = Len(Source$) * 2 - 2 For i = iBytes To 0 Step -2 If bytArray(i) = bytSub Then RevInstrByteMy2 = i / 2 + 1: Exit Function End If Next
увеличивает производительность в 3,5 раза (рис. 2).
Рис. 1
В этом случае скорость повышается за счет перехода к использованию байтового массива. Интересно, что оба эти варианта не являются точными аналогами InstrRev — они работают только в том случае, если разделитель является односимвольной переменной, причем с ASCII-кодом из нижней части таблицы (< 128). А универсальный вариант InstrReverseMy работает с любыми данными и что интересно — быстрее всех.
3. Быстродействие моего варианта ReplaceMy тоже не сильно уступает фирменному Replace.
Тип соответствия | Шаблон | Соответствуют шаблону (результат = True) | Не соответствуют шаблону (результат = False) |
Несколько символов | a*a | aa, aBa, aBBBa | aBC |
*ab* | abc, AABB, Xab | aZb, bac | |
Указанный символ | a[*]a | a*a | aaa |
Единичный символ | a?a | aaa, a3a, aBa | aBBBa |
Единичная цифра | a#a | a0a, a1a, a2a | aaa, a10a |
Диапазон символов | [a-z] | f, p, j | 2, & |
Вне диапазона | [!a-z] | 9, &, % | b, a |
Не цифра | [!0-9] | A, a, &, ~ | 0, 1, 9 |
Комбинированный вариант | a[!b-m]# | An9, az0, a99 | abc, aj0 |
Имя функции | Описание | Синтаксис |
MonthName | Возвращает строку, содержащую указанный месяц. | MonthName(month[, abbreviate]) |
WeekdayName | Возвращает строку, содержащую указанный день недели. С помощью этой функции можно задать день недели, который будет являться ее началом. | WeekdayName(weekday, abbreviate, firstdayofweek) |
FormatCurrency | Возвращает выражение в виде денежной единицы вместе с символом валюты, который определен в системной панели управлени | FormatCurrency(Expression[, NumDigitsAfterDecimal[, IncludeLeadingDigit[, UseParensForNegativeNumbers[, GroupDigits]]]]) |
FormatDateTime | Возвращает выражение в виде даты или времени. | FormatDateTime(Date[, NamedFormat]) |
FormatNumber | Возвращает выражение в виде отформатированного числа. Предоставляет возможность добавлять нули слева от числа, использовать круглые скобки для отрицательных чисел и выбирать точность возвращаемого числа. | FormatNumber(Expression[, NumDigitsAfterDecimal[, IncludeLeadingDigit[, UseParensForNegativeNumbers[, GroupDigits]]]]) |
FormatPercent | Возвращает строку в виде процентного отношения умноженного на 100), после которого идет символ %. | FormatPercent(Expression[, NumDigitsAfterDecimal[, IncludeLeadingDigit[, UseParensForNegativeNumbers[, GroupDigits]]]]) |
Эта группа функций разбивает строки на отдельные фрагменты с помощью разделителя или воссоздает строки из более мелких частей. Функции Filter и Split используют новую, появившуюся в VB6 возможность возвращать массивы строк.
Имя функции | Описание | Синтаксис |
Filter | Возвращает массив (нумерация индексов с нуля), который содержит подмножество строкового массива, полученное с помощью заданного критерия фильтрации | Filter(InputStrings, Value[, Include[, Compare]]) as String() |
Split | Возвращает строковый массив (нумерация индексов с нуля), содержащий заданное число подстрок. Эта функция может вернуть как все подмножество строк, так и любое требуемое количество строк. | Split (expression[, delimiter[, count[, compare]]]) as String() |
Join | Возвращает строку, созданную путем объединения нескольких подстрок, содержащихся в передаваемом пользователем массиве строк. В качестве разделителя может задаваться любой символ. | Join(list[, delimiter]) as String |
Имя функции | Описание | Синтаксис |
InstrRev | Возвращает величину типа Long, указывающую положение первого экземпляра одной строки внутри другой, начиная с конца строки. | InStr([start, ]string1, string2[, compare]) |
Replace | Возвращает строку, в которой указанная подстрока заменяется на другую заданное число раз. С помощью этой функции можно задавать количество производимых замен или же начинать замены в любом месте исходной строки. | Replace(expression, find, replacewith[, start[, count[, compare]]]) as String |
Round | Возвращает число, округленное до указанного числа десятичных знаков. | Round(expression[, numdecimalplaces]) |
StrReverse | Возвращает строку, в которой порядок символов заданной строки изменен на обратный. | StrReverse(string1) as String |
Функция | Среднее время выполнения (в "тиках") |
1. Source$ = "We are going to use this string for a string reversal example" | |
StrReverse (VB6) | 0,005 |
ReverseString | 0,650 |
ReverseStringMy | 0,510 |
ReverseStringByteMy | 0,290 |
2. Source$ = "This line has several : colons : in it for an example" | |
InstrRev (VB6) | 0,005 |
RevInstrMy | 0,160 |
InstrReverseMy | 0,010 |
RevInstrByteMy | 0,225 |
RevInstrByteMy2 | 0,090 |
3. Source$ = "Мы пошли погулять в сад, а забрели в какой-то полисадник" Заменить "по" на "при". |
|
Replace (VB6) | 0,56 |
ReplaceMy | 0,87 |
Public Function Test2$(Source$, Delim1$, Search$, vbComp%, Delim2$) ' Вариант 2 ' Используя традиционные (известные еще 15 лет назад) функции Dim Word$, Result$, i%, i1%, Source1$ i1% = 1 Source1$ = Source$ + Delim1$ Do i% = InStr(i1%, Source$, Delim1$) If i% = 0 Then Exit Do ' выделяем очередное поле Word$ = Mid$(Source1$, i1, i% - i1%) If InStr(1, Word$, Search$, vbComp%) > 0 Then ' поле отвечает критерию поиска Result$ = Result$ + Delim2$ + Word$ End If i1% = i% + Len(Delim1$) Loop If Result$ > "" Then Test2$ = Mid$(Result$, Len(Delim2$) + 1) Else: Test2$ = Result$ End If End Function
Function ReverseString(sSource As String) As String Dim sReverse As String Dim i% For i = Len(sSource) To 1 Step -1 sReverse = sReverse & Mid$(sSource, i, 1) Next i ReverseString = sReverse End Function
Public Function ReverseStringMy(Source$) As String Dim Reverse$, i%, j% Reverse$ = Space$(Len(Source$)) For i = Len(Source$) To 1 Step -1 j = j + 1 Mid$(Reverse$, j, 1) = Mid$(Source$, i, 1) Next ReverseStringMy = Reverse$ End Function
Public Function ReverseStringByteMy(Source$) As String ' revised procedure (VBPJ 12'98, p.129) Dim bString() As Byte, bString2() As Byte Dim i%, j%, iLen% ' bString = Source$ iLen = Len(Source$) * 2 - 1 ReDim bString2(iLen) For i = iLen To 0 Step -2 bString2(j) = bString(i - 1): bString2(j + 1) = bString(i): j = j + 2 Next i ReverseStringByteMy = bString2 End Function
Public Function RevInstrMy(Source$, SubString$) As Integer Dim i%, iStart%, iSub% iSub = Len(SubString$) iStart = Len(Source$) - iSub + 1 For i = iStart To 1 Step -1 If Mid$(Source$, i, iSub) = SubString$ Then RevInstrMy = i: Exit Function End If Next End Function
Public Function InstrReverseMy(Source$, SubString$) As Integer Dim i% Do i = InStr(i + 1, Source$, SubString$) If i <= 0 Then Exit Do InstrReverseMy = i Loop End Function
Public Function RevInstrByteMy(Source$, SubString$) As Integer Dim i%, bytArray() As Byte, iBytes% bytArray = Source$ iBytes = Len(Source$) * 2 - 2 For i = iBytes To 0 Step -2 If Chr(bytArray(i)) = SubString Then RevInstrByteMy = i / 2 + 1 Exit Function End If Next End Function
Public Function RevInstrByteMy2(Source$, SubString$) As Integer Dim i%, bytArray() As Byte, bytSub As Byte, iBytes% bytArray = Source$: bytSub = Asc(SubString) iBytes = Len(Source$) * 2 - 2 For i = iBytes To 0 Step -2 If bytArray(i) = bytSub Then RevInstrByteMy2 = i / 2 + 1 Exit Function End If Next End Function
Function ReplaceMy$(Source$, LookFor$, ReplaceWith$) ' ' Замена кода строкой переменной '=================================== Dim strTemp$, iStart%, iStrLen% strTemp$ = Source$ If Len(strTemp$) > 0 Then iStart% = 1 iStrLen% = Len(ReplaceWith$) Do iStart% = InStr(iStart%, strTemp$, LookFor$) If iStart% = 0 Then Exit Do strTemp$ = Left$(strTemp$, iStart% - 1) + _ ReplaceWith$ + Mid$(strTemp$, iStart% + Len(LookFor$)) iStart% = iStart% + iStrLen% Loop End If ReplaceMy$ = strTemp$ End Function