学习C++——lambda表达式

来源:互联网 发布:简明python教程 中文 编辑:程序博客网 时间:2024/06/05 19:04

C++ language

C++11 - the new ISO C++ standard

The C++ Standards Committee - ISOCPP
上面三个是C++学习网站,有助于了解C++11的新特性。

lambda表示式(C++11)

1、介绍lambda

我们使用过的仅有两种可调用对象是函数和函数指针。还有其他两种可调用对象:重载了函数调用运算符的类,以及lambda表达式。
一个lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。
一个lambda表达式的形式为:
[capture list] (parameter  list) -> return type {function body}
其中,capture list (捕获列表)是一个lambda所在函数中定义的局部变量的列表(通常为空);
return type,parameter list , function body与任何普通函数一样,分别表示返回类型,参数列表,函数体。但是,lambda必须使用尾置返回来指定返回类型。

尾置返回类型(C++ 11):

任何函数的定义都能使用尾置返回,但是这种形式对于返回类型比较复杂的函数最为有效,比如返回类型是数组的指针或者数组的引用。
尾置返回类型跟在形参列表后面并以一个->符号开头。为了表示函数真正的返回类型跟在形参列表之后,我们在本应该出现返回类型的地方放置了一个auto。

auto  func(int  i) -> int (*) [10];//func接受一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组。

我们可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体:
auto  f = [ ] {return 42};   定义了一个可调用对象f,它不接受参数,返回42;//此时的lambda没有参数列表,没有返回类型 ,只包括  [ ]  {return 42;}
cout << f() << endl;//这是lambda的调用方式,和普通函数的调用方式一样,输出42;

2、向lambda传递参数

调用一个lambda时给定的实参被用来初始化lambda的形参,与一个普通函数的调用是一样的。lambda不能有默认参数。

//作为一个带参数的lamdba的例子,我们可以编写一个与isShorter功能一样的lamdba[] (const string &a, const string &b){ return a.size() < b.size();}/*空捕获列表表明此lamdba不使用它所在函数中的任何局部变量。lamdba中的参数与isShorter类似,是const string 引用。*///使用stable_sortstable_sort(words.begin(), words.end(), [] (const string &a, const string &b){ return a.size() < b.size();});//当stable_sort需要比较两个元素时,就会调用这个lamdba。

3、使用捕获列表

一个lambda只有在其捕获列表中捕获一个它所在函数中的局部变量,才能在函数体中使用该变量。

[sz] (const string &a){ return a.size() >= sz; };//lamdba会捕获sz,函数体将string的大小和捕获到的sz进行比较。

备注:如果捕获列表中没有sz,则该lambda会出错。

  • 调用find_if,查找第一个长度大于等于sz的元素
auto wc = find_if(words.begin(), words.end(),[sz] (const string &a){ return a.size() >= sz; });/*这里对find_if的调用返回一个迭代器,指向第一个长度不小于给定参数sz的元素。如果这样的元素不存在,则返回words.end()的一个拷贝。*/

4、for_each算法

捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和它所在函数之外声明的名字。

//打印长度大于等于给定值的单词,每个单词后面接一个空格for_each(wc, words.end(),[] (const string &s) { cout << s << " ";});

5、完整的biggies(以及全部完整的源代码)

/*<span style="white-space:pre"></span>函数功能:将单词按从小到大的顺序排列,并删除重复的单词;<span style="white-space:pre"></span>  再按单词的长度由短到长排列。<span style="white-space:pre"></span>算法函数:stable_sort();sort();*/#include <iostream>#include <vector>#include <string>#include <algorithm>using namespace std;void elimDups(vector<string> &words);bool isShorter(const string &s1, const string &s2);void biggies(vector<string> &words,<span style="white-space:pre"></span>vector<string>::size_type sz);//寻找第一个大于等于给定长度的元素,一旦找到就可以计算出有多少元素的长度大于等于给定值。string make_plural(size_t ctr, const string &word, const string &ending);int main(){<span style="white-space:pre"></span><span style="white-space:pre"></span>vector<string> words;<span style="white-space:pre"></span>string val;<span style="white-space:pre"></span>cout << "Input the words: ";<span style="white-space:pre"></span>while(cin >> val)//用循环,输入字符串<span style="white-space:pre"></span>{<span style="white-space:pre"></span>words.push_back(val);<span style="white-space:pre"></span>}<span style="white-space:pre"></span>elimDups(words);//调用函数,排序,删除<span style="white-space:pre"></span>stable_sort(words.begin(), words.end(), isShorter);<span style="white-space:pre"></span>cout << "The output is: ";//输出排序删除之后的单词<span style="white-space:pre"></span>for(auto beg = words.begin(), end = words.end(); beg != end; ++beg)<span style="white-space:pre"></span>cout << *beg << " ";<span style="white-space:pre"></span>cout << endl;<span style="white-space:pre"></span>biggies(words,4);<span style="white-space:pre"></span><span style="white-space:pre"></span>while(1);<span style="white-space:pre"></span>return 0;}void elimDups(vector<string> &words){<span style="white-space:pre"></span>sort(words.begin(), words.end());//按字典序排序<span style="white-space:pre"></span>//uinque重排输入范围,使得每个单词只出现一次<span style="white-space:pre"></span>//排列在范围的前部,返回指向不重复区域之后一个位置的迭代器<span style="white-space:pre"></span>auto end_unique = unique(words.begin(), words.end());<span style="white-space:pre"></span>//使用向量操作erase删除重复单词<span style="white-space:pre"></span>words.erase(end_unique, words.end());}bool isShorter(const string &s1, const string &s2){<span style="white-space:pre"></span>return s1.size() < s2.size();}void biggies(vector<string> &words,<span style="white-space:pre"></span>vector<string>::size_type sz){<span style="white-space:pre"></span>elimDups(words);//将words排序,删除重复的单词<span style="white-space:pre"></span>//按长度排序,长度相同的单词维持字典序<span style="white-space:pre"></span>stable_sort(words.begin(), words.end(),<span style="white-space:pre"></span>[] (const string &a, const string &b)<span style="white-space:pre"></span>{ return a.size() < b.size();});<span style="white-space:pre"></span>//获取第一个迭代器,指向第一个满足size()>=sz的元素<span style="white-space:pre"></span>auto wc = find_if(words.begin(), words.end(),<span style="white-space:pre"></span>[sz] (const string &a)<span style="white-space:pre"></span>{ return a.size() >= sz; });<span style="white-space:pre"></span>//计算满足size>=sz的元素数数目<span style="white-space:pre"></span>auto count = words.end() - wc;//此时的words是按长度由小到大排列的。<span style="white-space:pre"></span>cout << count << " " << make_plural(count , "word", "s")<span style="white-space:pre"></span> << " of length " << sz << " or longer" << endl;<span style="white-space:pre"></span>//打印长度大于等于给定值的单词,每个单词后面接一个空格<span style="white-space:pre"></span>for_each(wc, words.end(),<span style="white-space:pre"></span>[] (const string &s) { cout << s << " ";});<span style="white-space:pre"></span>cout << endl;}string make_plural(size_t ctr, const string &word,<span style="white-space:pre"></span>   const string &ending){<span style="white-space:pre"></span>return (ctr>1) ? word+ending : word;}


  • 练习:编写一个lambda,接受两个int,返回它们的和
#include <iostream>int main(){using namespace std;int a,b;cout << "a = ";cin >> a;cout << "b = ";cin >> b;//auto f = [] (int a, int b) {return a+b;};//省略的返回类型int//cout << f(a,b) << endl;auto f = [] (int a, int b) ->int { return a+b;};cout << "a + b = " << f(a,b) << endl;cin.get();cin.get();return 0;}


  • 例子:编写一个lambda,捕获它所在函数的int,并接受一个int参数,lambda应该返回捕获的int和int参数的和。
#include <iostream>int main(){using namespace std;int a,val;cout << "a = ";cin >> a;cout << "val = ";cin >> val;auto f = [val] (int a) ->int { return a+val;};//接受一个参数,返回参数和捕获的值的和cout << "a + val = " << f(a) << endl;cin.get();cin.get();return 0;}



6、lambda捕获和返回

当定义一个lambda时,编译器生成一个与lambda对应的新的(未命名的)类类型。(暂时先不考虑这种类是如何生成的。)
可以这样理解,当向一个函数传递一个lambda时,同时定义了一个新类型和该类型的一个对象:传递的参数就是此编译器生成的类类型的未命名的对象。
类似地,当使用auto定义一个用lambda初始化的变量时,定义了一个从lambda生成的类型的对象。

默认情况下,从lambda生成的类都包含一个对应该lambda所捕获的变量的数据成员。类似任何普通类的数据成员,lambda的数据成员也在lambda对象创建时被初始化

  • 值捕获
与参数不同的是:被捕获的变量的值是在lambda创建时拷贝的,而不是调用时拷贝。
参数是在调用的时候才拷贝。采用值捕获的前提是变量可以拷贝。
[ val ] { return val; }
  • 引用捕获
采用引用的方式捕获变量。当以引用方式捕获一个变量时,必须保证在lambda执行时变量是存在的。
[ &val ] { return val; }

  • 隐式捕获
为了指示编译器推断捕获列表,应在捕获列表中写一个 & 或 = 。&告诉编译器采用捕获引用方式, = 则表示采用值捕获方式。
//sz为隐式捕获,值捕获方式wc = find_if(words.begin(), words.end(),[=] (const string &s){ return s.size() >= sz;});

void biggies(vector<string> &words, vector<string>::size_type sz,ostrem & os = cout ,char c = ' '){//os隐式捕获,引用捕获方式;c显示捕获,值捕获方式for_each(words.begin(), words.end(), [&, c] (const string & s){ os << s << c; });//os显示捕获,引用捕获方式;c为隐式捕获,值捕获方式for_each(words.begin(), words.end(), [=, &os] (const string & s) { os << s << c; });}
  • 可变lambda
如果我们希望能改变一个被捕获的变量的值,就必须在参数列表首加上关键字mutable。
void fcn3(){size_t v1 = 42;//f可以改变它所捕获的变量的值auto f = [v1] () mutable { return ++v1;};v1 = 0;auto j = f();//j = 43}

void fcn4(){size_t v1 = 42;//可以通过f2的引用来改变它auto f2 = [&v1] { return ++v1;};v1 = 0;auto j = f2();//j = 1;}//一个引用捕获的变量是否可以修改依赖于此引用指向的是一个const类型还是一个非const类型。

  • 指定lambda返回类型
使用标准库transform算法和一个lambda来将一个序列中的每个负数替换为其绝对值:
transform(vi.begin(), vi.end(), vi.begin(),[] (int i) { return i < 0 ? -i : i; });/*transform接受三个迭代器和一个可调用对象。前两个迭代器表示输入序列,第三个迭代器表示目的位置。算法对输入序列中的每个元素调用可调用对象(lamdba),并将结果写到目的位置;此时编译器可以推断出,lamdba的返回类型是 bool类型*/

当我们需要为一个lambda定义返回类型时,必须使用尾置返回类型
transform(vi.begin(), vi.end(), vi.begin(),[] (int i) -> int { if(i < 0) return -i; else return i;});/*如果没有尾置返回类型int,则编译器会推断这个lamdba返回类型为void,但是返回了一个int值,所以会出错。*/

总结:

  • 对于那种只在一两个地方使用的简单操作,lambda表达式是最有用的。如果我们需要在很多地方使用相同的操作,应该定义一个函数。
  • 如果一个操作需要很多语句才能完成,通常使用函数。
  • 如果lambda的捕获列表为空,通常可以用函数来代替它。

  • 对于捕获局部变量的lambda,用函数来替代就不行了。比如:find_if算法,接受一个一元谓词,因此可以是一个lambda,而不能是一个函数。

原博文地址:http://lib.csdn.net/article/39/46770?knId=1151

原创粉丝点击