本文转自《深入理解c++11》一书关于lambda函数的使用指南,在此相关的捕获列表知识请读者参考原书对应章节,在结合本文(原书中的示例代码)学习和理解效果更佳
#include<iostream>
using namespace std;
/*
int main()
{
int grils{3},boys{4};
// auto totalChild=[](int x,int y)->int{return x+y;};
auto totalChild=[=](void)->int{return grils+boys;};
// return totalChild();
[]{};
int a{3},b{4};
[=]{return a+b;}; //省略参数列表和返回类型,返回类型由编译器推断为int
auto fun1=[&](int c){b=a+c;}; //胜利返回值,无返回值
// fun1(2);
// cout<<b<<endl; //2
auto fun2=[=,&b](int c)->int{return b+=a+c;};
}
*/
class _functor{
public:
int operator()(int x,int y){
return x+y;
}
};
//int main()
//{
// int grils{3},boys{4};
// _functor totalChild;
// return totalChild(4,6);
//}
/*
相比于函数,仿函数可以有初始状态,一般通过class定义私有成员,并在声明对象的时候对其进行初始化。私
有成员的状态就成了仿函数的的初始状态。而由于声明一个仿函数对象可以拥有多个不同初始状态的实例,因此可以
借由仿函数产生多个功能类似却不同的仿函数实例
*/
class Tax
{
private:
float rate;
int base;
public:
Tax(float r,int b):rate(r),base(b){}
float operator()(float money){return (money-base)*rate;}
};
//int main()
//{
// Tax high(0.40,3000);
// Tax middle(0.25,2000);
// cout<<"tax over 3w "<<high(37500)<<endl;
// cout<<"tax over 2w "<<middle(27500)<<endl;
// //通过带状态的放函数,可以设定两种不同的税率计算
//}
class AirportPrice
{
private:
float _dutyfreerate;
public:
AirportPrice(float rate):_dutyfreerate(rate){}
float operator()(float price){ return price*(1-_dutyfreerate/100);}
};
//int main()
//{
//
// float tax_rate=5.5f;
// AirportPrice changi(tax_rate);
//
// auto changi2=[tax_rate](float price)->float{return price*(1-tax_rate/100);};
// float purchased=changi(3699);
// float purchased2=changi2(2899);
// //lambda函数捕获tax_rate,仿函数tax_rate初始化类。其他的,如在参数传递上,两者保持一致。
// //除去在语法层面上的不同,lambda和仿函数却有着相同的内涵->都可以捕获一些变量作为初始状态,并接受参数
// //进行运算
//}
/*
事实上,仿函数是一种编译器实现lambda函数的一种方式。在现阶段,通常编译器都会吧lambda函数转化为一个仿函数对象。
因而,c11中,lambda可以视为仿函数的一种等价方式
lambda函数在c++11标准中是内联的
*/
/*
lambda函数的一些问题
使用lambda函数是,捕获列表不同会得到不同的结果
对于按值方式传递的捕获列表,其传递的值在lambda函数定义的时候已经决定了
对于按原样传递的捕获列表变量,其传递的值则等于lambda函数调用时候的值
*/
//int main()
//{
// int j=12;
// auto by_value_lambda=[=]{return j+1;};
// auto by_ref_lambda=[&]{return j+1;};
//
// cout<<"by_value_lambda: "<<by_value_lambda()<<endl;
// cout<<"by_ref_lambda: "<<by_ref_lambda()<<endl;
//
// j++;
// cout<<"by_value_lambda: "<<by_value_lambda()<<endl; //13
// cout<<"by_ref_lambda: "<<by_ref_lambda()<<endl;
//}
/*
总结:
在使用lambda函数的时候,如果需要捕获的值成为lambda函数的常量,我们通常会按值传递的方式捕获。
需要捕获的值成为Lambda函数运行时的变量(类似于参数),则应该采用按引用的方式进行捕获。
lambda和函数指针的关系
大多数情况下,将匿名的Lambda赋值给了auto变量,这是lambda函数的一种声明和使用Lambda函数的方法
结合之前与仿函数的对比,lambda并非自定义类型或者是函数指针
从c++11的定义上发现,lambda类型被定义为闭包的类(c++11标准规定,closure类型被定义为特有的unique,匿名且非联合体的class类型·),而每个lambda表达式则会产生一个闭包类型的临时对象(右值)。
严格上讲,lambda函数并非函数指针。只不过c++11标准运行lambda表达式向函数指针的转换,
前提在于:
lambda没有捕获任何变量,且函数指针所指示的函数原型,必须跟lambda函数右值相同的调用方式。
*/
//int main()
//{
// int grils{3},boys{4};
// auto totalChild=[](int x,int y)->int{return x+y;};
// typedef int (*allChild)(int x,int y);
// typedef int (*oneChild)(int x);
//
// oneChild p;
p=totalChild; //编译失败,参数必须一致
//
// decltype(totalChild) allPeople=totalChild; //需提供decltype获得lambda的类型
decltype(totalChild) totalPeple=p; //编译失败 指针无法转换为lambda
//}
/*
将函数指针转化为lambda是不成功的
程序员可以通过decltype的方式获取Lambda函数的类型,一般用在实例化一些模板的时候使用
*/
/**
lambda的常量性和mutable关键字
*/
int main()
{
int value=0;
//编译失败,在const的lambda中修改常量
//auto const_val_lambda=[=](){value=3;};
//非const的lambda,可以修改常量数据
auto const_val_lambda=[=]()mutable{value=3;};
cout<<value<<endl;
//依旧是const的lambda,不过没有改动引用本身
auto const_ref_lambda=[&](){value=3;};
cout<<value<<endl;
//依旧是const的lambda,通过参数传递value
auto const_param=[&](int v){v=3;};
const_param(value);
}
/*
c11中·,默认情况下,Lambda函数是一个const函数,按照规则,一个const函数是不能在函数体中改变非静态成员变量的
。
但是在此处,编译器对于不同传参或者捕获列表的lambda函数执行了不同的规则,如何理解?
回顾之前lambda函数与仿函数基本的一致,或者就是原则,
我们需要将lambda函数转化为一个完整的仿函数,lambda函数的函数体部分,被转化仿函数以后将成为一个class的常成员函数
整个const_val_lambda看起来会像是下面的样子
class const_val_lambda
{
public:
const_value_lambda(int v):val(v){
}
public:
void operator()() const{ val=3;}; //常成员函数
private:
int val;
};
具体来讲,对于出来成员函数,不能在函数体内改变class中的任何成员变量。
有上面的实验之后,我们可以得出以下结论
Lambda的捕捉列表中的变量会成为等价仿函数的成员变量,而常量成员函数(如operator() 中改变其值的语义是不允许
的,因而按值捕获的变量在没有声明为mutable的lambda中,其值一旦被修改就会导致编译器报错
对于引用方式传递的变量在常量函数中的值被更改则不会导致错误。---》由于函数const_ref_lambda不会改变引用本身,
只会改变引用的值,因此编译器将编译通过。
现有的c+11标准中的lambda等价的是有常量operator()的仿函数。--》在使用不会列表的时候,按值捕获的变量是lambda
中不可改变的。
这个设计有其合理性,改变从上下文中拷贝的临时变量通常布局任何意义。
绝大多数时候,临时变量知识用于lambda函数的输入,如需输出到上下文,可以使用引用,或者让lambda函数放回的值来实现
)
*/