一般的,如果一个类定义了函数调用运算符,则我们可以像使用函数一样使用这个类,例如:一个类A定义了函数调用运算符,我们就可以使用A()这样的形式调用对象,实际上调用了类的调用运算符函数。如果一个类定义了调用运算符,则该类的对象可以被称为函数对象(可调用对象包含:函数,函数指针,重载了函数调用运算符的类和lambda表达式)。在lambda表达式中,编译器将改表达式翻译为一个未命名的类的未命名对象。lambda表达式产生的类中含有一个重载的函数调用运算符。默认情况下,lambda不能改变它捕获的变量(可以通过mutable关键字进行修改,后面会介绍)。因此,由lambda表达式产生的类中含有一个函数调用运算符的const成员函数(const成员函数有常量对象调用,一般不改变类的成员变量,例如get一个成员变量,应该在函数的参数列表后加上const关键字)。lambda表达式产生的类中不包含默认构造函数,赋值运算符和默认析构函数。是否含有默认的拷贝/移动构造函数则通常要视捕获的对象的类型而定。
lambda表达式来自于lambda演算,其定义如下:
λ演算_百度百科baike.baidu.com这里主要介绍下c++中的lambda表达式。
lambda表达式又被称为匿名函数,当我们在程序的某一处使用一个简单的函数,并且只在此处或者此作用域中使用一次或者几次,这时可以考虑使用lambda表达式。其基本语法如下:
lambda表达式的返回类型必须是尾置的。其中,参数列表和返回类型(返回类型忽略时,lambda可以根据其函数体中返回类型进行推断)可以忽略。lambda表达式不能有默认参数,因此形参和实参的数量必须一致且一 一对应。lambda表达式内部是无法使用其所在函数中的(非static)变量的,但有时必须使用,就必须使用捕获列表来捕获其所在作用域中的参数(注意:只能捕获lambda所在作用域中的参数,函数外的变量可以直接使用。例如:我们在lambda表达式函数体中使用的cout,可以直接使用)。根据捕获参数的方式,可以分为:值捕获,引用捕获,隐式捕获和表达式捕获。
捕获列表的形式如下:
1 值捕获
通过值捕获的变量会被拷贝到lambda产生的类对象中,因此这种捕获方式必须为每个值捕获的变量建立对应的数据成员。同时创建构造函数,令其捕获的变量初始化数据成员。值捕获的对象必须是可拷贝的。但其捕获的值是在lambda表达式对象创建时拷贝,而不是运行时拷贝:
//按值捕获int t=10;auto ff=[t]() mutable { //注意:这里是lambda函数的声明return ++t; //这里生成了一个_t的变量};auto ff2=[t]() mutable { //注意:这里是lambda函数的声明return ++t; //这里生成了一个_t的变量};cout<<ff()<<endl; //这里调用lambda表达式cout<<ff2()<<endl;cout<<ff()<<endl;cout<<ff2()<<endl;cout<<t<<endl;/*运行结果
11
11
12
12
10
*/
上述ff和ff2其本质是编译器生成的未命名类的未命名对象。由于是值捕获,因此++t只在lambda函数作用域有效,捕获t后实际在内部生成了一个_t的成员变量。lambda表达式内部对t的操作实际上是对_t这个变量的操作,并不影响其外部的t的值。
默认我们捕获到lambda表达式中的值是const的,是不能修改的,mutable关键字可允许我们对捕获到的值进行修改。因此,在上述代码中,++t的时候必须加上mutable。
上述lambda表达式等价于:
class cfun{
public:fun(t):_t(t){};bool operator(){ //由于这里使用了mutable关键字,因此是这样的_t++;}/*当没有mutable关键字时,函数调用运算符重载如下形式:bool operator() const{/* 对_t的一些操作 */}*/private:int _t;
};
可变lambda
这里 再简单介绍下mutable的使用。默认情况下,lambda不会改变一个值被拷贝的变量。如果希望改变被捕获的变量,就必须使用mutable。因此,可变lambda可以省略参数列表。
void fun(){size_t v1=42;auto f=[v1] () mutable { return ++v1; };v1=0;auto j=f(); //j=43
}
一个引用捕获的变量是否可以修改依赖于引用指向一个const对象还是非const对象。
void fun(){size_t v1=42; //局部变量//v1是一个非const的变量引用,可以使用f来改变其值。auto f=[&v1] { return ++v1; };v1=0;auto j=f(); //j=1
}
2 引用捕获
当lambda用引用捕获变量时,由程序负责确保lambda执行时引用所引的对象确实存在。因此,编译器可以直接使用该引用而无需在lambda产生的类中将其存储为数据成员。
//引用捕获
int t=10;
auto ff=[&t]() {cout<<t<<endl;t=13;
};
t=11;
ff();
cout<<t<<endl;//输出结果
/*
11
13
*/
可以看到,引用捕获的方式跟一般的引用形参函数类似,可以改变传入的参数值。
引用捕获是必要的,例如:
for_each(words.begin(),words.end(), [&os, c] (const string & s) { os<<s<<c; });
因为ostream对象os是不能拷贝的。
当然,我们也可以从一个函数中返回一个lambda,但是lambda在返回后,函数中的变量会被销毁,因此,lambda的捕获列表中不能包含函数中变量的引用。
3 隐式捕获
以上两种方式都是显式捕获方式,这里介绍下隐式捕获。编译器可以根据lambda函数体中的代码推断我们会使用那些变量。其中&告诉编译器引用捕获方式,=采用值捕获方式。
int a=1,b=2;
[a,b](){cout<<a<<"+"<<b<<endl;
}();//引用捕获
[&](){cout<<a<<"+"<<b<<endl;
}();//值捕获
[=](){cout<<a<<"+"<<b<<endl;
}();//混合使用
int c=3,d=4;
[=,&c,&d](){cout<<a<<"+"<<b<<endl;cout<<c++<<"+"<<++d<<endl;
}();
当我们使用混合方式时,捕获列表的第一个元素必须是&或者=。此符号指定了默认捕获方式。显式捕获必须使用与隐式捕获不同的方式。即,当默认捕获方式是值捕获时,显式捕获方式命名变量必须采用引用捕获方式。
4 指定lambda返回类型
如果lambda函数体中包含了除return的任何语句,编译器默认此lambda返回void。例如:
void fun(){auto ff4=[](int i){if(i<0)return -i;elsereturn i;};cout<<ff4(-100)<<endl;
}
此时会出现编译错误,正确的写法如下:
void fun(){auto ff4=[](int i)->int{if(i<0)return -i;elsereturn i;};cout<<ff4(-100)<<endl;
}
以下是一个lambda的使用例子:
void fun(){vector<int> vec={1,2,3,4,5};for(int i=0;i<vec.size();i++){if(vec[i]%2==0)cout<<vec[i]<<"是偶数"<<endl;elsecout<<vec[i]<<"是奇数"<<endl;}//高阶写法for_each(vec.begin(),vec.end(),[](int n){if(n%2==0)cout<<n<<"是偶数"<<endl;elsecout<<n<<"是奇数"<<endl;});
}