C++新特性~摘录lambda

来源:互联网 发布:济南seo推广公司 编辑:程序博客网 时间:2024/06/05 19:08

Lambda函数与表示式[编辑]

C++11标准提供了匿名函数的支持,在《ISO/IEC 14882:2011》(C++11标准文档)中叫做lambda表达式[10]。一个lambda表达式有如下的形式:

[capture] (parameters) mutable exception attribute -> return_type { body }

必须用方括号括起来的capture列表来开始一个lambda表达式的定义。

lambda函数的形参表比普通函数的形参表多了3条限制:

  1. 参数不能有缺省值
  2. 不能有可变长参数列表
  3. 不能有无名参数

如果lambda函数没有形参且没有mutable、exception或attribute声明,那么参数的空圆括号可以省略。但如果需要给出mutable、exception或attribute声明,那么参数即使为空,圆括号也不能省略。

如果函数体只有一个return语句,或者返回值类型为void,那么返回值类型声明可以被省略:

[capture](parameters){body}

一个lambda函数的例子如下:

[](int x, int y) { return x + y; } // 從return語句中隱式獲得的返回值類型[](int& x) { ++x; }   // 沒有return語句 -> lambda函數的返回值為void[]() { ++global_x; }  // 沒有參數,僅僅是訪問一個全局變量[]{ ++global_x; }     // 與前者相同,()可以被省略

这个无名函数的返回值是decltype(x+y) (在上面的第一个例子中)。如果lambda函数体的形式是return expression,或者什么也每返回,或者所有返回语句用decltype都能检测到同一类型,那么返回值类型可以被省略。

在标准 C++,特别是当使用 C++ 标准程序库算法函数诸如 sort 和 find,使用者经常希望能够在算法函数调用的附近定义一个临时的述部函数(又称谓词函数,predicate function)。由于语言本身允许在函数内部定义类,可以考虑使用函数对象,然而这通常既麻烦又冗赘,也阻碍了代码的流程。此外,标准 C++ 不允许定义于函数内部的类被用于模板,所以前述的作法是不可行的。

C++11 对 lambda 的支持可以解决上述问题。

一个 lambda 函数可以用如下的方式定义:

[](int x, int y) { return x + y; }

这个不具名函数的回返类型是 decltype(x+y)。只有在 lambda 函数符合"return expression"的形式下,它的回返类型才能被忽略。在前述的情况下,lambda 函数仅能为一个述句。

在一个更为复杂的例子中,回返类型可以被明确的指定如下(返回值类型可以显式指定):

[](int x, int y) -> int { int z = x + y; return z + x; }

本例中,一个临时的参数 z 被创建用来存储中间结果。如同一般的函数,z 的值不会保留到下一次该不具名函数再次被调用时(中间值在调用的前后并不存在)。

如果 lambda 函数没有传回值(例如 void ),其回返类型可被完全忽略(无需显式指定返回值,没有必要写-> void代码。)。

定义在与 lambda 函数相同作用域的参数引用也可以被使用。这种的参数集合一般被称作 closure (闭包)。

lambda函数可以捕获lambda函数外的具有automatic storage duration的变量。函数体与这些变量的集合合起来称做闭包。这些外部变量在声明lambda表达式时列在在方括号[]中。空的方括号表示没有外界变量被capture。这些变量被传值捕获或者引用捕获。对于传值捕获的变量,默认为只读。修改这些变量将导致编译报错。但在lambda表达式的参数表的圆括号后面使用mutable关键字,就允许lambda函数体内的语句修改传值引用的变量,这些修改与lambda表达式(实际上是用函数对象实现)有相同的生命期,但不影响被传值捕获的外部变量的值。lambda函数可以直接使用具有static存储期的变量。如果在lambda函数的捕获列表中给出了static存储期的变量,编译时会给出警告,仍然按照lambda函数直接使用这些外部变量来处理。因此具有static存储期的变量即使被声明为传值捕获,修改该变量实际上直接修改了这些外部变量。编译器生成lambda函数对应的函数对象时,不会用函数对象的数据成员来保持被“捕获”的static存储期的变量。示例:

[]  // 沒有定義任何變數。使用未定義變數會導致錯誤。[x, &y] // x 以傳值方式傳入(預設),y 以傳參考方式傳入。[&]   // 任何被使用到的外部變數皆隱式地以參考方式加以引用。[=]   // 任何被使用到的外部變數皆隱式地以傳值方式加以引用。[&, x]   // x 顯示地以傳值方式加以引用。其餘變數以參考方式加以引用。[=, &z]   // z 顯示地以參考方式加以引用。其餘變數以傳值方式加以引用。

closure 被定义与使用如下:

std::vector<int> someList;int total = 0;std::for_each(someList.begin(), someList.end(), [&total](int x) {  total += x;});std::cout << total;

上例可计算 someList 元素的总和并将其印出。 参数 total 是 lambda 函数 closure 的一部分,同时它以引用方式被传递入谓词函数, 因此它的值可被 lambda 函数改变。

若不使用引用的符号&,则代表参数以传值的方式传入 lambda 函数。 让使用者可以用这种表示法明确区分参数传递的方法:传值,或是传引用。 由于 lambda 函数可以不在被声明的地方就地使用(如置入 std::function 对象中); 这种情况下,若参数是以传引用的方式链接到 closure 中,是无意义甚至是危险的行为。

若 lambda 函数只在定义的作用域使用, 则可以用 [&] 声明 lambda 函数, 代表所有引用到 stack 中的参数,都是以引用的方式传入, 不必一一显式指明:

std::vector<int> someList;int total = 0;std::for_each(someList.begin(), someList.end(), [&](int x) {  total += x;});

参数传入 lambda 函数的方式可能随实现有所变化,一般期望的方法是 lambda 函数能保留其作用域函数的 stack 指针,借此访问区域参数。

若使用 [=] 而非 [&],则代表所有的引用的参数都是传值使用。

对于不同的参数,传值或传引用可以混和使用。 比方说,使用者可以让所有的参数都以传引用的方式使用,但带有一个传值使用的参数:

int total = 0;int value = 5;[&, value](int x) { total += (x * value); };

total 是传引用的方式传入 lambda 函数,而 value 则是传值。

在类的非静态成员函数中定义的lambda表达式可以显式或隐式捕捉this指针,从而可以引用所在类对象的数据成员与函数成员。lambda函数的函数体中,可以访问下述变量:

  • 函数参数
  • 局部声明的变量
  • 类数据成员(当函数声明在类中)
  • 具有静态存储期的变量(如全局变量)
  • 被捕获的外部变量
    • 显式捕获的变量
    • 隐式捕获的变量,使用默认捕获模式(传值或引用)来访问。

lambda函数的数据类型是函数对象,保存时必须用std::function模板类型或auto关键字,或用auto关键字。例如:

#include <functional>#include <vector>#include <iostream> double eval(std::function <double(double)> f, double x = 2.0){return f(x);} int main(){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(int i=0;i<fv.size();i++)std::cout << fv[i](2.0) << std::endl;for(int i=0;i<3;i++)std::cout << fa[i](2.0) << std::endl;for(auto &f : fv)std::cout << f(2.0) << std::endl;for(auto &f : fa)std::cout << f(2.0) << std::endl;std::cout << eval(f0) << std::endl;std::cout << eval(f1) << std::endl;std::cout << eval([](double x){return x*x;}) << std::endl;return 0;}

一个lambda函数的捕捉表达式为空,则可以用普通函数指针存储或调用。例如:

auto a_lambda_func = [](int x) { /*...*/ };void (* func_ptr)(int) = a_lambda_func;func_ptr(4); //calls the lambda.

若一个 lambda 函数被定义于某类的成员函数中,则可以使用该类对象的引用,并且能够访问其内部的成员。

[](SomeType *typePtr) { typePtr->SomePrivateMemberFunction(); };

这只有当该 lambda 函数创建的作用域是在 SomeType 的成员函数内部时才能运作。

在成员函数中指涉对象的 this 指针,必须要显式的传入 lambda 函数, 否则成员函数中的 lambda 函数无法使用任何该对象的参数或函数。

[this]() { this->SomePrivateMemberFunction(); };

若是 lambda 函数使用 [&] 或是 [=] 的形式,this在 lambda 函数即为可见。

lambda 函数是编译器从属类型的函数对象; 这种类型名称只有编译器自己能够使用。如果使用者希望将 lambda 函数作为参数传入,该类型必须是模版类型,或是必须创建一个std::function 去获取 lambda 的值。使用 auto 关键字让我们能够存储 lambda 函数:

auto myLambdaFunc = [this]() { this->SomePrivateMemberFunction(); };auto myOnheapLambdaFunc = new auto([=] { /*...*/ });

0 0
原创粉丝点击