c++ lambda表达式_C/C++_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > C/C++ > c++ lambda表达式

c++ lambda表达式

 2016/5/12 5:33:17  frontier2036  程序员俱乐部  我要评论(0)
  • 摘要:从c++11之后,c++出现了不少新特性,其中最让我感兴趣的是lambda表达式,它可以让我们在需要的时候定义一个匿名函数,自然带来和不少的方便,并且在匿名函数的内部可以对非函数内定义的变量进行操作,称为闭包。在java中常用闭包,现在终于也可以在c++中使用了。lambda表达式声明lambda表达式有以下几种声明方式:(1)[capture-list](params)mutable(optional)exceptionattribute->ret{body}[]
  • 标签:Lambda表达式 c++ 表达式

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

?

lambda表达式声明

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

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

?

(全文完)

发表评论
用户名: 匿名