从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表达式中所做的操作。
(全文完)
相关推荐
C++ Lambda表达式详解 C++ Lambda表达式是C++语言的一个重要特性,引入于C++11标准中。它可以作为函数对象使用,可以用来替代一些繁琐的函数声明和定义。Lambda表达式的基本语法结构如下:[capture list] ...
C++ 11引入了lambda表达式,这是一个强大的特性,极大地增强了C++的函数式编程能力。Lambda表达式允许在程序中直接定义匿名函数,并且可以直接在需要的地方使用,无需预先声明。这对于处理回调函数、简化算法实现...
**C++ Lambda表达式**是C++编程语言中一个强大的特性,从C++11标准开始引入,到C++20标准进一步增强。Lambda表达式允许程序员在代码中定义匿名函数,即没有名称的函数,这极大地提高了代码的灵活性和可读性。本书...
C++11引入了许多重要的新特性,其中最显著的一项是Lambda表达式。这一特性极大地简化了代码编写过程,使得开发者能够更方便地创建匿名函数对象。Lambda表达式在很多场景下都可以替代回调函数,并且在诸如STL容器的...
本apk包只是一个C++可用性的测试,主要展示以下内容: 1、使用std::function和std...2、使用C++11的Lambda表达式 3、在C++代码中回调Java对象的方法 详细描述请参考:http://blog.csdn.net/dyw/article/details/8099947
C++中的Lambda表达式是C++11引入的一项重要特性,它允许程序员在代码中定义匿名函数,并且可以直接在定义的地方使用。Lambda表达式的引入极大地增强了C++的可读性和简洁性,尤其是在处理函数对象和回调函数时。下面...
C++11新特性:Lambda表达式 Lambda表达式是C++11新特性中的一种,全新的特性听起来很深奥,但却是很多其他语言早已提供(比如C#)或者即将提供(比如Java)的。Lambda表达式就是用于创建匿名函数的,GCC 4.5.x和...
C++之Lambda表达式 C++之Lambda表达式是C++11标准中引入的一种新的函数定义方式,用于定义匿名函数,使得代码更加灵活简洁。Lambda表达式与普通函数类似,也有参数列表、返回值类型和函数体,只是它的定义方式更...
对于一次性的,带参数表达式,用LB可以节省不必要的class定义和维护,简化程序的设计-维护代价。 比如下面的vector处理代码,简洁明了: vector<int> v1(10, 1); int sum = 0; for_each (v1.begin()...
### 什么是C++ Lambda表达式 C++ Lambda表达式是C++11标准引入的一个重要特性,它允许开发者在程序中直接定义匿名函数,即没有具体函数名的函数。在Objective-C和Swift等其他编程语言中,与之相似的特性分别是block...
- `[=]`:按值捕获所有变量,意味着在lambda表达式内部创建这些变量的副本,但不能修改它们。 - `[&]`:按引用捕获所有变量,允许在lambda表达式内部读写这些变量。 - `[var]`或`[&var]`:按需捕获单个变量,可以...
C++11中的lambda表达式是一种强大的特性,它允许程序员在代码中定义匿名函数,这些函数可以在需要的地方直接使用,而无需预先声明。这极大地方便了代码的编写,特别是对于那些只用一次的函数,同时也增加了代码的...
查Lambda表达式资料时很容易被函数闭包、Lambda演算、形式系统这些深奥名词淹没而放弃学习,其实Lambda表达式就是匿名函数(annoymous function)——允许我们使用一个函数,但不需要给这个函数起名字。还是有点难懂...
在C#编程语言中,委托、事件和Lambda表达式是三个非常重要的概念,它们构成了C#强大功能的基础。本文将详细解析这些知识点,帮助你深入理解并熟练运用它们。 首先,我们来谈谈委托(Delegate)。委托在C#中类似于...
Lambda表达式作为C++0x(即C++11)引入的重大新特性之一,极大地简化了创建函数对象的过程,使得在需要回调函数或自定义比较等场景下,代码更加简洁和直观。lambda表达式允许开发者以更自然的方式编写代码,无需显式...