学习C++——lambda表达式
2015-02-04 14:22
417 查看
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_sort stable_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,而不能是一个函数。
相关文章推荐
- C++ 11学习(1):lambda表达式
- c++ 11学习笔记--Lambda 表达式(对比测试Lambda ,bind,Function Object)
- 【C++学习】函数对象和Lambda表达式
- C++ 11学习(1):lambda表达式
- [C++ 11,UWP]C++异步编程与lambda 表达式 学习UWP编程参考
- 结合C++11新特性来学习C++中lambda表达式的用法
- C++ 学习笔记(10)泛型算法、lambda表达式、bind函数、迭代器
- C++ 11 标准 新增的Lambda表达式、for_each语法,改变了auto关键字的意义
- 表达式左值右值(C++学习)
- 表达式左值右值(C++学习)
- 【菜鸟C++学习笔记】7.运算符与表达式
- Lambda表达式学习
- 2012/1/13 《C++ Primer Plus》第五章:循环和表达式 学习笔记
- C++ 和 C# 二者 Lambda 表达式之异同
- concurrency runtime学习笔记之一:Lambda表达式
- [研究笔记]Lambda表达式学习笔记
- C++学习之new 与 delete表达式
- C++ 0x新特性:详细讲解lambda表达式
- 2012/1/13 《C++ Primer Plus》第五章:循环和表达式 学习笔记
- 表达式模板 (C++学习)