lambda表达式
我们可以向一个算法传递任何类别可调用对象,如果可以对其使用调用运算符(),则称它为可调用的。c 中可调用对象有函数、函数指针、重载函数调用运算符类、lambda
表达式。
一个**lambda
**表达式表示一个可调用的代码单元,可将其理解为一个未命名的内联函数。一个**lambda
**具有一个返回类型、一个参数列表和一个函数体(同函数一样)。与函数不同的是,**lambda
**可定义在函数内部,有捕获列表:
[capture list] (parameter list)->return type{ function body };
captue list
(捕获列表)是一个lambda所在函数中定义的局部变量列表(通常为空)return type
为返回类型,parameter list
为参数列表、function body
为函数体
可以忽略参数列表(等价于指定一个空参数列表)和返回类型(此时根据代码推断,有return返回相应类型,没有为void),但必须包含捕获列表和函数体:
代码语言:txt复制auto f=[] {return 42;};
cout<<f()<<endl; //调用时也有调用运算符()
//lambda不能设默认参数,因此一个lambda调用时实参数目必须与形参一一对应。
[](const string &s1,const string &s2)
{
return s1.size()<s2.size();
}
上面的代码中,第一段代码定义了一个 lambda 表达式 f,它不接受任何参数,返回值为 42。第二行代码调用了这个 lambda 表达式,并输出其返回值 42。
lambda 表达式是 C 11 引入的一种新特性,可以用于定义一个匿名函数对象。它的基本形式为:
capture mutable(optional) exception-> return-type { body }
其中:
- capture:表示捕获列表,用于指定需要捕获的变量,可以是值传递或引用传递方式。
- parameters:表示参数列表。
- mutable:表示可变性,用于指定 lambda 表达式是否可以修改其捕获的变量。
- exception:表示异常规格说明。
- return-type:表示返回值类型。
- body:表示函数体。
第二段代码定义了一个带有两个参数的 lambda 表达式,它返回第一个参数字符串的长度是否小于第二个参数字符串的长度。该 lambda 表达式中使用了一个函数对象调用运算符,用于在调用时执行 lambda 表达式的函数体,并返回计算结果。
捕获规则
lambda表达式的捕获列表有值捕获和引用捕获!
我们可以在捕获列表中写一个**&
**或者**=
**,指示编译器推断捕获列表。**&
**告诉编译器采用捕获引用方式,**=
**则表示采用值捕获方式。我们希望对一部分变量采用值捕获,对其他变量采用引用捕获,可以混合使用隐式捕获和显式捕获:
- 当混合使用隐式捕获和显式捕获时,捕获列表中的第一个元素必须是一个
&
或=
(必须隐式) - 当混合使用隐式捕获和显式捕获时,显式捕获的变量必须使用与隐式捕获不同的方式
void biggies(vector<string> &words,vector<string>::size_type sz,ostream &os,string c=" "){ //words,sz c采用值捕获,os为引用捕获
for_each(words.begin(),words.end(),=,&os{
os<<s<<c<<ends;
}
);
}
上面的代码是
函数名为biggies
,接受三个参数:一个引用类型的vector``<string>
words
,一个vector``<string>``::size_type
类型的sz,一个ostream&
类型的os
,以及一个默认参数为一个空格字符串的string类型的c。
函数体中调用了STL库函数for_each()
,用来对words容器中的每个元素进行操作。对于每个元素,定义了一个lambda表达式作为for_each()的第三个参数,这个lambda表达式中使用了值捕获和引用捕获。其中,通过[=]
表示采用值捕获,即将函数体内除了os以外的所有局部变量(包括words,sz和c)都以值的形式传递给lambda表达式。通过“&os”表示引用捕获,即将os以引用的形式传递给lambda表达式。lambda表达式的函数体中将每个元素插入到os流中,并在字符串后面加上c参数所表示的字符串。函数执行完毕后,os流中包含了所有元素和它们之间的分隔符。
默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。如果我们希望能改变一个被捕获的变量的值,就必须在参数列表首加上关键字mutable
int sum(int& a, int& b, int& c)
{
c = 10;
a = 11;
b = 12;
cout << a<<" " << b<<" " << c << endl;
return a b c;
}
void lambdaTest(int a, int b)
{
int c = 20;
auto f = [&,c]()mutable {return sum(a, b, c); };
int ret = f();
cout << ret <<" " << a <<" "<<b <<" " << c << endl;
}
代码解释
首先,定义了一个函数sum
,它有三个引用参数a
、b
和c
。在函数中,将c
设置为10,a
设置为11,b
设置为12,然后返回这三个参数的和。
然后定义了一个lambda表达式f
,它通过捕获列表绑定了外部变量a
、b
和c
的引用,其中变量c
通过mutable
关键字被标记为可修改的。在lambda表达式中,sum
函数被调用,并将其返回值存储在变量ret
中。
最后,将ret
、a
、b
和c
的值打印到标准输出流中。由于在sum
函数中,a
、b
和c
是作为引用参数传递的,因此它们的值也被修改了。输出结果将显示a
和b
的值没有被改变,因为它们只是被引用传递,而c
的值已经被修改为10,因为它是被传递的引用参数。此外,由于c
在捕获列表中被标记为可修改的,因此它的值也被修改为13,因为在sum
函数中,它的值被设置为10。
输出:
代码语言:txt复制11 12 10
33 11 12 20
lambda是函数对象
我们编写一个lambda
后,编译器将表达式翻译成一个未命名类的未命名对象,这个类中有一个重载的函数调用运算符。如下面这个lambda
:
sort(vec.begin(),vec.end(),{return a.size()<b.size();});
相似于下面类对象:由于默认情况下lambda
不能改变它捕获的变量,因此在默认情况下lambda
生成的类当中的函数调用运算符是const
成员函数:
class shorter{
public:
bool operator()(const string&a,const string&b)const{
return a.size()<b.size();
}
};
生成类对于lambda的值捕获与引用捕获的不同
当lambda
表达式通过引用捕获变量时 ,程序确保lambda
执行引用时所引用的对象确实存在,编译器可以直接使用该引用而无需再lambda
产生的类中将其存储。但是通过值捕获时,在lambda
生成的类中需要为值捕获的变量生成数据成员,创建构造函数:
auto w=find_if(vec.begin(),vec.end(),sz{return a.size()>=sz;});
该lambda
值捕获sz
,则其产生的类将形如:
class Sizecmp{
public:
Sziecamp(size_t n):sz(n){}
bool operator()(const string&a)const{
return a.size()>=sz;
private:
size_t sz;
};
标准库定义了一组表示算术、关系、逻辑运算符的类,都被定义成模板的形式,可以为其指定具体的应用类型即调用运算符的形参类型。
我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!