Лямбда функции c захват переменных

Обновлено: 07.07.2024

Как выполнить захват по ходу (также известный как ссылка rvalue) в лямбда-выражении C ++ 11?

Я пытаюсь написать что-то вроде этого:

Обобщенный лямбда-захват в C ++ 14

В C ++ 14 будет так называемый обобщенный лямбда-захват . Это позволяет захватить ход. Следующее будет юридическим кодом на C ++ 14:

Но он гораздо более общий в том смысле, что захваченные переменные могут быть инициализированы чем угодно:

В C ++ 11 это пока невозможно, но есть некоторые уловки, связанные с вспомогательными типами. К счастью, компилятор Clang 3.4 уже реализует эту замечательную функцию. Компилятор будет выпущен в декабре 2013 г. или январе 2014 г., если будет сохранен темп недавнего выпуска.

Обходной путь для захвата движения

Вот реализация вспомогательной функции make_rref , которая помогает с искусственным захватом движения.

А вот тестовый пример для этой функции, которая успешно работала на моем gcc 4.7.3.

Недостатком здесь является то, что lambda можно копировать, и при копировании утверждение в конструкторе копирования rref_impl не работает, что приводит к ошибке времени выполнения. Следующее может быть лучшим и даже более общим решением, потому что компилятор обнаружит ошибку.

Эмуляция обобщенного лямбда-захвата в C ++ 11

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

Здесь lambda - объект-функтор (почти реальная лямбда), который захватил std::move(p) при передаче в capture() . Второй аргумент capture - это лямбда, которая принимает захваченную переменную в качестве аргумента. Когда lambda используется в качестве объекта функции, тогда все аргументы, которые ему передаются, будут перенаправлены во внутреннюю лямбду в качестве аргументов после захваченной переменной. (В нашем случае нет никаких дополнительных аргументов для пересылки). По сути, происходит то же, что и в предыдущем решении. Вот как реализован capture :

Это второе решение также является более чистым, поскольку оно отключает копирование лямбда-выражения, если захваченный тип не копируется. В первом решении это можно проверить только во время выполнения с помощью assert() .

Я смотрел на эти ответы, но мне было трудно читать и понимать переплет. Так что я сделал вместо этого класс, который перемещался по копии. Таким образом, он ясно показывает, что он делает.

Класс move_with_copy_ctor и его вспомогательная функция make_movable() будут работать с любым подвижным, но не копируемым объектом. Чтобы получить доступ к обернутому объекту, используйте operator()() .

Что ж, адрес указателя может отличаться. ;)

Кажется, это работает на gcc4.8

Поздно, но поскольку некоторые люди (включая меня) все еще застревают на С ++ 11:

Если честно, мне не очень нравится ни одно из опубликованных решений. Я уверен, что они сработают, но для этого потребуется много дополнительных вещей и / или загадочный синтаксис std::bind . и я не думаю, что стоит затрачивать усилия на такое временное решение, которое все равно будет переработано. при обновлении до c ++> = 14. Поэтому я думаю, что лучшим решением будет полностью отказаться от захвата перемещения для c ++ 11.

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

Если происходит очень редкий случай, когда move указатель действительно обязательно должен быть move (например, вы хотите явно удалить указатель в отдельном потоке из-за большой продолжительности удаления, или производительность имеет решающее значение), это в значительной степени единственный случай, когда я все еще использую необработанные указатели в С ++ 11. Их, конечно, тоже можно копировать.

Обычно я отмечаю эти редкие случаи знаком //FIXME: , чтобы убедиться, что после обновления до C ++ 14 будет произведен рефакторинг.

Да, в наши дни необработанные указатели не одобряются (и не без оснований), но я действительно думаю, что в этих редких (и временных!) Случаях они являются лучшим решением.

Вы можете добиться большей части желаемого, используя std::bind , нравится:

Хитрость здесь в том, что вместо захвата вашего объекта, предназначенного только для перемещения, в списке захватов мы делаем его аргументом, а затем используем частичное приложение через std::bind , чтобы он исчез. Обратите внимание, что лямбда принимает его по ссылке , потому что на самом деле он хранится в объекте привязки. Я также добавил код, который записывает в фактический подвижный объект, потому что вы, возможно, захотите это сделать.

В C ++ 14 вы можете использовать обобщенный лямбда-захват для достижения тех же целей с помощью этого кода:

Но этот код не дает вам ничего, чего у вас не было в C ++ 11, через std::bind . (В некоторых ситуациях обобщенный лямбда-захват более эффективен, но не в этом случае.)

Вы можете обойти проблему с классом-оболочкой и другим уровнем косвенности, но, возможно, вам не нужен std::function вообще. В зависимости от ваших потребностей вы можете использовать std::packaged_task ; он будет выполнять ту же работу, что и std::function , но не требуется, чтобы функция была копируемой, только перемещаемой (аналогично, std::packaged_task только подвижный). Обратной стороной является то, что, поскольку он предназначен для использования вместе с std :: future, вы можете вызвать его только один раз.

Вот небольшая программа, в которой показаны все эти концепции.

Я установил указанную выше программу на Coliru, чтобы вы могли запускать и играть с код.

Вот типичный результат .

Вы увидите, что местоположения кучи повторно используются, показывая, что std::unique_ptr работает правильно. Вы также видите, как сама функция перемещается, когда мы помещаем ее в оболочку, которую мы скармливаем std::function .

Если мы переключимся на использование std::packaged_task , последняя часть станет

Итак, мы видим, что функция была перемещена, но вместо того, чтобы перемещаться в кучу, она находится внутри std::packaged_task , находящегося в стеке.

Лямбда-выражения. Общие понятия о лямбда-выражениях. Лямбда-оператор. Одиночные и блочные лямбда-выражения

Содержание

Поиск на других ресурсах:

1. Какие есть способы создания анонимных функций?

Более подробно работа анонимных методов описывается в темах:

Цель использования лямбда-выражений точно такая же как и анонимных методов. Лямбда-выражения являются альтернативой анонимным методам.
Лямбда-выражения позволяют программировать функции в упрощенном виде без использования имени с помощью специального оператора, который обозначается ‘=>’ .

3. Что такое лямбда-оператор?

4. Какие существуют виды лямбда-выражений?
  • одиночные лямбда-выражения;
  • блочные лямбда-выражения.

5. Какая общая форма объявления одиночного лямбда-выражения? Примеры

Общая форма объявления одиночного лямбда-выражения, которое принимает один параметр:

  • параметр – параметр, который получает на входе лямбда-выражение;
  • выражение – непосредственно выражение, которое вычисляется.

Общая форма объявления одиночного лямбда-выражения, которое принимает несколько параметров:

  • список_параметров – два и более параметра, которые используются в лямбда-выражении.

Пример 1 одиночного лямбда-выражения, которое вычисляет значение cos(x+2) :

В данном примере x есть входным параметром. Данное выражение заменяет функцию приблизительно следующего вида:

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

Пример 2 одиночного лямбда-выражения, которое получает 3 параметра с именами a , b , c . В данном примере определяется, можно ли из длин сторон a , b , c образовать треугольник?

В вышеприведенном лямбда-выражении используется правило: сумма двух любых длин сторон треугольника больше длины третьей стороны.

6. Какая общая форма объявления блочного лямбда-выражения? Пример

Общая форма объявления блочного лямбда выражения, получающего один параметр:

Общая форма объявления блочного лямбда-выражения, получающего несколько параметров:

Пример.

В примере объявлено лямбда-выражение, которое получает параметром целое число x . В коде лямбда-выражения делается подсчет количества цифр ‘5’ в целом числе x . Например, для числа 5854 лямбда-выражение возвращает значение 2.

Возвращение значения осуществляется оператором return .

7. Какие действия (шаги) нужно выполнить, чтобы в программе применить лямбда-выражение? Пример

Чтобы в программном коде применить лямбда-выражение, нужно выполнить следующую последовательность действий:

  1. Объявить тип делегата, совместный с лямбда-выражением.
  2. Объявить переменную этого типа делегата (экземпляр делегата).
  3. Присвоить переменной (экземпляру делегата) лямбда-выражение.
  4. Вызвать переменную (экземпляр делегата) в программном коде.

Пример. Пройдем все шаги последовательно для задачи из предшествующего пункта (п.6). Пусть нужно продемонстрировать работу лямбда-выражения, которое получает входным параметром целое число x и вычисляет количество цифр ‘5’ в этом числе.

1. Объявить тип делегата. Тип делегата объявляется в некотором классе.

Имя типа делегата CalcNum5. Делегат этого типа будет получать один входной параметр ( x ). Делегат этого типа возвращает значение типа int .

2. Объявить переменную этого типа делегата. Переменная объявляется в некотором программном коде. Это может быть код произвольного метода класса, код обработчика события и т.п.

3. Присвоить переменной лямбда-выражение. В методе объявления переменной объявляется присваивание:

4. Вызвать переменную. В том же методе где объявлено лямбда-выражение реализован вызов переменной. Имя CN – это имя делегата, который содержит лямбда-выражение.

8. Пример использования одиночного лямбда выражения, получающего один параметр

В данном примере продемонстрировано использование лямбда-выражения, в котором вычисляется функция y = sin²x .
Сначала объявляется тип делегата, который получает один параметр типа float и возвращает значение типа float . Параметр имеет название – x . В обработчике события button1_Click () продемонстрировано использование лямбда-выражения.

9. Пример использования одиночного лямбда-выражения, которое получает несколько параметров

В данном примере, для заданных x , y вычисляется функция z = sin x² · cos y .

Чтобы объявить лямбда-выражение, сначала нужно объявить тип делегата, который получает 2 параметра и возвращает значение. Тип параметров и значения – double .

Объявление типа делегата имеет вид:

Демонстрация использования лямбда-выражения из другого метода (например, обработчика события)

10. Пример блочного лямбда-выражения, которое получает несколько параметров

Задача

Дано три разных целых числа. Реализовать лямбда-выражение, которое находит наибольшее из этих трех чисел.

Решение

Сначала объявляется тип делегата, который получает три параметра целого типа и возвращает целое значение

Затем, в другом программном коде, можно объявить лямбда-выражение и продемонстрировать его работу

11. Можно ли в одном методе реализовать различные лямбда-выражения, которые соответствуют одному типу делегата?

Пример.

Пусть объявлен тип делегата, получающий три параметра целого типа.

Тогда, в обработчике события или в другом методе можно написать так:

  • находят максимальное значение;
  • находят сумму параметров;
  • находят произведение параметров.

Лямбда-функции появились в C++11. Они представляют собой анонимные функции, которые можно определить в любом месте программы, подходящем по смыслу.

Приведу пример простейшей лямбда функции:

Выражение auto myLambda означает объявление переменной с автоматически определяемым типом. Крайне удобная конструкция C++11, которая позволяет сделать ваш код более лаконичным и устойчивым к изменениям. Настоящий тип лямбда-функции слишком сложный, поэтому набирать его нецелесообразно.

Прежде, чем перейти к более сложным примерам лямбда-функций, определим вспомогательную шаблонную функцию:

В качестве аргумента она принимает любой объект, который можно вызвать с аргументом -5 . Более подробно о создании таких функций мы говорили, когда рассматривали указатели на функции в C++. Мы будем передавать в call() наши лямбда-функции для запуска.

Сначала просто выведем переданное лямбда-функции значение:

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

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

Обратите внимание на побочный эффект от связывания переменных с лямбда-функцией по ссылке:

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

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

Если требуется привязать сразу все переменные, то можно использовать следующие конструкции:

Допустимо и комбинирование:

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

Когда использовать лямбда-функции?

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

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

Этот код интуитивно понятен, поэтому вы сами с ним легко разберетесь без моих пояснений. Приведу лишь то, что он выведет на консоль:

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

Что такое лямбда-выражения в C++11? Когда я буду его использовать? Какой класс проблем они решают, что было невозможно до их введения?

несколько примеров, и варианты использования были бы полезны.

C++ включает в себя полезные общие функции, такие как std::for_each и std::transform , что может быть очень удобным. К сожалению, они также могут быть довольно громоздкими в использовании, особенно если функтор вы хотели бы применить является уникальным для конкретной функции.

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

В C++03 у вас может возникнуть соблазн написать что-то вроде следующего, чтобы сохранить функтор локальным:

однако это не допускается, f не может быть передана шаблон функция в C++03.

C++11 вводит lambdas позволяет написать встроенный анонимный функтор для замены struct f . Для небольших простых примеров это может быть чище читать (он держит все в одном месте) и потенциально проще поддерживайте, например, в простейшей форме:

лямбда-функции-это просто синтаксический сахар для анонимных функторов.

типы возврата

в простых случаях для вас выводится тип возврата лямбды, например:

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

для решения этой вы разрешено явно указывать тип возврата для лямбда-функции, используя -> T :

"захват" переменные

до сих пор мы не использовали ничего, кроме того, что было передано лямбде внутри него, но мы также можем использовать другие переменные внутри лямбды. Если вы хотите получить доступ к другим переменным, вы можете использовать предложение capture ( [] выражения), который до сих пор не использовался в этих примерах, например:

вы можете захватить путем как ссылка, так и значение, которое вы можете указать с помощью & и = соответственно:

  • [&epsilon] захват по ссылке
  • [&] захватывает все переменные, используемые в лямбда по ссылке
  • [=] захватывает все переменные, используемые в лямбда по значению
  • [&, epsilon] захватывает переменные, такие как [&], но epsilon по значению
  • [=, &epsilon] захватывает переменные, такие как [ = ], но epsilon by ссылка

созданный operator() is const по умолчанию, с подразумеваемым, что захваты будут const при обращении к ним по умолчанию. Это приводит к тому, что каждый вызов с одним и тем же входом приведет к одному и тому же результату, однако вы можете отметьте лямбду как mutable просить operator() произведен не const .

концепция c++ лямбда-функции берет свое начало в лямбда-исчислении и функциональном программировании. Лямбда-это неназванная функция, которая полезна (в реальном программировании, а не в теории) для коротких фрагментов кода, которые невозможно повторно использовать и не стоит называть.

в C++ лямбда-функция определяется следующим образом

или во всей красе

[] список захвата , () список аргументов и <> тело функции.

список захвата

список захвата определяет, что снаружи лямбды должно быть доступно внутри тела функции и как. Это может быть:

  1. значение: [x]
  2. ссылка [&x]
  3. любая переменная в настоящее время в области по ссылке [&]
  4. то же, что и 3, но по значению [=]

вы можете смешать все вышеперечисленное в список разделенных запятыми [x, &y] .

аргумент

список аргументов такой же, как и в любой другой функции C++.

тело функции

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

тип возврата вычет

если лямбда имеет только один оператор return, тип return может быть опущен и имеет неявный тип decltype(return_statement) .

Mutable

если a лямбда помечается изменяемой (например, []() mutable < >) разрешено изменять значения, которые были захвачены по значению.

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

в C++14 лямбда были расширены различными предложениями.

Инициализирован Лямда Захватывает

элемент списка захвата теперь можно инициализировать с помощью = . Это позволяет переименовывать переменные и захватывать путем перемещения. Пример взят из стандарта:

и один взятый из Википедии, показывающий, как захватить с std::move :

Универсальный Лямбда

лямбда теперь может быть общим ( auto было бы эквивалентно T вот если T были аргументом шаблона типа где-то в окружающая область):

Улучшенный Тип Возврата Вычет

C++14 позволяет выводить типы возврата для каждой функции и не ограничивает его функциями формы return expression; . Это также распространяется на лямбды.

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

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

аналогично, вы можете использовать лямбда-выражения для инициализировать переменные на основе результата алгоритма.

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

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

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

ответы

Q: Что такое лямбда-выражение В C++11?

a: под капотом, это объект класса автоматически с перегрузкой оператор () const. Такой объект называется закрытие и создан компилятором. Эта концепция "закрытия" близка к концепции привязки из C++11. Но lambdas обычно генерируют лучший код. И звонки через закрытие позволяют полностью inlining.

Q: когда бы я использовал один?

A: чтобы определить "простую и небольшую логику" и попросить компилятор выполнить генерацию из предыдущего вопроса. Вы даете компилятору некоторые выражения, которые вы хотите быть внутри оператора(). Все остальное компилятор будет генерировать для вас.

Q: какой класс проблемы они решают, что не было возможно до их введения?

A: это какой-то синтаксический сахар, такой как перегрузка операторов вместо функций для пользовательских add, subrtact оперативный. Но это экономит больше строк ненужного кода, чтобы обернуть 1-3 строки реальной логики в некоторые классы и т. д.! Некоторые инженеры думают, что если количество строк меньше, то есть меньше шансов сделать ошибки в нем (я так думаю)

пример использования

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

1. Захваченные значения. Что вы можете захватить

1.1. Вы можете ссылаться на переменную со статической продолжительностью хранения в lambdas. Они все в плену.

1.2. Вы можете использовать лямбда для значений захвата "по значению". В таком случае захваченные vars будут скопированы в объект функции (закрытие).

1.3. Вы можете захватить ссылку. & -- в этом контексте означает ссылку, а не указатели.

1.4. Существует нотация для захвата всех нестатических vars по значению, или по ссылке

1.5. Существует нотация для захвата всех нестатических vars по значению или по ссылке и указания smth. больше. Образцы: Захват всех нестатических vars по значению, но по ссылке захват Param2

захват всех нестатических vars по ссылке, но по значению захвата Param2

2. Тип возврата вычет

2.1. Лямбда-тип возврата может быть выведен, если лямбда-одно выражение. Или вы можете явно укажите его.

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

3. Захваченные значения. Что вы не можете захватить

3.1. Вы можете захватить только локальные vars, а не переменную-член объекта.

4.1. лямбда не указатель функции, и это не анонимная функция, но может быть неявно преобразован в указатель на функцию.

п.з.

в C++14 добавлена дополнительная функция, названная "init capture". Это позволяет выполнять arbitarily объявление данных о закрытии члены:

вы можете написать функцию на месте, чтобы использовать ее как переданную для применения ниже:

но вы не можете этого сделать:

из-за ограничений в стандарт C++11. Если вы хотите использовать захваты, вы должны полагаться на библиотеку и

(или какая-то другая библиотека STL, как алгоритм, чтобы получить его косвенно), а затем работать с std::function вместо передачи нормальных функций в качестве параметров, таких как:

What is a lambda expression?

A лямбда-выражение, иногда также называют лямда - функция или (строго говоря неправильно, но разговорно) как лямда -, упрощенная нотация для определение и использование анонимный объект функции. Вместо определения именованного класса с помощью operator (), позже создание объекта этого класса и, наконец, вызывая его, мы можем использовать стенографию.

When would I use one?

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

What class of problem do they solve that wasn't possible prior to their introduction?

здесь я думаю, что каждое действие, выполненное с лямбда-выражением, может быть решено без них, но с гораздо большим кодом и гораздо большей сложностью. Лямбда-выражение-это способ оптимизации кода и способ сделать его более привлекательным. Как грустно от Stroustup:

эффективные способы оптимизация

Some examples

или через функцию

если вам нужно, вы можете назвать lambda expression , как показано ниже:

или предположим другой простой пример

будет генерировать следующий

[] - это список захвата или lambda introducer : если lambdas не требуют доступа к их локальной среде мы можем использовать его.

цитата из книги:

первый символ лямбда-выражения всегда [. Лямбда интродьюсер может принимать различные формы:

• []: пустой список захвата. Этот подразумевает, что никакие локальные имена из окружающего контекста не могут использоваться в теле лямбды. Для таких лямбда-выражений данные получены из аргументы или от нелокальных переменных.

• [&]: неявный захват ссылка. Можно использовать все локальные имена. Все локальные переменные доступ по ссылке.

• [=]: неявный захват по значению. Все местные можно использовать имена. Все имена относятся к копиям локальных переменных принятые в точке вызова лямбда-выражения.

• [capture-list]: явный захват; список захвата - это список имен локальных переменных, которые должны быть захвачены (т. е. сохранены в объекте) по ссылке или по значению. Переменные с именами предшествовать & захватываются ссылка. Другие переменные захвачен по стоимости. Список захвата также содержат это и имена, за которыми следуют . в качестве элементов.

• [&, capture-list]: неявный захват по ссылке всех локальных переменных с именами, не указанными в списке. Список захвата может содержать это. Перечисленным именам не может предшествовать &. Переменные, названные в список захвата захватываются по значению.

• [=, capture-list]: неявно захват по значению все локальные переменные с именами, не упомянутыми в списке. Список захвата не может содержать это. Перечисленным именам должно предшествовать &. Варианты, названные в списке захвата, захватываются по ссылке.

отметим, что локальное имя предшествует и всегда захвачен ссылка и локальное имя, не переданное&, всегда фиксируются значение. Только захват по ссылке позволяет изменять переменные в вызов окружающая среда.

Additional

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

Ну, одно практическое использование, которое я узнал, - это сокращение кода котельной плиты. Например:

без лямбда, вам может потребоваться сделать что-то для разных bsize случаях. Конечно, вы можете создать функцию, но что, если вы хотите ограничить использование в рамках функции пользователя душа? природа лямбды удовлетворяет этому требованию, и я использую ее для этого случая.

Читайте также: