C++ для начинающих. Алгоритм for_each

for_each в С++ — это алгоритм, позволяющий выполнять обход массивов и массивоподобных структур. В переводе с английского название означает "для каждого", т. е. для каждого элемента (подразумевается, что выполнять что-то). Название алгоритма выбрано таким образом, что алгоритм как будто просит придать смысл своего существования. Что-то, что нужно будет выполнить, можно задавать с помощью отдельной функции (либо функтора), либо прям по месту с помощью лямбда-выражения. В очень старых компиляторах алгоритмов нет, поэтому и for_each в них использовать не получится.
Поскольку в первую очередь алгоритм выполняет обход элементов, то и первым примером использования будет обход обычного массива.

  • for_each — это агоритм для обхода массива или массивоподобной структуры.
  • for_each принимает три параметра: начало и конец обходимого набора данных и указатель на функцию, либо лямбда-выражение или функтор, выполняющие основную обработку обходимымых элементов.
Задавать границы массива можно либо с помощью обычных указателей с использованием арифметики указателей, либо с помощью итераторов, если таковые в обходимой структуре есть. Например, обычные массивы [] итераторов не предполагают, поэтому для них нужно использовать указатели, а массивы array из пространства имён std (подключаемые include <array>) хранят в себе итераторы и поэтому для них лучше использовать итераторы.

Явно количество элементов в массиве не задано, но массивы С++ по определению имеют всегда фиксированную ёмкость, поэтому при инициализации массиву выделяется определённое число ячеек, а поскольку количество ячеек я явно не указал, то использую формулу для вычисления их количества. Пусть это никого не пугает. Работает это так: общий размер массива делим на размер ячейки элемента массива и получаем количество ячеек.
При вызове нами for_each первым аргументом отдаётся название массива, оно неявно приводится к указателю, а этот указатель направлен на адрес, хранящий первый элемент, т. е. название массива используется как указатель на начало набора данных; второй аргумент — это смещение, т. е. это вычисляемый с помощью адресной арифметики адрес последнего элемента массива (когда плюсовики складывают указатели, они получают смещения адресов); а третий аргумент — это функция, обрабатывающая выбранные элементы.

Поскольку в #1.2 не массив, а класс, не умеющий неявно преобразовывать собственное имя в указатель, то адресная арифметика непосредственно с именем класса работать не будет, но можно использовать адресную арифметику на итераторах:


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

Алгоритмы удобны тем, что в них можно подставлять разные функции, обходя один и тот же набор данных, сохраняя некоторую элегантность во внешнем виде кода. for_each позволяет не только обходить, но и выполнять любую обработку элементов. Если мы напишем функцию, изменяющие данные, то значения в массиве будут изменены. Допишем изменяющую массив функцию в наш код:

Изменяющая функция принимает параметр по ссылке, поэтому любые изменения пришедшего параметра влияют на наружнюю сущность этого параметра. Если забыть использовать ссылочность, то, конечно, изменения с элементами массива не произойдут (читайте о передаче параметра по ссылке и по значению, если не понимаете о чём речь).
Вообще for_each предполагается как немодифицирующий алгорим, т. е. хоть используя его и можно изменить данные обходимой последовательности, делать этого не следует, а кое-что даже и не получится написать: при необходимости изменять значения следует использовать модифицирующие алгоритмы. Так, например, нельзя задать функции-обработчику никакое число параметров, кроме одного.

Так что помните, что for_each прежде всего следует использовать только для чтения данных. Обычно для просто обхода и вывода данных на экран или записи данных в файл, в базу данных или чего-то наподобие.
Результат работы функции-обработчика можно подбирать, но для этого нужно использовать функторы. В принципе, можно и функции, но использованию функций мешает неожиданно лишний параметр. Выражение

for_each(it1,it2, foo)

возвращает функцию, которую принимает третьим параметром (здесь это foo, а it1, it2 — это условное обозначение итераторов). Т. е. если мы подаём foo как функцию-обработик, то результат foo можно вытащить, используя полное выражение

for_each(it1,it2, foo)

как функцию.

В листинге #3.2 в круглых скобках написано -1 — это аргумент, отдаваемый функции foo, к этому числу прибавляется 5, а результат возвращается. for_each возвращает функцию, потому его можно воспринимать как функцию, т. е. вызывать как функции и подбирать значения как у функций. Использовать функции не всегда удобно, иногда необходимо хранить состояние, а функции не обладают умением сохранять данные. Казалось бы, что можно использовать статические переменные, но их использование само по себе такое себе. Кроме нежелательного использования статических переменных ещё и параметр может оказыватся неудачным: для алгоритма параметр нужен, но для вывода результата работы параметр может быть как бельмо на глазу. Вот пример, попробуем написать счётчик четных чисел:

На экране появляется 0. Это число, которое мы отдаём как параметр алгоритму, выполняющему роль функции. Т. е. тот последний ноль в скобках. Здесь он как ни крути не к месту. Но а без него (или любого другого числа вместо него) функцию не запустить, потому что функции-обработчику нужен параметр. Конечно, может быть, что какое-то число точно не входит в последовательность, что такое число можно использовать как признак, но такое бывает не всегда. Поэтому можно написать костыли, где учитывать такой несвоеместный параметр, но есть выход получше: использование функторов. Функторы — это классы, а классы хороши тем, что они умеют хранить своё состояние. Т. е. при использовании функтора не придётся использовать статическую переменную, а параметр мешаться не будет.

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

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

Использование лямбда-выражений может поначалу выглядеть довольно уродливо, непонятно, но они на самом деле хорошее средство улучшить читаемость кода, если их использовать тогда, когда нужно встроить небольшие выполняемые фрагменты в алгоритмы.

10 комментариев на «“C++ для начинающих. Алгоритм for_each”»

  1. мир:

    void operator () ( int i) пожалуйста обисните
    эту строчку. (int i)-здес параметр что ли?

  2. Guest:

    void operator () ( int i) принимает параметр типа int и печатает его на экран.

    void — нет возвращаемого значения.

  3. Аноним:

    void operator () ( int i)

    это перегрузка скобок, что бы после объявления объекта obj, можно было ему передать в скобках аргумент, иначе в скобках можно передать при объявлении.

  4. Руслан:

    Это ФУНКТОР! Структура состоит только из функции(перегруженного оператора вызова скобок функции)! void operator() (int i) нужен для того, чтоб когда пишешь obj, тоесть вызываешь обьект этой структуры, то срабатывает перегрузка оператора скобки и тем самым вызыватся функция! Вроде так…если нет то поправьте)

  5. Аноним:

    каким образом происходит передача параметра «i» в функтор?
    в прототипе for_each — принимается только укзатель на функтор но, нет нигде ссылки на аргумент, вызываемой функции (функтора)

  6. Даниил:

    можно ли присвоить for_each какой либо переменной. Например я хочу посчитать сумму нечетных элементов в векторе, могу ли я прописать функцию для определения четности числа, сделать данную функцию не воид, а int и передать как 3 параметр for_each. После написать int sum += for_each(vector.begin(), vector.end(), poiskchet);

    • Посчитать число элементов и сохранить результат в переменную посредством for_each можно, но для выполнения задачи счёта_с_условием следует использовать count_if.

    • Если хочется повыделываться, то сделать это можно так:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Поиск

 
     

Случайная книга в электронном формате

https://www.litres.ru/aleksey-nikolaevich-vasilev/programmirovanie-na-c-v-primerah-i-zadachah-22054458/?lfrom=15589587
Яндекс.Метрика