|
Программирование >> Включение нужных заголовков
же принцип, что и прежде: строки сравниваются алгоритмом 1 exi cographi са1 compare, а отдельные символы сравниваются после приведения к верхнему регистру. Впрочем, на этот раз вместо глобальной переменной используется объект локального контекста. Кстати говоря, сравнение после приведения обоих символов к верхнему регистру не всегда дает тот же результат, что и после приведения к нижнему регистру. Например, во французском языке в символах верхнего регистра принято опускать диакритические знаки, вследствие чего вызов toupper во французском локальном контексте может приводить к потере информации: символы ё и е преобразуются в один символ верхнего регистра Е. В этом случае при сравнении на базе функции toupper символы ё и е будут считаться одинаковыми, а при сравнении на базе tolower они будут считаться разными. Какой из ответов правилен? Вероятно, второй, но на самом деле все зависит от языка, национальных обычаев и специфики приложения. struct lt str l :public std: :binary function<stcl: :string.std: :string.bool>{ struct U char{ const std::ctype<char>& ct: lt char(const std::ctype<char>& c):ct(c) {} bool operatorO (char x. char y) const { return ct.toupper(x)<ct.toupper(y): std::locale loc; const std::ctype<char>& ct: lt str l(const std::locale& L = std::locale::classic()) :loc(L).ct(std::use facet<std::ctype<char> >(loc)) {} bool operatorO(const std::string& x. const std::string& y) const { return std::1exi cographi ca1 compa re(x.begi n О.x.end(), y.beginO.y.endO. lt char(ct)); Данное решение не оптимально; оно работает медленнее, чем могло бы работать. Проблема чисто техническая: функция toupper вызывается в цикле, а Стандарт С-и- требует, чтобы эта функция была виртуальной. Некоторые оптимизаторы выводят вызов виртуальной функции из цикла, но чаще этого не происходит. Циклические вызовы виртуальных функций нежелательны. В данном слзае тривиального решения не существует. Возникает соблазнительная мысль - воспользоваться одной из функций объекта ctype: const char* ctype<char>::toupper(char* f. char* i) const Эта функция изменяет регистр символов в интервале [f,i]. К сожалению, для наших целей этот интерфейс не подходит. Чтобы воспользоваться этой функцией для сравнения двух строк, необходимо скопировать обе строки в буферы и затем char tab[CHAR MAX-CHAR MIN+l]: lt str 2(const std::locale& L = std::locale::classic()){ const std::ctype<char>& ct = std::use facet<std::ctype<char> >(L): forCint i = CHAR MIN:i<=CHAR MAX:++i) tab[i-CHAR MIN]=(char)i: ct.toupper(tab, tab+(CHAR MAX-CHAR MIN+l)): bool OperatorO(const std::string& x, const std::string& y) const { return std:: 1 exicographical compare( x.begi n(), x. endO, y.beginO,y.end(), It char(tab)): Как видите, различия между 1 t str l и 1 t str 2 не так уж велики. В первом случае используется объект функции сравнения символов, использующий фасет ctype напрямую, а во втором случае - объект функции сравнения с таблицей заранее вычисленных преобразований символов к верхнему регистру. Второе решение уступает первому, если создать объект функции lt str 2, воспользоваться им для сравнения нескольких коротких строк и затем уничтожить. С другой стороны, при обработке больших объемов данных 1t str 2 работает значительно быстрее lt str l. В моих тестах превосходство было более чем двукратным: при использовании Itstrl сортировка списка из 23 791 слова заняла 0,86 секунды, а при использовании lt str 2 понадобилось только 0,4 секунды. преобразовать их содержимое к верхнему регистру. Но откуда возьмутся эти буферы? Они не могут быть массивами фиксированного размера (неизвестно, каким должен быть максимальный размер), а динамические массивы потребуют дорогостоящего выделения памяти. Альтернативное решение заключается в однократном преобразовании каждого символа с кэшированием результата. Такое решение недостаточно универсально-в частности, при использовании 32-разрядных символов UCS-4 оно абсолютно неработоспособно. С другой стороны, при работе с типом char (8-разрядным в большинстве систем) идея хранения 256 байт дополнительных данных в объекте функции сравнения выглядит вполне реально. struct lt str 2: public std::binary function<std;:string.std::string,bool>{ struct lt char{ const char* tab: lt char(const char* t):tab(t) {} bool operatorO (char x, char y) const { return tab[x-CHAR MIN]<tab[y-CHAR-MIN]: Итак, что же мы узнали? Класс строки без учета регистра символов реализуется на неправильном уровне абстракции. Обобщенные алгоритмы стандартной библиотеки С++ параметризуются, и этот факт следует использовать. Лексикографическое сравнение строк осуществляется сравнением отдельных символов. Если у вас имеется объект функции, сравнивающий символы без зета регистра, задача фактически решена, а этот объект может использоваться для сравнения других типов последовательностей символов, таких как vector<char>, строковые таблицы или обычные строки С. Задача сравнения строк без зета регистра сложнее, чем кажется на первый взгляд. Она имеет смысл лишь в конкретном локальном контексте, поэтому объект функции сравнения должен содержать информацию о текущем локальном контексте. Если сравнение должно быть оптимизировано по скорости, напишите объект функции таким образом, чтобы избежать многократного вызова дорогостоящих операций с фасетами. Правильное сравнение строк без учета символов требует большого объема рутинной работы, но ее необходимо проделать только один раз. Или вам, как и большинству коллег, не хочется думать о локальных контекстах? Впрочем, лет десять назад никому не хотелось думать об ошибке 2000 года . И все же у вас больше шансов обойти стороной эту проблему, если ваш локально-зависимый код будет с самого начала правильно работать.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки. |