Kselax.ru

Hacker Kselax — the best hacker in the world

Menu
  • Блог
  • Контакты
  • wp plugin генератор
  • Русский
    • Русский
    • English
Menu

С++ (С++ 11) Лямбда выражения

Posted on 2 августа, 201414 августа, 2014 by admin

75fed44f93a20fff0e6db06381cd1717Здорова Господа!!!

От решил написать статью о лямбда выражения в С++, что это такое и как их использовать?!

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

[note]

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

[/note]

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

Содержание:

1. Пример лямбда-выражения
2. Директива фиксации
3. Фиксация конкретных переменных
4. Шаблоны и лямбда-выражения
5. Оболочка лямбда — выражения
6. Нахождение НОД (наибольший общий делитель)
7. Заполнение вектора числами Фибоначчи используя лямбда-выражение.


Пример лямбда-выражения

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
using std::cout;
using std::endl;
#include <vector>
using std::vector;
#include <algorithm>
using std::transform;
 
int main()
{
vector<int> v(3,2);
for(int i=0;i<v.size();i++)
cout <<v[i]<<' ';
cout <<endl;
 
vector<int> cube(3);
transform(v.begin(),v.end(),cube.begin(),
[](double x)
{
return x*x*x;
}
);
for(int i=0;i<cube.size();i++)
cout <<cube[i]<<' ';
cout <<endl;
 
return 0;
}

вывод:

QIP Shot - Screen 120

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

1
2
3
4
[](double x)
{
return x*x*x;
}

возводится в куб и с помощью алгоритма transform записывается в вектор cube

Рассмотрим подробнее используемое лямбда выражение

1
[](double x){return x*x*x;}

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

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

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

1
[](double x)->double{return x*x*x;}

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

1
2
3
4
5
[](double x)->double{
                                 double result(x*x*x);
                                 cout <<result<<" ";
                                 return result;
                            }

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

Директива фиксации

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

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
using std::cout;
using std::endl;
#include <vector>
using std::vector;
#include <algorithm>
using std::transform;
 
int main()
{
vector<int> v(3,2);
for(int i=0;i<v.size();i++)
cout <<v[i]<<' ';
cout <<endl;
 
vector<int> cube(3);
int num=10;
transform(v.begin(),v.end(),cube.begin(),
[=](double x)->double
{
return num*x*x*x;
}
);
for(int i=0;i<cube.size();i++)
cout <<cube[i]<<' ';
cout <<endl;
 
return 0;
}

вывод:

QIP Shot - Screen 121

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

1
[=](double x)->double{return num*x*x*x;}

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

1
2
3
4
5
6
7
transform(v.begin(),v.end(),cube.begin(),
[=](double x)->double
{
                num+=10;//Недопустимо!
return num*x*x*x;
}
);

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

1
2
3
4
5
6
7
8
transform(v.begin(),v.end(),cube.begin(),
[=](double x)mutable->double
{
num+=10;
return num*x*x*x;
}
);
cout <<"num= "<<num<<endl;//10

Теперь можно изменять копию любой переменной в пределах окружающей области видимости, не изменяя исходную переменную. После выполнения этого оператора значение переменной num все еще будет равно 10. Лямбда-выражение запоминает локальное значение переменной num от вызова к вызову, поэтому для первого элемента значение переменной num будет 10+10=20, для второго 20+10=30 и т.д.

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

1
2
3
4
5
6
7
8
9
10
vector<int> cube(3);
int num=10;
transform(v.begin(),v.end(),cube.begin(),
[&](double x)->double
{
num+=10;
return num*x*x*x;
}
);
cout <<"num= "<<num<<endl;//40

вывод:

QIP Shot - Screen 122

Здесь мы не нуждаемся в ключевом слове mutable. Все переменные окружающей области видимости доступны по ссылке, таким образом, можете использовать и изменять их значения. В результате выполнения этого кода переменная num содержит значение 40. Если вы думали что будет 20, то вспомните, что лямбда-выражение выполняется по разу для каждого элемента вектора v, а вычисление кубов элементов последовательно увеличит значение переменной num с 10 до 40.

Фиксация конкретных переменных

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

1
2
3
4
5
6
7
transform(v.begin(),v.end(),cube.begin(),
[&num](double x)->double
{
num+=10;
return num*x*x*x;
}
);

Теперь переменная num — единственная доступная в окружающей области видимости, причем по ссылке. Если пропустить симво &, переменная num была бы доступна по значению и не обновляема. Если вы хотите указать в директиве фиксации несколько переменных, то отделяйте их запятыми. В список директивы фиксации можно включить знак =, а также явно указать имена переменных. Например, при директиве фиксации [=,&num] у лямбда-выражения был бы доступ к переменной num по ссылке, а ко всем остальным переменным в окружающей области видимости — по значению. И наоборот, директива фиксации [&,num] предоставит доступ к переменной num по значению, а ко всем другим по ссылке. В функции-члене класса вы можете включить в директиву фиксации для лямбда-выражения указатель this, который обеспечит доступ к другим функциям и переменным-членам, которые принадлежат классу.Лямбда-выражение может также включать спецификацию исключения throw(). Это указывает на то, что лямбда-выражение не передает исключение.

1
2
3
4
5
6
7
8
int num=10;
transform(v.begin(),v.end(),cube.begin(),
[&num](double x)throw()->double
{
num+=10;
return num*x*x*x;
}
);

Если хотите включить ключевое слово mutable вместе со спецификацией throw(), то оно должно предшествовать спецификации throw() и быть отделено от него одним или несколькими пробелами.

Шаблоны и лямбда-выражения

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
using std::cout;
using std::endl;
#include <vector>
using std::vector;
#include <algorithm>
using std::transform;
 
//Шаблон функции возведение в куб
template<class T>
vector<T> cub(const vector<T>& v)
{
vector<T> result(v.size());
int num=10;
transform(v.begin(),v.end(),result.begin(),
[&num](const T& x)->T
{
num+=10;
T result=num*x*x*x;
return result;
}
);
return result;
}
 
int main()
{
vector<int> v(3,2);
for(int i=0;i<v.size();i++)
cout <<v[i]<<' ';
cout <<endl;
 
vector<int> cube;
//вызов функции
cube=cub(v);
 
for(int i=0;i<cube.size();i++)
cout <<cube[i]<<' ';
cout <<endl;
 
return 0;
}

вывод:

QIP Shot - Screen 123

В примере выше мы создали шаблон функции vector<T> cub(vector<T>), функцию вызываем и все у нас нормально работает. В коде выше все понятно как работает функция.

Оболочка лямбда — выражения

Расширение заголовка STL functional определяет шаблон класса function<>, который можно использовать для определения оболочки объекта функции; он включает лямбда-выражение. Шаблон function<> называется оболочкой полиморффной функции, поскольку экземпляр шаблона может служить оболочкой для множества объектов функции с заданным списком параметров и типом возвращаемого значения. Я не буду обсуждать все подробности шаблона класса function<>, а продемонстрирую лишь возможность его использования для оболочки лямбда-выражения. Применение шаблона function<> для оболочки лямбда-выражения фактически присваивает ему имя, делая возможной рекурсию в пределах лямбда-выражения. Это позволяет также использовать лямбда-выражение в нескольких операторах или передавать его нескольким разным функциям. В разное время та же оболочка функции могла бы послужить оболочкой для разных лямбда-выражений.Чтобы создать объект оболочки из шаблона function<>, необходимо предоставить лямбда-выражению информацию о типе возвращаемого значения и типах всех параметров. Вот как вы могли бы создать объект типа fucntion, способный стать оболочкой объекта функции, имеющего один параметр типа double и возвращающий значение типа int

1
2
function<int(double> f=[](double x)->int{return                                        
        static_cast<int>(x*x);};

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

mnojitel6Нахождение НОД (наибольший общий делитель)

 

Код этого примера находит наибольший общий множитель для пары целочисленных значений. Наибольший общий множитель — это наибольшее число. на которое делятся нацело два целых числа. Наибольший общий множитель называется также наибольшим общим делителем. Код приведен ниже.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
using std::cout;
using std::endl;
#include <functional>
using std::function;
 
int main()
{
setlocale(LC_ALL,"Russian");
//Оболочка лямбда-выражения для вычисления общего множителя
function<int(int,int)>hcf=[&](int m,int n)mutable->int{
if(m<n)return hcf(n,m);
int remainder(m%n);
if(0==remainder)return n;
return hcf(n, remainder);};
 
int a(17719), b(18879);
cout <<"Для чисел " <<a<<" и "<<b
<<"общий множитель "<<hcf(a,b)<<endl;
 
a=103*53*17*97; b=3*29*103;
cout <<"Для чисел "<<a<<" b "<<b
<<"общий множитель "<<hcf(a,b)<<endl;
 
return 0;
}

вывод:

QIP Shot - Screen 124

Первый оператор в функции min() создает объект hcf с помощь шаблона класса function

1
2
3
4
5
function<int(int,int)>hcf=[&](int m,int n)mutable->int{
if(m<n)return hcf(n,m);
int remainder(m%n);
if(0==remainder)return n;
return hcf(n, remainder);};

Лямбда-выражение имеет два параметра типа int и возвращает значение типа int, таким образом, объект hcf имеет тип function(int(int,int)>.

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

Код лямбда-выражения подразумевает, что аргумент m является большим из двух переданных чисел. Но если это не так, то вызов функции hcf() меняет значения аргументов местами. Если первый аргумент — большее число, вычисляется остаток от деления m на n. Если остаток нулевой, то значение аргумента n-наибольший общий множитель, который и возвращается. Если остаток ненулевой, то вызывается функция hcf() со значением аргумента n и остатком.

В функции main() функция hcf() вызывается в первом операторе вывода с аргументами а и b.

1
2
cout <<"Для чисел "<<a<<" и "<<b
       <<" общий множитель "<<hcf(a,b)<<endl;

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

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

78125От еще один пример использования лямбда-выражения для заполнения вектора числами Фибоначи.

Ряд Фибоначи — интересная последовательность целых чисел, которая нередко встречается в естественном мире. Вы можете увидеть это на примере листьев и веток, отходящих от ствола дерева, спиралей на сосновых шишках и некоторых морских ракушках. Она состоит из последовательности целых чисел 0,1, 1, 2,3,5, 8,13,21…где каждое число после первых двух — сумма двух предыдущих чисел (обратите внимание на то, что в прогрессии иногда отсутствует начальный нуль).

Показать »

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
using std::cout;
using std::endl;
#include <vector>
using std::vector;
#include <algorithm>
using std::transform;
 
int main()
{
vector<int> v(10);
 
for(int i=0;i<v.size();i++)
cout <<v[i]<<' ';
cout <<endl<<endl;
 
int i=2;
v[0]=0;
v[1]=1;
transform(v.begin()+2,v.end(),v.begin()+2,
[&i,&v](int& x)->int
{
int n1=v[i-1];
int n2=v[i-2];
i++;
int result=n1+n2;
return result;
});
 
for(int i=0;i<v.size();i++)
cout <<v[i]<<' ';
cout <<endl;
 
return 0;
}

вывод:

QIP Shot - Screen 125

 

 

 

[youtube]https://www.youtube.com/watch?v=3V5aZBCY4IM[/youtube]

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

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

Рубрики

  • C++ (293)
  • JavaScript (1)
  • linux (1)
  • MFC (39)
  • node.js (2)
  • React (3)
  • vBulletin (5)
  • Visual Studio (9)
  • wordpress (18)
  • Разное (29)

Метки

Ajax bootstrap CentOS CLI expressjs FormData GDlib google Invisible reCAPTCHA JWT media MFC php react-router-dom redux repository wordpress RTTI STL vBulletin vector Visual Studio WINAPI wordpress wp-plugins XMLHttpRequest Двоичное дерево Задачи С++ Игры С++ Исключения С++ О-большое Операторы_С++ Перегрузка операторов С++ Поиск С++ Потоки Проектирование_С++ С++ Типы_С++ Типы С++ Шаблоны С++ библиотеки локализация макросы С++ сортировка С++

Свежие комментарии

  • RA3PKJ к записи visual C++, создание диалоговых окон.
  • JasonReant к записи Создание и использование статических библиотек .lib в visual studio.
  • MyWin2020 к записи Программка для заполнения форума на vBulletin 3.8.7
  • ScottJip к записи Создание и использование статических библиотек .lib в visual studio.
  • ArnoldKig к записи Создание и использование статических библиотек .lib в visual studio.
©2021 Kselax.ru Theme by ThemeGiant