C++17 STL Стандартная библиотека шаблонов — страница 70 из 119

"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};