Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
Операторы сравнения и равенства кортежей ведут себя подобно соответствующим операторам контейнеров (см. раздел 9.2.7). Эти операторы выполняются для членов двух кортежей, слева и справа. Сравнить два кортежа можно только при совпадении количества их членов. Кроме того, чтобы использовать операторы равенства или неравенства, должно быть допустимо сравнение каждой пары членов при помощи оператора ==; а для использования операторов сравнения допустимым должно быть использование оператора <. Например:
tuple<string, string> duo("1", "2");
tuple<size_t, size_t> twoD(1, 2);
bool b = (duo == twoD); // ошибка: нельзя сравнить size_t и string
tuple<size_t, size_t, size_t> threeD(1, 2, 3);
b = (twoD < threeD); // ошибка: разное количество членов
tuple<size_t, size_t> origin(0, 0);
b = (origin < twoD); // ok: b — это true
Поскольку кортеж определяет операторы < и ==, последовательности кортежей можно передавать алгоритмам, а также использовать кортеж как тип ключа в упорядоченном контейнере.
Упражнения раздела 17.1.1Упражнение 17.1. Определите кортеж, содержащий три члена типа int, и инициализируйте их значениями 10, 20 и 30.
Упражнение 17.2. Определите кортеж, содержащий строку, вектор строки и пару из строки и целого числа (типы string, vector<string> и pair<string, int>).
Упражнение 17.3. Перепишите программы TextQuery из раздела 12.3 так, чтобы использовать кортеж вместо класса QueryResult. Объясните, что на ваш взгляд лучше и почему.
17.1.2. Использование кортежей для возвращения нескольких значений
Обычно кортеж используют для возвращения из функции нескольких значений. Например, рассматриваемый книжный магазин мог бы быть одним из нескольких магазинов в сети. У каждого магазина был бы транзакционный файл, содержащий данные по каждой проданной книге. В этом случае могло бы понадобиться просмотреть все продажи данной книги по всем магазинам.
Предположим, для каждого магазина имеется файл транзакций. Каждый из этих транзакционных файлов в магазине будет содержать все транзакции для каждой группы книг. Предположим также, что некая другая функция читает эти транзакционные файлы, создает вектор vector<Sales_data> для каждого магазина и помещает эти векторы в вектор векторов:
// каждый элемент в файле содержит транзакции
// для определенного магазина
vector<vector<Sales_data>> files;
Давайте напишем функцию, которая будет просматривать файлы в поисках магазина, продавшего заданную книгу. Для каждого магазина, у которого есть соответствующая транзакция, необходимо создать кортеж для содержания индекса этого магазина и двух итераторов. Индекс будет позицией соответствующего магазина в файлах, а итераторы отметят первую и следующую после последней записи по заданной книге в векторе vector<Sales_data> этого магазина.
Функция, возвращающая кортежДля начала напишем функции поиска заданной книги. Аргументами этой функции будет только что описанный вектор векторов и строка, представляющая ISBN книги. Функция будет возвращать вектор кортежей с записями по каждому магазину, где была продана по крайней мере одна заданная книга:
// matches имеет три члена: индекс магазина и итераторы в его векторе
typedef tuple<vector<Sales_data>::size_type,
vector<Sales_data>::const_iterator,
vector<Sales_data>::const_iterator> matches;
// files хранит транзакции по каждому магазину
// findBook() возвращает вектор с записями для каждого магазина,
// продавшего данную книгу
vector<matches>
findBook(const vector<vector<Sales_data>> &files,
const string &book) {
vector<matches> ret; // изначально пуст
// для каждого магазина найти диапазон, соответствующий книге
// (если он есть)
for (auto it = files.cbegin(); it != files.cend(); ++it) {
// найти диапазон Sales_data с тем же ISBN
auto found = equal_range(it->cbegin(), it->cend(),
book, compareIsbn);
if (found.first != found.second) // у этого магазина есть продажи
// запомнить индекс этого магазина и диапазона соответствий
ret.push_back(make_tuple(it - files.cbegin(),
found.first, found.second));
}
return ret; // пуст, если соответствий не найдено
}
Цикл for перебирает элементы вектора files, которые сами являются векторами. В цикле for происходит вызов библиотечного алгоритма equal_range(), работающего как одноименная функция-член ассоциативного контейнера (см. раздел 11.3.5). Первые два аргумента функции equal_range() являются итераторами, обозначающими исходную последовательность (см. раздел 10.1). Третий аргумент — значение. По умолчанию для сравнения элементов функция equal_range() использует оператор <. Поскольку тип Sales_data не имеет оператора <, передаем указатель на функцию compareIsbn() (см. раздел 11.2.2).
Алгоритм equal_range() возвращает пару итераторов, обозначающих диапазон элементов. Если книга не будет найдена, то итераторы окажутся равны, означая, что диапазон пуст. В противном случае первый член возвращенной пары обозначит первую соответствующую транзакцию, а второй — следующую после последней.
Использование возвращенного функцией кортежаПосле создания вектора магазинов с соответствующей транзакцией эти транзакции необходимо обработать. В данной программе следует сообщить результаты общего объема продаж для каждого магазина, у которого была такая продажа:
void reportResults(istream &in, ostream &os,
const vector<vector<Sales_data>> &files) {
string s; // искомая книга
while (in >> s) {
auto trans = findBook(files, s);
// магазин, продавший эту книгу
if (trans.empty()) {
cout << s << " not found in any stores" << endl;
continue; // получить следующую книгу для поиска
}
for (const auto &store : trans) // для каждого магазина с
// продажей
// get<n> возвращает указанный элемент кортежа в store
os << "store " << get<0>(store) << " sales: "
<< accumulate(get<1>(store), get<2>(store),
Sales_data(s))
<< endl;
}
}
Цикл while последовательно читает поток istream по имени in, чтобы запустить обработку следующей книги. Вызов функции findBook() позволяет выяснить, присутствует ли строка s, и присваивает результаты вектору trans. Чтобы упростить написание типа trans, являющегося вектором кортежей, используем ключевое слово auto.
Если вектор trans пуст, значит, по книге s никаких продаж не было. В таком случае выводится сообщение и происходит возврат к циклу while, чтобы обработать следующую книгу.
Цикл for свяжет ссылку store с каждым элементом вектора trans. Поскольку изменять элементы вектора trans не нужно, объявим ссылку store ссылкой на константу. Для вывода результатов используем get: get<0> — индекс соответствующего магазина; get<1> — итератор на первую транзакцию; get<2> — на следующую после последней.
Поскольку класс Sales_data определяет оператор суммы (см. раздел 14.3), для суммирования транзакций можно использовать библиотечный алгоритм accumulate() (см. раздел 10.2.1). Как отправную точку суммирования используем объект класса Sales_data, инициализированный конструктором Sales_data(), получающим строку (см. раздел 7.1.4). Этот конструктор инициализирует переменную-член bookNo переданной строкой, а переменные-члены units_sold и revenue — нулем.
Упражнения раздела 17.1.2Упражнение 17.4. Напишите и проверьте собственную версию функции findBook().
Упражнение 17.5. Перепишите функцию findBook() так, чтобы она возвращала пару, содержащую индекс и пару итераторов.
Упражнение 17.6. Перепишите функцию findBook() так, чтобы она не использовала кортеж или пару.
Упражнение 17.7. Объясните, какую версию функции findBook() вы предпочитаете и почему.
Упражнение 17.8. Что будет, если в качестве третьего параметра алгоритма accumulate() в последнем примере кода этого раздела передать объект класса Sales_data?