Lambda我们可以将其理解为一个未命名的内联函数。
与任何函数类似,一个lambda具有一个返回类型,一个参数列表和一个函数体。
但与函数不同,lambda可能定义在函数内部。
一个lambda表达式具有如下形式:
[capture list] (parameter list) ->return type {function body}
capture list: 捕获列表,是一个lambda所在函数中定义的局部变量列表(通常为空)
parameter list:参数列表
return type:返回类型
function body:函数体
但是与普通函数不同,lambda必须使用尾置返回来指定返回类型
我们可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体
auto f=[]{return 42;};//分号不能丢
此例中,我们定义了一个可调用对象f,它不接受参数,返回42.
lambda的调用方式与普通函数调用方式相同,都是使用调用运算符:
cout<<f()<<endl;//打印42
在lambda中忽略括号和参数列表等价于指定一个空参数列表。在此例中,当调用f时,参数列表是空的。如果忽略返回类型,lambda根据函数体中的代码推断出返回类型。
如果函数体只是一个return语句,则返回类型从返回的表达式的类型推断而来,否则,返会类型为void.
如果lambda的函数体包含任何一个单一的return语句之外的内容,且未指定返回类型,则返回void
与一个普通函数调用类似,调用一个lambda时给定的实参被用来初始化lambda的形参。通常,实参和形参的类型必须匹配。但与普通函数不同,lambda不能有默认参数。
因此,一个lambda调用的实参数目永远与形参数目相等。
下面举一个带参数的lambda的例子:
[](const string &a,const string& b) {return a.size()<b.size();};//这里分号不能丢
空捕获列表表面此lambda不使用它所在函数中的任何局部变量。
虽然一个lambda可以出现在一个函数中,使用其局部变量,但它只能使用那些明确指明的变量。一个lambda通过将局部变量包含在其捕获列表中来指明将会使用这些变量。捕获列表指引lambda在其内部包含访问局部变量所需的信息。
下面举一个例子:
#include<iostream> using namespace std; void test() { string sz("abc"); string words("abds"); auto ret=[sz](const string& a) { return a.size() >= sz.size(); }; cout << ret("efgdasd") << endl; } int main() { test(); system("pause"); return 0; }
与find_if结合使用
举例:调用find_if算法在字符串s中查找第一个长度大于等于字符串sz的元素
#include <iostream> #include <vector> #include <algorithm> using namespace std; //用lambda作为参数 void bigger(vector<string>& words,vector<string>::size_type sz) { vector<string>::iterator pos = find_if(words.begin(), words.end(), [sz](const string& a) {return a.size() > sz; }); cout << *pos << endl; } int main() { vector<string> svec{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" }; bigger(svec, 4); }
//用lambda作为参数 void bigger(vector<string>& words,vector<string>::size_type sz) { vector<string>::iterator pos = find_if(words.begin(), words.end(), [sz](const string& a) {return a.size() > sz; }); //这里两个迭代器做减法,类似指针做减法,得到两个迭代器之间的距离 //与指针不同,我们无法直接打印迭代器,例如cout<<pos<<endl; auto count = words.end() - pos; cout << count<< endl; }
//用lambda作为参数 void bigger(vector<string>& words,vector<string>::size_type sz) { vector<string>::iterator pos = find_if(words.begin(), words.end(), [sz](const string& a) {return a.size() > sz; }); for_each(pos, words.end(), [](const string& a) {cout << a << " "; }); } int main() { vector<string> svec{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" }; bigger(svec, 4); }
#include <iostream> #include <vector> #include <algorithm> using namespace std; //删除重复元素 void elimDups(vector<string>& words) { //再删除重复元素之前,需要先进行排序 sort(words.begin(), words.end()); //把重复元素移到尾部 auto new_end = unique(words.begin(), words.end()); //将尾部重复元素删除 words.erase(new_end, words.end()); } //用lambda作为参数 void bigger(vector<string>& words,vector<string>::size_type sz) { //将words按字典序重排,并消除重复单词 elimDups(words); //按长度重新排序,长度相同的单词维持字典序 stable_sort(words.begin(), words.end(), [](const string& a1, const string& a2) {return a1.size() > a2.size(); }); //获取一个迭代器,指向第一个满足size>=sz的元素 vector<string>::iterator pos = find_if(words.begin(), words.end(), [sz](const string& a) {return a.size() > sz; }); //计算满足size>=sz的元素的数目 auto count = count_if(words.begin(), words.end(), [sz](const string& a) {return a.size()>sz; }); cout << count << endl; //打印长度大于等于给定值的单词,每个单词后面接一个空格 for_each(pos, words.end(), [](const string& a) {cout << a << " "; }); } int main() { vector<string> svec{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" }; bigger(svec, 4); }
与传值参数类似,采用值捕获的前提是变量可以拷贝。
与参数不同,被捕获的变量的值实在lambda创建时拷贝,而不是调用时拷贝
举例:
#include <iostream> using namespace std; void test() { size_t v1 = 42;//局部变量 //将v1拷贝到名为f的可调用对象 auto f = [v1] {return v1; }; v1 = 0; auto j = f(); cout << j << endl;//j为42,f保存了我们创建它时的拷贝 } int main() { test(); return 0; }
举例:
#include <iostream> using namespace std; void test() { size_t v1 = 42;//局部变量 //f对象包含v1的引用 auto f = [&v1] {return v1; }; v1 = 0; auto j = f(); cout << j << endl;//j为0,f保存了v1的引用,而非拷贝 } int main() { test(); return 0; }
#include <iostream> #include <vector> #include <algorithm> using namespace std; void bigger(vector<string>& words ,ostream &os=cout,char ch=' ') { for_each(words.begin(), words.end(), [&os, ch](const string& s) {os << s << ch; }); } int main() { vector<string> svec{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" }; bigger(svec); }
捕获一个普通变量,如int,string或其他非指针类型,通常采用简单的值捕获方式。
如果我们捕获一个指针或迭代器,或采用引用捕获方式,就必须保证对象具有预期的值。
在lambda从创建到它执行这段时间内,可能有代码改变绑定对象的值。
也就是说,在该指针(或引用)被捕获的时刻,绑定的对象的值是我们所期望的,但在lambda执行时,该对象的值已经完全不同了。
一般来说,我们应该尽量减少捕获的数据量,来避免潜在的捕获导致的问题。而且,如果有可能的话,应该避免捕获指针或引用。
通过在捕获列表中写一个&或=,指示编译器推断捕获列表。
&告诉编译器采用引用方式,=则表示采用值捕获方式
例如:
#include <iostream> #include <vector> #include <algorithm> using namespace std; void bigger(vector<string>& words,vector<string>::size_type sz) { //sz为隐式捕获,值捕获方式 auto wc = find_if(words.begin(), words.end(), [=](const string& s) {return s.size() >= sz; }); cout << *wc << endl; } int main() { vector<string> svec{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" }; bigger(svec, 4); }
#include <iostream> #include <vector> #include <algorithm> using namespace std; void bigger(vector<string>& words,vector<string>::size_type sz,ostream& os=cout,char c=' ') { //os为隐式捕获,引用捕获方式 //c显示捕获,值捕获方式 for_each(words.begin(), words.end(), [&, c](const string& s) {os << s << c; }); cout << endl; //os显示捕获,引用捕获方式 //c隐式捕获,值捕获方式 for_each(words.begin(), words.end(), [=,&os](const string& s) {os << s << c; }); } int main() { vector<string> svec{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" }; bigger(svec, 4); }
1、空。没有使用任何函数对象参数。
2、=。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
3、&。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
4、this。函数体内可以使用Lambda所在类中的成员变量。
5、a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
6、&a。将a按引用进行传递。
7、a, &b。将a按值进行传递,b按引用进行传递。
8、=,&a, &b。除a和b按引用进行传递外,其他参数都按值进行传递。
9、&, a, b。除a和b按值进行传递外,其他参数都按引用进行传递。
默认情况下,对于一个值被拷贝的变量,lambda不会改变其值,如果我们希望能改变一个被捕获的变量的值,就必须在参数列表首加上关键字mutable。因此,可变lambda能省略参数列表:
#include <iostream> using namespace std; void test() { size_t val = 42;//局部变量 //f可以改变它所捕获的变量的值 auto f = [val]()mutable {return val++; }; val = 0; cout << f() << endl; cout << f() << endl; cout << f() << endl; cout << val << endl; } int main() { test(); return 0; }
#include <iostream> using namespace std; void test() { size_t val = 42;//局部变量 //val是一个非const变量的引用 //可以通过f中的引用来改变它 auto f = [&val]() {return val++; }; val = 0; cout << f() << endl; cout << f() << endl; cout << f() << endl; cout << val << endl; } int main() { test(); return 0; }
默认情况下,如果一个lambda体包含return之外的任何语句,则编译器假定此lambda返回void.
与其他返回void的函数类似,被推断为返回void的lambda不能有返回值。
举例:
#include <iostream> #include<algorithm> using namespace std; void test() { int arr[5] = { -1,-2,-3,-4,-5 }; transform(arr, arr + 5,arr,[](int i) {return i < 0 ? -i : i; }); for (int i = 0; i < 5; i++) { cout << arr[i]; } cout << endl; } int main() { test(); return 0; }
#include <iostream> #include<algorithm> using namespace std; void test() { int arr[5] = { -1,-2,-3,-4,-5 }; transform(arr, arr + 5, arr, [](int i)->int{if (i < 0) return -i;else return i;}); for (int i = 0; i < 5; i++) { cout << arr[i]; } cout << endl; } int main() { test(); return 0; }
当我们编写了一个lambda后,编译器将该表达式翻译成一个未命名类的未命名对象。
在lambda表达式产生的类中含有一个重载的函数调用运算符。
举例:
#include <iostream> #include <vector> #include <algorithm> using namespace std; void bigger(vector<string>& words,vector<string>::size_type sz) { //根据单词的长度对其进行排序 stable_sort(words.begin(), words.end(), [](const string& a1, const string& a2) {return a1.size()<a2.size(); }); for_each(words.begin(), words.end(), [](const string& a) {cout << a << " "; }); } int main() { vector<string> svec{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" }; bigger(svec, 4); }
#include <iostream> #include <vector> #include <algorithm> using namespace std; class ShortString { public: bool operator()(const string& a1, const string& a2) { return a1.size() < a2.size(); } }; void bigger(vector<string>& words,vector<string>::size_type sz) { //根据单词的长度对其进行排序 stable_sort(words.begin(), words.end(), ShortString()); for_each(words.begin(), words.end(), [](const string& a) {cout << a << " "; }); } int main() { vector<string> svec{ "the", "quick", "red", "fox", "jumps","over", "the", "slow", "red", "turtle" }; bigger(svec, 4); }
当一个lambda表达式通过引用捕获变量时,将由程序负责确保lambda执行时引用所引的对象确实存在。因此,编译器可以直接使用该引用而无需在lambda产生的类中将其存储为数据成员。
相反,通过值捕获的变量被拷贝到lambda中。因此,这种lambda产生的类必须为每个值捕获的变量建立对应的数据成员,同时创建构造函数,令其使用捕获捕获的变量的值来初始化数据成员。
举个例子:
void bigger(vector<string>& words,vector<string>::size_type sz) { auto wc = find_if(words.begin(), words.end(), [sz](const string& a) {return a.size() >= sz; }); }
该lambda表达式的产生的类的将形如:
class SizeComp { private: size_t sz;//该数据成员对应通过值捕获的变量 public: SizeComp(size_t n):sz(n){}//该形参对应的捕获变量 //该调用运算符的返回类型,形参和函数体都与lambda一致 bool operator()(const string& s)const { return s.size() >= sz; } };
上面这个类含有一个数据成员以及一个用于初始化该成员的构造函数。
这个合成的类不含有默认构造函数,因此想使用这个类必须提供一个实参:
void bigger(vector<string>& words,vector<string>::size_type sz) { auto wc = find_if(words.begin(), words.end(), SizeComp(sz)); }
lambda表达式产生的类不含默认构造函数,赋值运算符及默认析构函数;
它是否含有默认的拷贝/移动构造函数则通常要视捕获的数据成员类型而定。
联系客服