Программирование >>  Включение нужных заголовков 

1 ... 62 63 64 [ 65 ] 66 67 68 ... 71


Локальные контексты

В совете 35 приведена реализация сравнения строк без учета регистра символов с применением алгоритмов mismatch и lexicographical compare, но в нем также указано, что полноценное решение должно зитывать локальный контекст. Книга посвящена STL, а не вопросам интернационализации, поэтому локальным контекстам в ней не нашлось места. Тем не менее, Мэтт Остерн, автор книги Generic Programming and the STL [4], посвятил этой теме статью в майском номере журнала С-1-1- Report* [И]. Текст этой статьи приведен в настоящем приложении. Я благодарен Мэтту и фирме lOlcommunications за то, что они разрешили мне это сделать.

Сравнение строк без учета регистра символов

Мэтт Остерн

Если вам когда-либо доводилось писать программы, в которых используются строки (а кому, спрашивается, не доводилось?), скорее всего, вы встречались с типичной ситуацией - две строки, различающиеся только регистром символов, должны были интерпретироваться как равные. В этих случаях требовалось, чтобы операции сравнения - проверка равенства, больше-меньше, выделение подстрок, сортировка - игнорировали регистр символов. Программисты очень часто спрашивают, как организовать подобные операции средствами стандартной библиотеки C-I-I-. На этот вопрос существует огромное количество ответов, многие из которых неверны.

Прежде всего необходимо избавиться от мысли о написании класса, сравнивающего строки без зета регистра. Да, с технической точки зрения это более или менее возможно. Тип std:; string стандартной библиотеки в действительности является синонимом для типа std: :basic string<char,std: :char traits<char>,std:: al 1 ocator<char> >. Операции сравнения определяются вторым параметром; пере-



См. статью Александреску А. (Andrei Alexandrescu) в майском номере <sC++ Report* за 2000 г. [19].

давая второй параметр с переопределенными операциями равно и меньше , можно специализировать basic string таким образом, что операции < и = будут игнорировать регистр символов. Такое решение возможно, но игра не стоит свеч.

Вы не сможете выполнять операции ввода-вывода или это потребует больших дополнительных хлопот. Классы ввода-вывода стандартной библиотеки (такие как std:: basi ci stream и std: :basic ostream) специализируются по двум начальным параметрам std::basic string (a std: :ostream всего лишь является синонимом для std: :basic ostream<char,char traits<char> >). Параметры характеристик (traits) должны совпадать. Если вы используете строки типа std:: basic string<char, my trai ts cl ass>, то для вывода строк должен использоваться тип std::basic ostream<char,my traits class>. Стандартные потоки cin и cout для этой цели не подойдут.

Игнорирование регистра символов является не свойством объекта, а лишь контекстом его использования. Вполне возможно, что в одном контексте строки должны интерпретироваться с зетом регистра, а в другом контексте регистр должен игнорироваться (например, при установке соответствующего режима пользователем).

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

Этого вообще не достаточно. Даже если все функции basic string будут игнорировать регистр, это никак не отразится на использовании внешних обобщенных алгоритмов, таких как std::search и std:: find end. Кроме того, такое решение перестает работать, если по соображениям эффективности перейти от контейнера объектов basi cstr ing к таблице строк.

Более правильное решение, которое лзше соответствует архитектуре стандартной библиотеки, заключается в том, чтобы игнорировать регистр символов только в тех слзаях, когда это действительно необходимо. Не стоит возиться с такими функциями контейнера string, как string: :find first или string: :rfind; они лишь дублируют функциональные возможности, уже поддерживаемые внешними обобщенными алгоритмами. С другой стороны, алгоритмы обладают достаточной гибкостью, что позволяет реализовать в них поддержку сравнений строк без Зета регистра. Например, чтобы отсортировать коллекцию строк без учета регистра, достаточно передать алгоритму правильный объект функции сравнения:

std:: sort (С. begi п (), C.endO. compare wi thout case);

Написанию таких объектов и посвящена эта статья.

Первая попытка

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



Первая попытка 209

Mary McCarthy имени Bernard Malamud или следует после него? (В действительности это лишь вопрос привычки, я встречал оба варианта.) Впрочем, простейший способ сравнения строк хорошо знаком нам по школе: речь идет о лексикографическом, или словарном , сравнении, основанном на последовательном сравнении отдельных символов двух строк.

Лексикографический критерий сравнения может оказаться неподходящим для некоторых специфических ситуаций. Более того, единого критерия вообще не существует - например, имена людей и географические названия иногда сортируются по разным критериям. С другой стороны, в большинстве случаев лексикографический критерий подходит, поэтому он был заложен в основу механизма строковых сравнений в С++. Строка представляет собой последовательность символов. Если объекты х и у относятся к типу std:: string, то выражение х<у эквивалентно выражению

std:: 1 exi cographi cal compare(x. begi nO.x.endO,у. begi п(),у. endO)

В приведенном выражении алгоритм lexicographical conipare сравнивает отдельные символы оператором <, однако существует другая версия lexicographical compare, позволяющая задать пользовательский критерий сравнения символов. Она вызывается с пятью аргументами вместо четырех; в последнем аргументе передается объект функции, двоичный предикат, определяющий, какой из двух символов предшествует другому. Таким образом, для сравнения строк без зета регистра на базе lexicographical compare достаточно объединить этот алгоритм с объектом функции, игнорирующим различия в регистре символов.

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

struct lt nocase :public std::binary function<char,char,bool>{

bool operatorO (char x,char y) const{ return std::toupper(static cast<unsigned char>(x))< std::toupper(static cast<unsigned char>(y)):

У каждой сложной задачи есть решение простое, элегантное и... неправильное. Авторы книг С++ обожают этот класс за простоту и наглядность. Я тоже неоднократно использовал его в своих книгах. Он почти правилен, и все-таки не совсем, хотя недостаток весьма нетривиален. Следующий пример выявляет этот недостаток:

int mainO {

const char* si = GEW\334RZTRAMINER : const char* s2 = gew\374rztraminer : printf( sl=%s. s2= s\n ,si,s2):



1 ... 62 63 64 [ 65 ] 66 67 68 ... 71

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика