Читать книги » Книги » Компьютеры и Интернет » Программирование » C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

Читать книгу C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц, Яцек Галовиц . Жанр: Программирование.
C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц
Название: C++17 STL Стандартная библиотека шаблонов
Дата добавления: 15 июль 2023
Количество просмотров: 1 473
(18+) Внимание! Книга может содержать контент только для совершеннолетних. Для несовершеннолетних просмотр данного контента СТРОГО ЗАПРЕЩЕН! Если в книге присутствует наличие пропаганды ЛГБТ и другого, запрещенного контента - просьба написать на почту для удаления материала.
Читать онлайн

C++17 STL Стандартная библиотека шаблонов читать книгу онлайн

C++17 STL Стандартная библиотека шаблонов - читать онлайн , автор Яцек Галовиц

С++ — объектно-ориентированный язык программирования, без которого сегодня немыслима промышленная разработка ПО. В этой замечательной книге описана работа с контейнерами, алгоритмами, вспомогательными классами, лямбда-выражениями и другими интересными инструментами, которыми богат современный С++. Освоив материал, вы сможете коренным образом пересмотреть привычный подход к программированию.
Преимущество издания — в подробном описании стандартной библиотеки шаблонов С++, STL. Ее свежая версия была выпущена в 2017 году. В книге вы найдете более 90 максимально реалистичных примеров, которые демонстрируют всю мощь STL. Многие из них станут базовыми кирпичиками для решения более универсальных задач.
Вооружившись этой книгой, вы сможете эффективно использовать С++17 для создания высококачественного и высокопроизводительного ПО, применимого в различных отраслях.

1 ... 37 38 39 40 41 ... 121 ВПЕРЕД
Перейти на страницу:
...xs) {

  (void)std::initializer_list<int>{

    ((void)f(xs), 0)...

  };

});

Сердцем данной функции является выражение f(xs).xs — набор параметров, и нужно распаковать его, чтобы получить отдельные значения и передать их отдельным вызовам функции f. К сожалению, мы не можем просто написать конструкцию f(xs)... с помощью нотации ..., с которой уже знакомы.

Вместо этого можно создать список значений с помощью std::initializer_list, имеющего конструктор с переменным числом параметров. Выражение наподобие return std::initializer_list<int>{f(xs)...}; решает задачу, но имеет недостатки. Взглянем на реализацию функции for_each, которая тоже работает и при этом выглядит проще нашего варианта:

auto for_each ([](auto f, auto ...xs) {

  return std::initializer_list<int>{f(xs)...};

});

Она более проста для понимания, но имеет следующие недостатки.

1. Создает список инициализаторов для возвращаемых значений на основе вызовов функции f. К этому моменту нас не волнуют возвращаемые значения.

2. Возвращает данный список, а нам нужна функция, работающая в стиле «запустил и забыл», которая не возвращает ничего.

3. Вполне возможно, что f — функция, которая не возвращает ничего, в таком случае код даже не будет скомпилирован.

Гораздо более сложная функция for_each решает все эти проблемы. Она делает следующее.

1. Не возвращает список инициализаторов, а приводит все выражение к типу void с помощью (void)std::initializer_list<int>{...}.

2. Внутри инициализирующего выражения преобразует выражение f(xs)... в выражение (f(xs),0). Это приводит к тому, что возвращаемое выражение отбрасывается, а значение 0 все еще помещается в список инициализаторов.

3. Конструкция f(xs) в выражении (f(xs),0).  также преобразуется к типу void, поэтому возвращаемое значение, если таковое существует, нигде не обрабатывается.

Объединение этих особенностей, к сожалению, ведет к появлению уродливой конструкции, но она корректно работает и компилируется для множества объектов функций независимо от того, возвращают ли они какое-то значение.

Приятной особенностью описанного механизма является тот факт, что порядок вызовов функций будет сохраняться в строгой последовательности.

 

 Выполнять преобразование конструкции (void)выражение в рамках старой нотации языка С не рекомендуется, поскольку в языке С++ имеются собственные операции преобразования. Вместо этого стоит использовать конструкцию reinterpret_cast<void>(выражение), но данный вариант еще больше снизит удобочитаемость кода.

Реализуем функцию transform_if с применением std::accumulate и лямбда-выражений

Большинство разработчиков, применяющих std::copy_if и std::transform, могли задаваться вопросом, почему не существует функции std::transform_if. Функция std::copy_if копирует элементы из исходного диапазона по месту назначения, но опускает элементы, не соответствующие определенной пользователем функции-предикату. Функция std::transform безусловно копирует все элементы из исходного диапазона по месту назначения, но при этом преобразует их в процессе. Это происходит с помощью функции, которая определена пользователем и может выполнять как нечто простое (например, умножение чисел), так и полные преобразования к другим типам.

Эти функции существуют достаточно давно, но функции std::transform_if все еще нет. Ее можно легко создать, реализовав функцию, которая итерирует по диапазонам данных и копирует все элементы, соответствующие предикату, выполняя в процессе их преобразование. Однако мы воспользуемся случаем и разберем решение данной задачи с точки зрения лямбда-выражений.

Как это делается

В этом примере мы создадим собственную функцию transform_if, которая работает, передавая алгоритму std::accumulate правильные объекты функций.

1. Как и всегда, включим некоторые заголовочные файлы:

#include <iostream>

#include <iterator>

#include <numeric>

2. Сначала реализуем функцию с именем map. Она принимает функцию преобразования входных данных и возвращает объект функции, который будет работать с функцией std::accumulate:

template <typename T>

auto map(T fn)

{

3. Мы будем возвращать объект функции, принимающий функцию reduce. Когда данный объект вызывается с этой функцией, он возвращает другой объект функции, который принимает аккумулятор и входной параметр. Он вызывает функцию reduce для этого аккумулятора и преобразованной входной переменной fn. Если это описание кажется вам слишком сложным — не волнуйтесь, далее мы соберем все вместе и посмотрим, как работают эти функции.

  return [=] (auto reduce_fn) {

    return [=] (auto accum, auto input) {

      return reduce_fn(accum, fn(input));

    };

  };

}

4. Теперь реализуем функцию filter. Она работает точно так же, как и функция map, но не затрагивает входные данные, в то время как map преобразует их с помощью функции transform. Вместо этого принимаем функцию-предикат и опускаем те входные переменные, которые не соответствуют данному предикату, не выполняя для них функцию reduce.

template <typename T>

auto filter(T predicate)

{

5. Два лямбда-выражения имеют такие же сигнатуры функций, что и выражения в функции map. Единственное отличие заключается в следующем: входной параметр остается неизменным. Функция-предикат используется для определения того, будем ли мы вызывать функцию reduce_fn для входных данных или же получим доступ к аккумулятору, не внося никаких изменений.

  return [=] (auto reduce_fn) {

    return [=] (auto accum, auto input) {

      if (predicate(input)) {

        return reduce_fn(accum, input);

      } else {

        return accum;

      }

    };

  };

}

6. Теперь воспользуемся этими вспомогательными функциями. Создадим экземпляры итераторов, которые позволяют считать целочисленные значения из стандартного потока ввода:

int main()

{

  std::istream_iterator<int> it {std::cin};

  std::istream_iterator<int> end_it;

7. Далее определим функцию-предикат even, которая возвращает значение true, если перед нами четное число. Функция преобразования twice умножает свой целочисленный параметр на 2:

  auto even ([](int i) { return i % 2 == 0; });

  auto twice ([](int i) { return i * 2; });

8. Функция std::accumulate принимает диапазон значений и аккумулирует их. Аккумулирование по умолчанию означает суммирование значений с помощью оператора +. Мы хотим предоставить собственную функцию аккумулирования. Таким образом, хранить сумму значений не нужно. Мы присвоим каждое значение из диапазона разыменованному итератору it, а затем вернем его после продвижения вперед.

  auto copy_and_advance ([](auto it, auto input) {

    *it = input; return ++it;

  });

9. Наконец мы готовы собрать все воедино. Мы итерируем по стандартному потоку ввода и предоставляем вывод ostream_iterator, который

1 ... 37 38 39 40 41 ... 121 ВПЕРЕД
Перейти на страницу:
Комментарии (0)