Программирование. Принципы и практика использования C++ Исправленное издание, стр. 322
23.5. Проблема
Потоки ввода-вывода и класс
stringstring s;while (cin>>s) { if (s.size()==7 && isalpha(s[0]) && isalpha(s[1]) && isdigit(s[2]) && isdigit(s[3]) && isdigit(s[4]) && isdigit(s[5]) && isdigit(s[6])) cout << " найдена " << s << '\n';}Здесь значение
isalpha(x)truexisdigit(x)truex• Оно громоздко (четыре строки, восемь вызовов функций).
• Мы пропускаем (умышленно?) почтовые индексы, не отделенные от своего контекста пробелом (например, "TX77845", TX77845–1234 и ATX77845).
• Мы пропускаем (умышленно?) почтовые индексы с пробелом между буквами и цифрами (например, TX 77845).
• Мы принимаем (умышленно?) почтовые индексы, в которых буквы набраны в нижнем регистре (например, tx77845).
• Если вы решите проанализировать почтовые индексы, имеющие другой формат (например, CB3 0FD), то будете вынуждены полностью переписать весь код.
Должен быть более хороший способ! Перед тем как его описать, рассмотрим поставленные задачи. Предположим, что мы хотим сохранить “старый добрый код”, дополнив его обработкой указанных ситуаций.
• Если мы хотим обрабатывать не один формат, то следует добавить инструкцию
ifswitch• Если мы хотим учитывать верхний и нижний регистры, то должны явно конвертировать строки (обычно в нижний регистр) или добавить дополнительную инструкцию
if• Мы должны как-то (как?) описать контекст, в котором выполняется поиск. Это значит, что мы должны работать с отдельными символами, а не со строками, т.е. потерять многие преимущества, предоставляемые потоками
iostreamЕсли хотите, попробуйте написать код в этом стиле, но нам очевидно, что в этом случае вы запутаетесь в сети инструкций
if123!123456!2007–06–05
June 5, 2007
jun 5, 2007
5 June 2007
6/5/2007
5/6/07
...
В этот момент, если не раньше, опытный программист воскликнет: “Должен быть более хороший способ!” (чем нагромождение ординарного кода) и станет его искать. Простейшим и наиболее широко распространенным решением этой задачи является использование так называемых регулярных выражений (regular expressions).
Регулярные выражения являются основой большинства методов обработки текстов и команды
grepРегулярные выражения, которые мы будем использовать, реализованы в библиотеке, которая станет частью следующего стандарта языка С++ (C++0x). Они сопоставимы с регулярными выражениями из языка Perl. Этой теме посвящено много книг, учебников и справочников, например, рабочий отчет комитета по стандартизации языка C++ (в сети веб он известен под названием WG21), документация Джона Мэддокса (John Maddock)
boost::regexПОПРОБУЙТЕ
В последних двух абзацах “неосторожно” упомянуты несколько имен и аббревиатур без каких-либо объяснений. Поищите в веб информацию о них.
23.6. Идея регулярных выражений
Основная идея регулярного выражения заключается в том, что оно определяет шаблон (pattern), который мы ищем в тексте. Посмотрим, как мы могли бы точно описать шаблон простого почтового кода, такого как TX77845. Результат первой попытки выглядит следующим образом:
wwddddd
где символ w означает любую букву, а символ d — любую цифру. Мы используем символ w (от слова “word”), поскольку символ l (от слова “letter”) слишком легко перепутать с цифрой 1. Эти обозначения вполне подходят для нашего простого примера, но что произойдет, если мы попробуем применить их для описания формата почтового кода, состоящего из девяти цифр (например, TX77845–5629). Что вы скажете о таком решении?
wwddddd–dddd
\w\w\d\d\d\d\d–\d\d\d\d
Выглядит довольно некрасиво, но, по крайней мере, мы устранили неоднозначность, а обратные косые черты ясно обозначают то, что за ними следует “нечто необычное”. Здесь повторяющиеся символы просто перечислены один за другим. Это не только утомительно, но и провоцирует ошибки. Вы можете быстро сосчитать, что перед обратной косой чертой до дефиса действительно стоят пять цифр, а после — четыре? Мы смогли, но просто сказать 5 и 4 мало, чтобы в этом убедиться, поэтому придется их пересчитать. После каждого символа можно было бы поставить счетчик, указывающий количество его повторений.