"Gabe" "Musical dog on maximum borkdrive" 2016
"Honey Badger" "Crazy nastyass honey badger" 2011
"Dramatic Chipmunk" "Chipmunk with a very dramatic look" 2007
9. Компиляция и запуск программы с базой данных, содержащей примеры мемов, дадут следующий результат:
$ cat memes.txt | ./filling_containers
Doge : Very Shiba Inu. so dog. much funny. wow., 2013
Dramatic Chipmunk : Chipmunk with a very dramatic look, 2007
Gabe : Musical dog on maximum borkdrive, 2016
Honey Badger : Crazy nastyass honey badger, 2011
Pepe : Anthropomorphic frog, 2016
Как это работает
В данном примере можно отметить три интересные детали. Первая заключается в том, что на основе данных последовательного символьного потока ввода мы заполняли не вектор или список, а более сложный контейнер
std::map
. Еще одна — мы использовали эти манипуляторы потока. Последняя — вызов accumulate
, который определяет размер самой длинной строки.Начнем с работы с ассоциативным массивом. Наша структура
meme
содержит только поле с описанием и год. Название интернет-мема не является частью структуры, поскольку используется в качестве ключа. При вставке чего-нибудь в ассоциативный массив можно предоставить объект типа std::pair
, имеющий разные типы для ключа и значения. Именно это мы и сделали. Сначала реализовали оператор потока >>
для структуры meme
, а затем сделали то же самое для типа pair
. Затем мы использовали конструкцию istream_iterator>{cin}
, чтобы получить такие элементы из стандартного потока ввода и передать их в ассоциативный массив с помощью inserter(m, end(m))
.При десериализации элементов типа
meme
из потока мы разрешили использовать пробелы в названиях и описаниях. Это легко реализовать, однако они не длиннее одной строки, поскольку мы поместили данные поля в кавычки. Взгляните на пример формата строк: "Name with spaces" "Description with spaces" 123
.При работе со строками, заключенными в кавычки как на входе, так и на выходе, поможет
std::quoted
. Если у нас есть строка s
, то при выводе ее с помощью cout << quoted(s)
она будет заключена в кавычки. В случае десериализации строки из потока, например используя cin >> quoted(s)
, мы считаем следующий символ кавычек, заполним строку символами, стоящими после него, и будем делать это до тех пор, пока не увидим следующий символ кавычек, независимо от количества встреченных пробелов.Последний необычный момент заключается в том, как мы передали функцию
max_func
в наш вызов алгоритма accumulate
:
auto max_func ([](size_t old_max, const auto &b) {
return max(old_max, b.first.length());
});
size_t width {accumulate(begin(m), end(m), 0u, max_func)};
Похоже, что функция
max_func
принимает аргумент типа size_t
и еще один аргумент с автоматическим типом, который оказывается элементом типа pair
, взятым из ассоциативного массива. На первый взгляд все выглядит очень странно, поскольку большинство бинарных функций сжатия принимают аргументы идентичных типов, а затем объединяют их с помощью некой операции, как, например, это делает std::plus
. В нашем случае все выглядит по-другому, поскольку мы не объединяем сами пары. Мы только получаем длину каждой строки, представляющей собой ключ, для каждой пары, отбрасываем остальное, а затем сжимаем полученные значения типа size_t
с помощью функции max
.В вызове
accumulate
первый вызов функции max_func
получает значение 0u
, изначально предоставленное нами в качестве левого аргумента, и ссылку на первый элемент типа pair
с правой стороны. Это дает возвращаемое значение max(0u, string_length)
, которое станет левым аргументом для следующего вызова, где очередная пара будет представлять собой правый параметр, и т.д.Выводим любые данные на экран с помощью итераторов std::ostream
Очень легко вывести что-то на экран с помощью потоков вывода, поскольку в STL есть много полезных перегруженных версий оператора
<<
для большинства простых типов. Таким образом, структуры данных, содержащие элементы подобных типов, можно легко вывести на экран, задействовав класс std::ostream_iterator
, что мы довольно часто делали.В данном примере мы сконцентрируемся на том, как это сделать для пользовательского типа и что еще можно сделать для управления выводом на экран с помощью выбора типа шаблона без необходимости писать при этом много кода на вызывающей стороне.
Как это делается
В этом примере мы поработаем с итератором
std::ostream_iterator
, объединив его с новым пользовательским классом, и взглянем на его возможности неявного преобразования, что может помочь при выводе данных на экран.
1. Сначала указываем, какие заголовочные файлы включить, а затем объявляем об использовании пространства имен
std
:
#include
#include
#include
#include
#include
using namespace std;
2. Реализуем функцию преобразования, которая соотносит числа и строки. Она будет возвращать строку
"one"
для значения 1
, "two"
для значения 2
и т.д.:
string word_num(int i) {
3. Мы заполним ассоциативный массив, основанный на хешах, этими парами, чтобы получить к ним доступ позже:
unordered_map m {
{1, "one"}, {2, "two"}, {3, "three"},
{4, "four"}, {5, "five"}, //...
};
4. Теперь можно передать в функцию
find
ассоциативного массива, основанного на хеше, аргумент i
и вернуть то значение, которое она найдет. Если функция ничего не найдет — например, для заданного числа нет перевода, — то вернем строку "unknown"
:
const auto match (m.find(i));
if (match == end(m)) { return "unknown"; }
return match->second;
};
5. Мы будем работать также со структурой
bork
. Она содержит только одно целое число и неявно создается на основе целого числа. Кроме того, она имеет функцию print
, которая принимает ссылку на поток вывода и выводит строку "bork"
столько раз, сколько указано в целочисленной переменной borks
:
struct bork {
int borks;
bork(int i) : borks{i} {}
void print(ostream& os) const {
fill_n(ostream_iterator{os, " "},
borks, "bork!"s);
}
};
6. Для использования функции
bork::print
перегрузим оператор <<
для объектов потока, чтобы они автоматически вызывали функцию bork::print
, когда объекты типа bork
попадают в поток вывода:
ostream& operator<<(ostream &os, const bork &b) {
b.print(os);
return os;
}
7. Теперь наконец можем начать реализовывать саму функцию
main
. Изначально просто создаем вектор, содержащий некоторые значения:
int main()
{
const vector v {1, 2, 3, 4, 5};