const Message& m = *p;
string s;
if (find_from_addr(&m,s))
sender.insert(make_pair(s,&m));
}
// Теперь перемещаемся по объекту класса multimap
// и извлекаем темы сообщений, поступивших от John Doe:
typedef multimap<string, const Message*>::const_iterator MCI;
pair<MCI,MCI> pp =
for(MCI p = pp.first; p!=pp.second; ++p)
cout << find_subject(p–>second) << '\n';
}
Рассмотрим подробнее использование ассоциативных массивов. Мы использовали класс
multimap
(разделы 20.10 и Б.4), поскольку хотели собрать в одном месте много сообщений, поступивших из одного адреса. Стандартный класс
multimap
делает именно это (облегчая доступ к элементам с помощью одного и того же ключа). Очевидно (и типично), что наша задача распадается на две подзадачи:
• создать ассоциативный массив;
• использовать ассоциативный массив.
Мы создаем объект класса
multimap
путем обхода всех сообщений и их вставки с помощью функции
insert()
:
for (Mess_iter p = mfile.begin(); p!=mfile.end(); ++p) {
const Message& m = *p;
string s;
if (find_from_addr(&m,s))
sender.insert(make_pair(s,&m));
}
В ассоциативный массив включаются пары (ключ, значение), созданные с помощью функции
make_pair()
. Для того чтобы найти имя отправителя, используем “кустарную” функцию
find_from_addr()
.
Почему мы используем ссылку
m
и передаем ее адрес? Почему не использовать итератор
p
явно и не вызвать функцию так:
find_from_addr(p,s)
? Потому что, даже если мы знаем, что итератор
Mess_iter
ссылается на объект класса
Message
, нет никакой гарантии, что он реализован как указатель.
Почему мы сначала записали объекты класса
Message
в вектор, а затем создали объект класса
multimap
? Почему сразу не включить объекты класса
Message
в ассоциативный массив класса
map
? Причина носит простой и фундаментальный характер.
• Сначала мы создаем универсальную структуру, которую можно использовать для многих вещей.
• Затем используем ее в конкретном приложении.
Таким образом, мы создаем коллекцию в той или иной степени повторно используемых компонентов. Если бы мы сразу создали ассоциативный массив в объекте класса
Mail_file
, то вынуждены были бы переопределять его каждый раз, когда хотим использовать его для решения другой задачи. В частности, наш объект класса
multimap
(многозначительно названный
sender
) упорядочен по полю
Address
. Большинство других приложений могут использовать другой критерий сортировки: по полям Return, Recipients, Copy-to fields, Subject fields, временным меткам и т.д.
Создание приложений по этапам (или слоям (layers), как их иногда называют) может значительно упростить проектирование, реализацию, документацию и эксплуатацию программ. Дело в том, что каждая часть приложения решает отдельную задачу и делает это вполне очевидным образом. С другой стороны, для того чтобы сделать все сразу, нужен большой ум. Очевидно, что извлечение информации и заголовков сообщений электронной почты — это детский пример приложения. Значение разделения задач, выделения модулей и поступательного наращивания приложения по мере увеличения масштаба приложения проявляется все более ярко.
Для того чтобы извлечь информацию, мы просто ищем все упоминания ключа "John Doe", используя функцию
equal_range()
(раздел Б.4.10). Затем перемещаемся по всем элементам в последовательности
[first,second]
, возвращаемой функцией
equal_range()
, извлекая темы сообщений с помощью функции
find_subject()
.
typedef multimap<string, const Message*>::const_iterator MCI;
pair<MCI,MCI> pp = sender.equal_range("John Doe");
for (MCI p = pp.first; p!=pp.second; ++p)
cout << find_subject(p–>second) << '\n';
Перемещаясь по элементам объекта класса map, мы получаем последовательность пар (ключ,значение), в которых, как в любом другом объекте класса
pair
, первый элемент (в данном случае ключ класса
stringkey
) называется
first
, а второй (в данном случае объект класса
Message
) —
second
(см. раздел 21.6).
23.4.1. Детали реализации
Очевидно, что мы должны реализовать используемые нами функции. Соблазнительно, конечно, сэкономить бумагу и спасти дерево, предоставив читателям самостоятельно решить эту задачу, но мы решили, что пример должен быть полным.
Конструктор класса
Mail_file
открывает файл и создает векторы
lines
и
m
.
Mail_file::Mail_file(const string& n)
// открывает файл с именем "n"
// считывает строки из файла "n" в вектор lines
// находит сообщения в векторе lines и помещает их в вектор m,
// для простоты предполагая, что каждое сообщение заканчивается