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对象创建时被初始化。
- 值捕获
- 引用捕获
- 隐式捕获
//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
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(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,而不能是一个函数。