c++ lambda表达式

从c++11之后,c++出现了不少新特性,其中最让我感兴趣的是lambda表达式,它可以让我们在需要的时候定义一个匿名函数,自然带来和不少的方便,并且在匿名函数的内部可以对非函数内定义的变量进行操作,称为闭包。在java中常用闭包,现在终于也可以在c++中使用了。

 

lambda表达式声明

lambda表达式有以下几种声明方式:

(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

capture-list中规定了在函数体内要获取哪些函数之外声明的变量,以及如何获取这些变量,具体如下:

[a,&b] 这种方式下,a会以传值的方式在函数体内存在,即如果在函数体内修改a的值,不会影响函数之外的原变量的值,b则以引用的方式存在于函数体内,可想而知,如果在函数中修改b的值,一定会影响函数之外的变量的值。

 

[this] 获取对象的指针,当然首先this指针要存在

 

[&] 以引用的方式获取在函数体内使用到的任何函数外的变量

 

[=] 以传值的方式获取在函数体内使用到的任何函数外的变量

 

[] 不获取任何值

 

下面就来创建简单的lambda表达式:

 

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表达式中所做的操作。

 

(全文完)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值