Более правильное решение, которое лучше соответствует архитектуре стандартной библиотеки, заключается в том, чтобы игнорировать регистр символов только в тех случаях, когда это действительно необходимо. Не стоит возиться с такими функциями контейнера string
, как string::find_first
или string::rfind
; они лишь дублируют функциональные возможности, уже поддерживаемые внешними обобщенными алгоритмами. С другой стороны, алгоритмы обладают достаточной гибкостью, что позволяет реализовать в них поддержку сравнений строк без учета регистра. Например, чтобы отсортировать коллекцию строк без учета регистра, достаточно передать алгоритму працильный объект функции сравнения:
std::sort(C.begin, C.end.compare_without_case);
Написанию таких объектов и посвящена эта статья.
Первая попытка
Существует несколько способов упорядочения слов по алфавиту. Зайдите в книжный магазин и посмотрите, как расставлены книги на полках. Предшествует ли имя
1. См. статью Александреску А. (Andrei Alexandrescu) в майском номере «C++ Report» за 2000 г. [19].
Mary McCarthy имени Bernard Malamud или следует после него? (В действительности это лишь вопрос привычки, я встречал оба варианта.) Впрочем, простейший способ сравнения строк хорошо знаком нам по школе: речь идет о лексикографическом, или «словарном», сравнении, основанном на последовательном сравнений отдельных символов двух строк.
Лексикографический критерий сравнения может оказаться неподходящим для некоторых специфических ситуаций. Более того, единого критерия вообще не существует — например, имена людей и географические названия иногда сортируются по разным критериям. С другой стороны, в большинстве случаев лексикографический критерий подходит, поэтому он был заложен в основу механизма строковых сравнений в C++. Строка представляет собой последовательность символов. Если объекты x
и y
относятся к типу std::string
, то выражение x
std::lexicographical_compare(x.begin, x.end, y.begin, y.end)
В приведенном выражении алгоритм lexicographical_compare
сравнивает отдельные символы оператором <
, однако существует другая версия lexicographical_compare
, позволяющая задать пользовательский критерий сравнения символов. Она вызывается с пятью аргументами вместо четырех; в последнем аргументе передается объект функции, двоичный предикат, определяющий, какой из двух символов предшествует другому. Таким образом, для сравнения строк без учета регистра на базе lexicographical_compare
достаточно объединить этот алгоритм с объектом функции, игнорирующим различия в регистре символов.
Распространенный принцип сравнения двух символов без учета регистра заключается в том, чтобы преобразовать оба символа к верхнему регистру и сравнить результаты. Ниже приведена тривиальная формулировка этой идеи в виде объекта функции C++ с использованием хорошо известной функции toupper из стандартной библиотеки C:
struct lt_nocase:
public std::binary_function
bool operator(char x, char y) const {
return std::toupper(static_cast
std::toupper(static_cast
}
};
«У каждой сложной задачи есть решение простое, элегантное и… неправильное». Авторы книг C++ обожают этот класс за простоту и наглядность. Я тоже неоднократно использовал его в своих книгах. Он почти правилен, и все-таки не совсем, хотя недостаток весьма нетривиален. Следующий пример выявляет этот недостаток:
int main {
const char* s1 = "GEW\334RZTRAMINER";
const char* s2 = "gew\374rztraminer";
printf("s1=%s, s2=%s\n", s1, s2);
printf("s1
std::lexicographical_compare(s1, s1+14, s2, s2+14, lt_nocase)
?"true":"false");
}
Попробуйте запустить эту программу в своей системе. На моем компьютере (Silicon Graphics О2 с системой IRIX 6.5) результат выглядел так:
s1=GEW"URZTRAMINER,s2=gew"urztraminer
s1
Странно… Разве при сравнении без учета регистра «GEW"URZTRAMINER» и «gew"urztraminer» не должны быть равными? И еще возможен вариант с небольшой модификацией: если перед командой printf вставить строку
setlocale(LC_ALL, "de");
результат неожиданно изменяется:
s1=GEW"URZTRAMINER,s2=gew"urztraminer
s1
Задача сравнения строк без учета регистра сложнее, чем кажется сначала. Работа элементарной на первый взгляд программы в огромной степени зависит от того, о чем многие из нас предпочли бы забыть. Речь идет о локальном контексте.
Локальный контекст