Скотт Мейерс - Эффективное использование STL
- Название:Эффективное использование STL
- Автор:
- Жанр:
- Издательство:Питер
- Год:2002
- ISBN:5-94723-382-7
- Рейтинг:
- Избранное:Добавить в избранное
-
Ваша оценка:
Скотт Мейерс - Эффективное использование STL краткое содержание
В этой книге известный автор Скотт Мейерс раскрывает секреты настоящих мастеров, позволяющие добиться максимальной эффективности при работе с библиотекой STL.
Во многих книгах описываются возможности STL, но только в этой рассказано о том, как работать с этой библиотекой. Каждый из 50 советов книги подкреплен анализом и убедительными примерами, поэтому читатель не только узнает, как решать ту или иную задачу, но и когда следует выбирать то или иное решение — и почему именно такое.
Эффективное использование STL - читать онлайн бесплатно полную версию (весь текст целиком)
Шрифт:
Интервал:
Закладка:
Более правильное решение, которое лучше соответствует архитектуре стандартной библиотеки, заключается в том, чтобы игнорировать регистр символов только в тех случаях, когда это действительно необходимо. Не стоит возиться с такими функциями контейнера 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(x))<
std::toupper(static_cast(y));
}
};
«У каждой сложной задачи есть решение простое, элегантное и… неправильное». Авторы книг 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ÜRZTRAMINER,s2=gewürztraminer
s1
Странно… Разве при сравнении без учета регистра «GEWÜRZTRAMINER» и «gewürztraminer» не должны быть равными? И еще возможен вариант с небольшой модификацией: если перед командой printf вставить строку
setlocale(LC_ALL, "de");
результат неожиданно изменяется:
s1=GEWÜRZTRAMINER,s2=gewürztraminer
s1
Задача сравнения строк без учета регистра сложнее, чем кажется сначала. Работа элементарной на первый взгляд программы в огромной степени зависит от того, о чем многие из нас предпочли бы забыть. Речь идет о локальном контексте.
Локальный контекст
Символьный тип
char
в действительности представляется самым обычным целым числом. Это число можно интерпретировать как символ, но такая интерпретация ни в коем случае не является универсальной. Что должно соответствовать конкретному числу — буква, знак препинания, непечатаемый управляющий символ?
На этот вопрос невозможно дать однозначный ответ. Более того, с точки зрения базовых языков C и C++ различия между этими категориями символов не так уж существенны и проявляются лишь в некоторых библиотечных функциях: например, функция isalpha
проверяет, является ли символ буквой, а функция toupper
переводит символы нижнего регистра в верхний регистр и оставляет без изменений буквы верхнего регистра и символы, не являющиеся буквами. Подобная классификация символов определяется особенностями культурной и лингвистической среды. В английском языке действуют одни правила, по которым буквенные символы отличаются от «не буквенных», в шведском — другие и т. д. Преобразование из нижнего регистра в верхний имеет один смысл в латинском алфавите, другой — в кириллице, и вообще не имеет смысла в иврите.
По умолчанию функции обработки символов работают с кодировкой, подходящей для простого английского текста. Символ '\374' не изменяется функцией toupper
, поскольку он не считается буквой; в некоторых системах при выводе он имеет вид ü, но для библиотечной функции C, работающей с английским текстом, это несущественно. В кодировке ASCII нет символа ü. Команда
setlocale(LC_ALL, "de");
сообщает библиотеке C о переходе на немецкие правила (по крайней мере в системе IRIX — имена локальных контекстов не стандартизованы). В немецком языке есть символ ü, поэтому функция toupper
преобразует ü в Ü.
У любого нормального программиста этот факт вызывает обоснованное беспокойство. Оказывается, простая функция toupper
, вызываемая с одним аргументом, зависит еще и от глобальной переменной — хуже того, от скрытой глобальной переменной. Это приводит к стандартной проблеме: на работу функции, использующей toupper
, теоретически может повлиять любая другая функция во всей программе.
Шрифт:
Интервал:
Закладка: