您的位置:首页 > 编程语言 > C语言/C++

学习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,而不能是一个函数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: