一、Lambda 的语法形式
Lambda 的语法形式如下:
[外部变量访问方式说明符] (参数表) mutable 或 exception 声明 -> 返回值类型 {函数体}
Lambda表达式具体实例形式如下 : [capture] (parameters)->return_type{ body }
[=](int x, int y) -> bool { return x % 10 < y % 10; }
可以看到,Lambda 主要分为五个部分:[外部变量访问方式说明符]、(参数表)、mutable 或 exception 声明、-> 返回值类型、{函数体}.
二、Lambda使用说明
1、 外部变量访问方式说明符用于捕获外部变量,使得匿名函数体可以使用这些变量,捕获的方法分为引用捕获和值(拷贝)捕获两种
[] 不捕获任何变量;
[&] 按引用捕获所有外部变量;
[=] 按值捕获所有外部变量
[&, var] 默认按引用捕获,仅按值捕获 var;
[=, &var] 默认按值捕获,仅按引用捕获 var;
显示捕获
Lambda表达式通过在最前面的方括号[]来明确指明其内部可以访问的外部变量,这一过程也称作Lambda表达式“的显示捕获
隐式捕获
除了显式列出我们希望使用的来自所在函数的变量之外,还可以让编译器根据lambda体中的代码来推断我们要使用哪些变量。
2、 mutable 或 exception 声明
这部分可以省略。按值传递函数对象参数时,加上 mutable 修饰符后,可以修改传递进来的拷贝(注意是能修改拷贝,而不是
值本身)。exception 声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用 throw(int)。
3、 利用->标识函数返回值的类型,当返回值为 void,或者函数体中只有一处 return 的地方(此时编译器可以自动推断出返回值类型) 时,这部分可以省略。
三、代码演示
#include <iostream>
#include <string>
#include <algorithm>
#include <functional>
#include <vector>
#include <exception>
#include <assert.h>
//函数外值传递与引用传递
void capture_example();
//mutable实例
void mutable_example();
//排序实例
void sort_example();
//数组综合实例
void vector_example();
//回调实例
void Callback_example();
int menu() {
int choice;
std::cout << " **** Menu **** " << std::endl << std::endl;
std::cout << "(1) capture_example. " << std::endl;
std::cout << "(2) mutable_example. " << std::endl;
std::cout << "(3) sort_example. " << std::endl;
std::cout << "(4) vector_example. " << std::endl;
std::cout << "(5) Callback_example. " << std::endl;
std::cout << "(6) Quit. " << std::endl << std::endl;
std::cout << "Your choice: ";
std::cin >> choice;
std::cout << std::endl;
return choice;
}
int main()
{
bool exit = false;
while (1)
{
system("cls"); //清屏
int choice = menu();
switch (choice) {
case (1):
capture_example();
system("pause");
break;
case (2):
mutable_example();
system("pause");
break;
case (3):
sort_example();
system("pause");
break;
case (4):
vector_example();
system("pause");
break;
case (5):
Callback_example();
system("pause");
break;
case (6):
exit = true;
break;
default:
std::cout << "Please select again! " << std::endl;
break;
}
if (exit == true)
break;
}
}
void capture_example()
{
int i=2;
int j = 4;
int k = 6;
//AnonyFunc函数声明定义
auto by_val_lambda = [=,&j,&k]() -> void
{
std::cout <<"by_val_lambda :"<<i << std::endl;//2
std::cout <<"by_val_lambda :"<< &i << std::endl;
std::cout << "by_val_lambda :" << j << std::endl;//0
std::cout << "by_val_lambda :" << k << std::endl;//9
};
auto by_ref_lambda = [&,j,k]() -> void
{
std::cout << "by_ref_lambda :" << i << std::endl;//2
std::cout << "by_ref_lambda :" << &i << std::endl;
std::cout << "by_ref_lambda :" << j << std::endl;//4
std::cout << "by_ref_lambda :" << k<< std::endl;//6
};
i = 1;
j = 0;
k = 9;
std::cout << "current value :" << i << std::endl;//1
std::cout << "current address:" << &i << std::endl;
std::cout << "current value :" << j << std::endl;//0
std::cout << "current value :" << k << std::endl;//9
//AnonyFunc函数调用
by_val_lambda();
by_ref_lambda();
}
void mutable_example()
{
int val = 0;
// 对于const的成员函数,修改非静态的成员变量,所以就出错了
// auto const_val_lambda = [=](){ val = 3; }; //wrong!!!
// 对与mutable标识的lambda表达式,以值传递方式捕获的变量可以在表达式内部修改,但作用域仅限于表达式内部,外部变量的值不会被改变
auto mutable_val_lambda = [=]() mutable {val = 3; std::cout << val << std::endl;/*3*/ };
mutable_val_lambda();
std::cout << val << std::endl; // 0
auto const_ref_lambda = [&]() { val = 4; };
const_ref_lambda();;
std::cout << val << std::endl; // 4
auto mutable_ref_lambda = [&]() mutable { val = 5; };
mutable_ref_lambda();
std::cout << val << std::endl; // 5
int a1 = 111, b = 222;
auto func = [=, &b]() mutable { a1 = 22; b = 333; std::cout << "a:" << a1 << " b:" << b << std::endl;/*a:22 b:333*/ };
func();
std::cout << "a1:" << a1 << " b:" << b << std::endl;//a:111 b:333
try
{
int x = 3;
int y = 2;
auto func_division = [x, y]() mutable /*throw()*/
{
y = 0;
if (y == 0) throw (y);
return x / y;
};
func_division();
}
catch (int)
{
std::cout << "error of Integer division by zero " << std::endl;
//exit(1); //异常退出程序
}
}
void sort_example()
{
int a[4] = { 11, 4, 2, 33 };
std::sort(a, a + 4, [=](int x, int y) -> bool { return x % 10 < y % 10; });
std::for_each(a, a + 4, [=](int x) { std::cout << x << " "; });//11 2 33 4
std::cout << std::endl;
}
double eval(std::function<double(double)> f, double x = 2.0) { return f(x); }
void vector_example()
{
//auto eval = [](std::function<double(double)> f, double x = 2.0) { return f(x); };
auto newline= []() {std::cout << std::endl; };
std::function<double(double)> f0 = [](double x) {return 1; };
auto f1 = [](double x) {return x; };
decltype(f0) fa[3] = { f0,f1,[](double x) {return x * x; } };
std::vector<decltype(f0)> fv = { f0,f1 };
fv.push_back([](double x) {return x * x; });
for (size_t i = 0; i < fv.size(); i++) { std::cout << fv[i](2.0) << "\t" ;}//1 2 4
newline();
for (size_t i = 0; i < 3; i++) { std::cout << fa[i](2.0) << "\t"; }//1 2 4
newline();
for (auto& f : fv) { std::cout << f(2.0) << "\t"; }//1 2 4
newline();
for (auto& f : fa) { std::cout << f(2.0) << "\t"; }//1 2 4
newline();
std::cout << eval(f0) << "\n";//1
std::cout << eval(f1) << "\n";//2
//一个没有指定任何捕获的lambda函数,可以显式转换成一个具有相同声明形式函数指针
//std::cout << [](std::function<double(double)> f, double x = 2.0) { return f(x); }(f0) << "\n";
//std::cout << [](std::function<double(double)> f, double x = 2.0) { return f(x); }(f1) << "\n";
auto a_lambda_func = [](int x) {std::cout << "calls the lambda" << std::endl; };
void(*func_ptr)(int) = a_lambda_func;
func_ptr(4); //calls the lambda
}
template <class Callback>
int CollectFeatures(Callback CB)
{
int count = 0;
for (int i = 0; i < 10; i++)
{
if (CB(i))
{
count++;
}
}
return count;
}
bool AddFeature(size_t Feature)
{
return Feature % 2;
}
void Callback_example()
{
int i = CollectFeatures([](size_t Feature) -> bool { return AddFeature(Feature); });
std::cout << i << std::endl;//5
}
四、注意点
1、 在lambda匿名函数体里边,按值捕获到的变量,实质上是调用者函数中变量的只读拷贝(read - only),后面变量值更改,不改变捕获的变量值,按引用捕获,调用者函数中该变量的值,随调用时引用值的变化而变化。
2、 按值捕获到的变量,加入了mutable后,匿名函数体内部可以修改这个拷贝的值,也就是说调用者函数中该变量的值依然不会被改变;
3、 捕获列表为空,完全相当于调用普通函数。可以显式转换成一个具有相同声明形式函数指针。
4、当混合使用隐式捕获和显式捕获时,捕获列表中的第一个元素必须是一个”&”或”=“。此符号指定了默认捕获方式为引用或值;并且显式捕获的变量必须使用与隐式捕获不同的方式。即,如果隐式捕获是引用方式,则显式捕获命名变量必须采用值方式;类似的,如果隐式捕获采用的是值方式,则显式捕获命名变量必须采用引用方式。
五、总结
1、lambda 表达式,即用即删除,很适合需要完成一项功能,但是此功能只在此一处使用。
2、lambda表达式使用方便、代码简洁、定义和使用在同一个地方,而且不需要取名字就可以使用,它本身就是一个匿名的函数。
3、lambda表达式可以作为回调函数,传递给某些应用,比如消息处理。