Lambda表达式
持续更新,补充C++新增feature,目录结构遵循《C++ Primer》
Lambda 表达式
C++ 11引入了一种新的类型称为lambda表达式,一个lambda表达式代表一个可调用的代码单元,可以将其理解为一个匿名的内联函数。Lambda表达式的定义为:
\[[capture \thinspace list] \thinspace (parameter \thinspace list) \enspace \rightarrow \enspace return \thinspace type \enspace \{ \enspace function \thinspace body \enspace \}\]其中$capture \thinspace list$(捕获列表)是一个lambda表达式中定义局部变量的列表(通常为空)。参数列表和返回类型可以忽略,捕获列表必须存在。
auto f = []{return 42;}
捕获列表的规则
[] |
不适用任何外部变量 |
[=] |
隐式捕获,= 必须放在最前面,以传值的形式使用外部所有变量,值不可以被修改 |
[&] |
隐式捕获,& 必须放在最前面,以引用的形式使用所有外部变量,引用可以修改 |
[x,&y] |
x以值传递(拷贝)形式引入, y以引用形式引入 |
[=,&x,&y] |
x,y以引用形式使用,其余变量以传值形式引入 |
[&,x,y] |
x,y以传值形式引入,其余变量以引用形式使用 |
对于捕获列表,只用于捕获所在函数内的局部变量,对于全局符号,或者static变量,则无需出现在捕获列表,里,例如下面代码中的cout
属于全局符号,不需要捕获:
vector<string> words{"C++","Java","Ruby"};
for_each(words.begin(),words.end(), [](const string& s){
cout<<s<<endl;
});
当定义了一个lambda对象时,编译器做了这么几件事:
- 创建了一个与lambda对应的新类型(未命名,它实际上是一个Functor重载了
()
运算符,在运算符重载一节还会分析)。当向一个函数传递lambda时,同时定义了一个新类型和该类型的一个对象 - 在生成的新类型中,捕获列表作为该类的数据成员在lambda对象被创建时初始化
- 如果使用
auto
定义lambda变量时,实际上定义了一个从lambda生成的类型的对象
尽量保持lambda捕获的变量简单化,避免捕捉指针或者引用。如果捕获了引用,应确保在lambda函数体执行时该引用仍有效。
mutable lambda
如果一个参数以值捕获的方式被lamda所引用,那么它的值是不能被修改的,如果要求改被捕获的值,需要使用mutable
关键字。
void fcn3(){
size_t v1 = 42; //局部变量
//f可以改变v1
auto f = [v1]()mutable { return ++v1; }
v1 = 0;
auto j = f(); //j = 43;
}
如果局部变量是以引用的形式被捕获,则不需使用mutable关键字,能否修改被捕获的变量要看引用的类型,是否是const
bind函数
考虑这样一个问题,标准库中的find_if
函数接受三个参数,前两个是迭代器对象用来确定查找返回,第三个参数是接受一个参数的函数指针,我们可以使用lambda表达式来作为第三个参数:
find_if(words.begin(),words.end(),[sz](const string& s){
return s.size() > sz;
});
上个面的lambda表达式实际上是接受了2个参数s
和sz
,s
是find_if
传入lambda表达式的,sz
是由lambda捕获的,函数的作用是比较s.size()
和sz
。这个问题也可以使用函数指针,我们可以定义这样一个函数:
bool check_size(const string& s, size_t sz){
return s.size() > sz;
}
这里便出现了一个问题,check_size
接受两个参数,显然不满足find_if
的要求,因此便不能将check_size
当做参数传递给find_if
。
这时我们需要使用bind
对check_size
进行一下包装,C++ 11中对bind
的定义为:
auto newCallable = bind(callable, arg_list);
其中newCallable
是一个可调用对象,arg_list
是一个callable
接受的以逗号分隔的参数列表,当调用newCallable
时,newCallable
会调用callable
,并传入arg_list
作为参数。
arg_list
中的参数可能包含形如_n
的名字,其中n
是一个自然数,代表“占位符”,例如_1
表示newCallable
的第一个参数,_2
表示第二个参数,以此类推。
接下来我们可以用bind
来包装check_size
来生成一个可以调用check_size
的对象,如下:
auto check = bind(check_size,_1,6);
string s = "hello";
check(s); //check(s)会调用check_size(s,6);
上述check
函数只接受一个参数_1
,该参数对应check_size
的第一个参数,即const string&
类型,这样就可将其应用于find_if
函数:
using namespace std::placeholders
find_if(words.begin(),words.end(),bind(check_size,_1,sz));
- 使用placeholder
占位符_n
定义在std::placeholders
的命名空间中,使用前要进行using
声明。_n
还可以用来调整函数接受参数的顺序,例如
auto g = bind(f,a,b,_2,c,_1);
即g
的第二参_2
数对应f
的第三个参数,g
的第一个参数_1
对应f
的最后一个参数,例如:
g(X,Y) //会调用 f(a,b,Y,c,X);
- 绑定引用参数
对于bind
的另一个问题是如何绑定引用类型的参数,除占位符以外的参数,默认是值传递,有些情况参数无法拷贝,只能取引用,这时需要使用ref
函数(定义在functional
),ref
返回一个对象,包含给定的引用,此对象是可以拷贝的,类似的还有cref
,生成一个保存const
引用类的对象。
void some_func{
...
//os是一个局部变量,引用一个输出流
//c是一个局部变量,类型为char
//使用lambda表达式来捕获引用类型
for_each(words.begin(),words.end(),[&os, c](const string &s){
os<<s<<c;
});
}
对上述代码使用函数进行改写后,使用bind
:
ostream& print(ostream& os, const string& s, char c){
return os<<s<<c;
}
void some_func(){
...
//os是一个局部变量,引用一个输出流
//c是一个局部变量,类型为char
//使用ref来返回一个引用类型的可拷贝对象
for_each(words.begin(),words.end(),bind(print,ref(os),_1,c));
}
STL中的bind1st, bind2nd函数已被废弃