从c++11之后,c++出现了不少新特性,其中最让我感兴趣的是lambda表达式,它可以让我们在需要的时候定义一个匿名函数,自然带来和不少的方便,并且在匿名函数的内部可以对非函数内定义的变量进行操作,称为闭包。在java中常用闭包,现在终于也可以在c++中使用了。
?
lambda表达式有以下几种声明方式:
monospace !important;">(1)[
?capture-list?]
?(
?params?)
?mutable
(optional)?exception?attribute?->
?ret?{
?body?}
[],内是要获取的匿名函数外的变量,
params,是调用匿名函数需要传入的参数
mutable,这是一个可选的选项,因为在匿名函数内通过值的copy得到的变量是readonly,而当加上了mutable关键字后,在函数体内就可以改变变量的值。
还记得mutable的用法吗,在一个类中的常函数不能修改成员变量的值,但如果成员变量声明为了mutable,那么就为常函数创建了一个摆动场,在常函数中就可以改变成员函数的值了。
?
(2)[
?capture-list?]
?(
?params?)
?->
?ret?{
?body?}
这种方式去掉了mutable,其他与第一种无区别
?
(3)[
?capture-list?]
?(
?params?)
?{
?body?}
更简单的写法,省去了ret,那么匿名函数的返回类型决定于body中最后的返回值
?
(4)[
?capture-list?]
{
?body?}
省去了参数的传入,即此匿名函数不用传参
?
capture-list中规定了在函数体内要获取哪些函数之外声明的变量,以及如何获取这些变量,具体如下:
[a,&b] 这种方式下,a会以传值的方式在函数体内存在,即如果在函数体内修改a的值,不会影响函数之外的原变量的值,b则以引用的方式存在于函数体内,可想而知,如果在函数中修改b的值,一定会影响函数之外的变量的值。
?
[this]?获取对象的指针,当然首先this指针要存在
?
[&] 以引用的方式获取在函数体内使用到的任何函数外的变量
?
[=] 以传值的方式获取在函数体内使用到的任何函数外的变量
?
[] 不获取任何值
?
下面就来创建简单的lambda表达式:
?
class="cpp">int n = 10; auto lambda = [n](int a, int b) { cout << "a + b = " << a + b << " n = " << n << endl; return a + b; }; int a = 7, b = 8; int c = lambda(a, b); cout << "c = " << c << endl;
?首先定义一个变量n,并且赋值为10,在lambda表达式中以传值的方式获取n,函数的功能很简单,输出a+b的值和n的值,并返回a与b之和
?
编译一下:
?
$ g++ -o lambda lambda.cpp
?居然报错了:
?
?
lambda.cpp: In function ‘int main()’: lambda.cpp:6:7: error: ‘lambda’ does not name a type auto lambda = [n](int a, int b) mutable { ^ lambda.cpp:11:21: error: ‘lambda’ was not declared in this scope int c = lambda(a, b); ^
?很明显,g++默认以c98标准编译,所以会报错,添加相应参数,再编译:
?
?
$ g++ -o lambda lambda.cpp -std=c++11 $ ./lambda a + b = 15 n = 10 c = 15
?
?可以看到在匿名函数中成功地得到了n的值,以一个int变量接受lambda的值,能够成功获取,可见在省去ret的声明时,lambda的返回值由函数体中返回的类型决定。
?
下面修改一下代码,如下:
?
auto lambda = [n](int a, int b) { n = 100; cout << "a + b = " << a + b << " n = " << n << endl; return a + b; };
?增加了一个操作,将n的值修改为100,编译:
?
?
$ g++ -o lambda lambda.cpp -std=c++11 lambda.cpp: In lambda function: lambda.cpp:7:5: error: assignment of read-only variable ‘n’ n = 100; ^
?编译报错,因为n在表达式中为read-only。
?
那么如何才能修改n的值呢,根据lambda的特点,可以将n以引用的方式获取:
?
auto lambda = [&n](int a, int b) { n = 100; cout << "a + b = " << a + b << " n = " << n << endl; return a + b; }; int a = 7, b = 8; int c = lambda(a, b); cout << "c = " << c << endl; cout << "n = " << n << endl;
?重新编译,再运行,结果如下:
?
?
$ ./lambda a + b = 15 n = 100 c = 15 n = 100
?可以看出来,在lambda外n的值也是100,也就是说以引用方式获取的变量虽然可以改变其值,但是会影响到lambda之外的变量值,因为是引用嘛。
?
如果不想造成这样的影响,那么就要请mutable出场了,还记得mutable的作用吗,就是允许lambda修改原本应该是read-only的变量:
?
int n = 10; auto lambda = [n](int a, int b) mutable { n = 100; cout << "a + b = " << a + b << " n = " << n << endl; return a + b; }; int a = 7, b = 8; int c = lambda(a, b); cout << "c = " << c << endl; cout << "n = " << n << endl;
?将n改为传值,但加上了mutable关键字,此时结果为:
?
?
$ ./lambda a + b = 15 n = 100 c = 15 n = 10
?不难看出n的值在lambda中被修改,但不影响lambda之外的n,因为lambda内的n是外面的n的一个copy而非引用。
?
?
到这里lambda基本就介绍完了,不过很好奇编译器究竟是如何编译lambda表达式的,于是有了下面的尝试
?
$ gdb lambda
?在gdb中调试一下,首先看看main的汇编代码:
?
?
Dump of assembler code for function main: 0x000000000040091c <+0>: push %rbp 0x000000000040091d <+1>: mov %rsp,%rbp 0x0000000000400920 <+4>: sub $0x20,%rsp 0x0000000000400924 <+8>: movl $0xa,-0x10(%rbp) 0x000000000040092b <+15>: mov -0x10(%rbp),%eax 0x000000000040092e <+18>: mov %eax,-0x20(%rbp) 0x0000000000400931 <+21>: movl $0x7,-0xc(%rbp) 0x0000000000400938 <+28>: movl $0x8,-0x8(%rbp) 0x000000000040093f <+35>: mov -0x8(%rbp),%edx 0x0000000000400942 <+38>: mov -0xc(%rbp),%ecx 0x0000000000400945 <+41>: lea -0x20(%rbp),%rax 0x0000000000400949 <+45>: mov %ecx,%esi 0x000000000040094b <+47>: mov %rax,%rdi 0x000000000040094e <+50>: callq 0x40089e <_ZZ4mainENUliiE_clEii> 0x0000000000400953 <+55>: mov %eax,-0x4(%rbp) 0x0000000000400956 <+58>: mov $0x400aa4,%esi 0x000000000040095b <+63>: mov $0x601080,%edi 0x0000000000400960 <+68>: callq 0x400780 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 0x0000000000400965 <+73>: mov -0x4(%rbp),%edx 0x0000000000400968 <+76>: mov %edx,%esi 0x000000000040096a <+78>: mov %rax,%rdi 0x000000000040096d <+81>: callq 0x400720 <_ZNSolsEi@plt> 0x0000000000400972 <+86>: mov $0x4007a0,%esi 0x0000000000400977 <+91>: mov %rax,%rdi 0x000000000040097a <+94>: callq 0x400790 <_ZNSolsEPFRSoS_E@plt> 0x000000000040097f <+99>: mov $0x400aa9,%esi 0x0000000000400984 <+104>: mov $0x601080,%edi 0x0000000000400989 <+109>: callq 0x400780 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 0x000000000040098e <+114>: mov -0x10(%rbp),%edx 0x0000000000400991 <+117>: mov %edx,%esi 0x0000000000400993 <+119>: mov %rax,%rdi 0x0000000000400996 <+122>: callq 0x400720 <_ZNSolsEi@plt> 0x000000000040099b <+127>: mov $0x4007a0,%esi 0x00000000004009a0 <+132>: mov %rax,%rdi 0x00000000004009a3 <+135>: callq 0x400790 <_ZNSolsEPFRSoS_E@plt> 0x00000000004009a8 <+140>: mov $0x0,%eax 0x00000000004009ad <+145>: leaveq 0x00000000004009ae <+146>: retq End of assembler dump.
?在汇编代码中发现这一行
?
?
callq 0x40089e <_ZZ4mainENUliiE_clEii>
?
不难分析出编译器是将lambda当成了一个普通函数编译了,看看_ZZ4mainENUliiE_clEii的汇编代码:
Dump of assembler code for function _ZZ4mainENUliiE_clEii: 0x000000000040089e <+0>: push %rbp 0x000000000040089f <+1>: mov %rsp,%rbp 0x00000000004008a2 <+4>: push %r12 0x00000000004008a4 <+6>: push %rbx 0x00000000004008a5 <+7>: sub $0x10,%rsp 0x00000000004008a9 <+11>: mov %rdi,-0x18(%rbp) 0x00000000004008ad <+15>: mov %esi,-0x1c(%rbp) 0x00000000004008b0 <+18>: mov %edx,-0x20(%rbp) 0x00000000004008b3 <+21>: mov -0x18(%rbp),%rax 0x00000000004008b7 <+25>: movl $0x64,(%rax) 0x00000000004008bd <+31>: mov -0x18(%rbp),%rax 0x00000000004008c1 <+35>: mov (%rax),%ebx 0x00000000004008c3 <+37>: mov -0x20(%rbp),%eax 0x00000000004008c6 <+40>: mov -0x1c(%rbp),%edx 0x00000000004008c9 <+43>: lea (%rdx,%rax,1),%r12d 0x00000000004008cd <+47>: mov $0x400a95,%esi 0x00000000004008d2 <+52>: mov $0x601080,%edi 0x00000000004008d7 <+57>: callq 0x400780 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 0x00000000004008dc <+62>: mov %r12d,%esi 0x00000000004008df <+65>: mov %rax,%rdi 0x00000000004008e2 <+68>: callq 0x400720 <_ZNSolsEi@plt> 0x00000000004008e7 <+73>: mov $0x400a9e,%esi 0x00000000004008ec <+78>: mov %rax,%rdi 0x00000000004008ef <+81>: callq 0x400780 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 0x00000000004008f4 <+86>: mov %ebx,%esi 0x00000000004008f6 <+88>: mov %rax,%rdi 0x00000000004008f9 <+91>: callq 0x400720 <_ZNSolsEi@plt> 0x00000000004008fe <+96>: mov $0x4007a0,%esi 0x0000000000400903 <+101>: mov %rax,%rdi 0x0000000000400906 <+104>: callq 0x400790 <_ZNSolsEPFRSoS_E@plt> 0x000000000040090b <+109>: mov -0x20(%rbp),%eax 0x000000000040090e <+112>: mov -0x1c(%rbp),%edx 0x0000000000400911 <+115>: add %edx,%eax 0x0000000000400913 <+117>: add $0x10,%rsp 0x0000000000400917 <+121>: pop %rbx 0x0000000000400918 <+122>: pop %r12 0x000000000040091a <+124>: pop %rbp 0x000000000040091b <+125>: retq End of assembler dump.
?
可以看出正是我们在lambda表达式中所做的操作。
?
(全文完)