c++ Lambda表达式

来源:互联网 发布:java调用ssh协议 编辑:程序博客网 时间:2024/05/16 13:42
ISO C++ 11 标准的一大亮点是引入Lambda表达式。

基本语法如下:

[捕获列表](形参列表) mutable ->返回值类型 复合语句
其中除了“[]”(其中捕获列表可以为空)和“复合语句”(相当于具名函数定义的函数体),其它都是可选的。它的类型是唯一的具有成员operator()的非联合的类类型,称为闭包类型(closure type)。

使用对比

例如调用<algorithm>中的std::sort,ISO C++ 98 的写法是要先写一个compare函数:
1
2
3
4
boolcompare(int&a,int&b)
{
returna>b;//降序排序
}
然后,再这样调用:
1
sort(a,a+n,compare);
然而,用ISO C++ 11 标准新增的Lambda表达式,可以这么写:
1
sort(a,a+n,[](int a,int b){return a>b;});//降序排序
这样一来,代码明显简洁多了。
由于Lambda的类型是唯一的,不能通过类型名来显式声明对应的对象,但可以利用auto关键字和类型推导:
1
auto f=[](int a,int b){return a>b;});
和其它语言的一个较明显的区别是Lambda和C++的类型系统结合使用,如:
1
2
3
4
auto f=[=](int a,int b){return a>x;});//x被捕获复制
int x=0,y=1;
auto g=[&](int x){return++y;});//y被捕获引用,调用g后会修改y,需要注意y的生存期
bool(*fp)(int,int)=[](int a,int b){return a>b;});//不捕获时才可转换为函数指针
Lambda表达式可以嵌套使用。
即将出版的ISO C++14支持基于类型推断的泛型lambda表达式。上面的排序代码可以这样写:
1
sort(a,a+n,[](constauto&a,constauto&b){returna>b;});//降序排序:不依赖a和b的具体类型
因为参数类型和函数模板参数一样可以被推导而无需和具体参数类型耦合,有利于重构代码;和使用auto声明变量的作用类似,它也允许避免书写过于复杂的参数类型。特别地,不需要显式指出参数类型使使用高阶函数变得更加容易。

lambda表达式

C++ 语言中的lambda表达式在很多情况下提供了函数对象的另一种实现机制。Lambda表达式并不是STL所特有的,但它广泛应用于这一环境中。Lambda是表达式是定义一个没有名称、也不需要显示类定义的函数对象。Lambda表达式一般作为一种手段,用来将函数作为实参传递到另一个函数。相比于定义和创建一个常规的函数对象而言,lambda表达式非常容易使用和理解,而且需要的代码也较少。当然,一般而言,lambda表达式并不会取代函数对象。

举个例子,假设有个包含数值的矢量,我们计算此矢量的立方值。可以用transform()函数操作,简单的用lambda表达式完成。

double  values[] = {1,2,3,4,5,6};vector<double> data(values,values+6);vector<double> cubes(data.size());transform(values.begin(),values.end(),cubes.begin(),[](double x){ return x*x*x;});

最后这条语句用来计算data中的立方值,并存储在cubes。这里简单提一下transform()函数。它是algorithm头文件中的函数,它有两个版本。

第一个版本是将一个一元函数对象指定的操作应用到由一对迭代器指定的一个元素集合上,格式如下:

transform(InputIterator begin, InputIterator end, OutputIterator result,UnaryFuncton f);

transform()的这个版本将一元函数f应用到迭代器begin 和end指定的范围中的所有元素,并从迭代器result指定的位置开始存储结果。Result迭代器可以与begin迭代器相同,只是在这种情况下将会替换原有的内容。这个函数返回一个迭代器,指向存储的最后一个结果的下一个位置。

举例如下:

double  values[] = {1,2,3,4,5,6};vector<double> data(values,values+6);transform(values.begin(),values.end(),values.begin(),negate<double>);

transform()函数调用negate<double>函数对象应用到矢量data中的所有元素,结果存储在data中,并重写了原始值,执行完后data将包含 -1,-2,-3,-4,-5,-6。函数返回迭代器data.end()。

transform()第二个版本通过来自迭代器指定的两个范围内的操作数应用一个二元函数。格式为:

transform(InputIterator1 begin1, InputIterator 1end1, InputIterator2 begin2, OutputIterator result,BinaryFunction f);

由begin1和end1指定的范围表示最后一个实参指定的二元函数f的左操作数集合。表示右操作的范围从begin2迭代器指定的位置开始,这个范围不需要提供end迭代器,因为这个范围的元素数量必须与begin1和end1指定的范围元素个数相同,结果从result迭代器位置开始存储在这个范围内。如果希望存回原范围中,result迭代器可以与begin1相同。

举例如下:

复制代码
double values[]={2.5,-3.5,4.5,-5.5,6.5,-7.5};vector<double> data(values, values + sizeof values / sizeof values[0]);vector<double> squares(data.size());transform(data.begin(),data.end(),data.begin(),squares.begin(),multiplies<double>());ostream_iterator<double> out(cout,” “);copy(squares.begin(),squares.end(),out);
复制代码

Transform()函数通过multiplies<double>函数对象将自身相乘,结果存储到squares中。最后两句用一个输出迭代器输出内容。

现在回到上文:

transform(values.begin(),values.end(),cubes.begin(),[](double x){ return x*x*x;});

开始的方括号称为lambda引导,它标志着lambda表达式的开始。后面的圆括号中的是lambda的参数列表,这与普通函数相同。此例中只有一个形参x。注意,lambda的参数列表不允许指定形参的默认值,并且参数列表的长度是不可变的。大括号中的是lambda的主体,此例只有一条return语句,当然可以包含多条语句。大家可能注意到这里没有返回类型说明。当lambda表达式的主体是一条单一返回语句,而该语句在lambda表达式主体中返回一个值时,返回类型默认为返回值的类型。否则,返回void。当然可以指定返回类型,如下:

[](double x) ->double{ return x*x*x;} //指定返回double
Capture子句

lambda表达式引导可以包含一个捕获子句,用来确定lambda主体如何访问封闭作用域中的变量。前面lambda表达式方括号之间没有内容,表面封闭作用域没有可以再lambda表达式中访问的变量。若要访问,第一种是方括号之间是 = ,则lambda主体可以按值访问封闭作用域的所有自动变量,但不会修改原始变量。另一中是方括号之间是 & ,则封闭作用域的所有自动变量按应用访问,因此lambda表达式可以修改变量值。例如:

复制代码
double index = 3.0;double  values[] = {1,2,3,4,5,6};vector<double> data(values,values+6);vector<double> cubes(data.size());transform(values.begin(),values.end(),cubes.begin(),[=](double x){ return index*x*x*x;});
复制代码

需要主要的是,这与按值传递实参根本不同,变量index的值可用在lambda中,但不能更新index的副本。如:

transform(values.begin(),values.end(),cubes.begin(),[=](double x) ->double{ index += 10; // error return index*x*x*x;});

以上是错误的,若要修改变量的临时副本,则通过添加mutable关键字实现。如:

transform(values.begin(),values.end(),cubes.begin(),[=](double x)mutable ->double{ index += 10; // okreturn index*x*x*x;});

现在可以修改作用域中的任意变量副本,而不会修改原始值。

transform(values.begin(),values.end(),cubes.begin(),[&](double x)mutable ->double{ index += 10; // change original valuereturn index*x*x*x;});

现在采用按引用使用,则会改变index的原始值。

若要捕获一个特定的变量,则:

transform(values.begin(),values.end(),cubes.begin(),[&index](double x)mutable ->double{ index += 10; // change original valuereturn index*x*x*x;});

这样,只捕获index,如要捕获多个变量,中间用逗号隔开即可。

Lambda也可以包含throw()异常说明,如:

transform(values.begin(),values.end(),cubes.begin(),[&index](double x)mutable throw()->double{ index += 10; // change original valuereturn index*x*x*x;});

如果想要包含mutable说明和throw()说明,则中间必须用一个或多个空格隔开。

现在综合看个实例,用以前说过的函数模板实现。
复制代码
// Using lambda expressions#include <algorithm>#include <iostream>#include <iomanip>#include <vector>#include <ctime>#include <cstdlib>using namespace std; // Just to avoid a lot of using directives in the example...// Template function to return the average of the elements in a vectortemplate <class T> T average(const vector<T>& vec){    T sum(0);    for_each(vec.begin(), vec.end(),        [&sum](const T& value){ sum += value; });    return sum/vec.size();}// Template function to set a vector to values beginning with start and incremented by incrementtemplate <class T> void setValues(vector<T>& vec, T start, T increment){    T current(start);    generate(vec.begin(), vec.end(),         [increment, &current]()->T{T result(current);    current += increment;    return result;});}// Template function to set a vector to random values between min and maxtemplate<class T> void randomValues(vector<T>& vec, T min, T max){    srand(static_cast<unsigned int>(time(0)));   // Initialize random number generator    generate(vec.begin(), vec.end(),        [=](){ return static_cast<T>(static_cast<double>(rand())/RAND_MAX*(max-min)+min); });}// Template function to list the values in a vectortemplate<class T> void listVector(const vector<T>& vec){    int count = 0;      // Used to control outputs per line    for_each(vec.begin(), vec.end(),        [&count](const T& n)->void{  cout << setw(10) << n;    if(++count % 5)        cout << "  ";    else        cout << endl;});}int main(){    vector<int> integerData(50);    randomValues(integerData, 10, 100);    // Set random integer values    cout << "Vector contains:" << endl;    listVector(integerData);    cout << "Average value is "<< average(integerData) << endl;    vector<double> realData(20);    setValues(realData, 5.0, 2.5);   // Set real values starting at 5.0 ,increment by 2.5    cout << "Vector contains:" << endl;    listVector(realData);    cout << "Average value is "<< average(realData) << endl;    return 0;}

复制代码

 

Lambda表达式的包装

使用function< >模板

Lambda表达式的包装实际上是使用function< >模板赋予lambda表达式一个名字,这不仅提供了在Lambda表达式内递归的可能,而且可以再多条语句使用同样的lambda表达式。如:

Function< int (double)> f = [](double x){ return static_cast<int>(x*x)};

这里具有一个double类型的形参,并返回一个为int类型的值,当然,此处只是举例而已,因为该语句存在问题,将double赋给int时可能会丢失数据。

举例:

复制代码
#include <iostream>#include <functional>using std::function;using std::cout;using std::endl;                        int main(){  // Wrap the lambda expression to compute the HCF  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 << "For numbers " << a << " and " << b << " the HCF is " << hcf(a, b) << endl;  a = 103*53*17*97;  b = 3*29*103;  cout << "For numbers " << a << " and " << b << " the HCF is " << hcf(a, b) << endl;   return 0;}
该实例用欧几里得法求两个数的最大公约数,即所谓辗转相除法,采用递归形式实现。
使用auto关键字
#include <iostream>using namespace std;int main() {auto f = [](int x)->int {return x*x;};int t = f(2);cout << t << endl;return 0;}

0 0