`
frontier2036
  • 浏览: 843 次
文章分类
社区版块
存档分类
最新评论

c++ lambda表达式

    博客分类:
  • c++
阅读更多

从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++ Lambda表达式是C++语言的一个重要特性,引入于C++11标准中。它可以作为函数对象使用,可以用来替代一些繁琐的函数声明和定义。Lambda表达式的基本语法结构如下:[capture list] ...

    C++ 11 lambda 表达式

    C++ 11引入了lambda表达式,这是一个强大的特性,极大地增强了C++的函数式编程能力。Lambda表达式允许在程序中直接定义匿名函数,并且可以直接在需要的地方使用,无需预先声明。这对于处理回调函数、简化算法实现...

    C++ Lambda Story - From C++98 to C++20.pdf

    **C++ Lambda表达式**是C++编程语言中一个强大的特性,从C++11标准开始引入,到C++20标准进一步增强。Lambda表达式允许程序员在代码中定义匿名函数,即没有名称的函数,这极大地提高了代码的灵活性和可读性。本书...

    C++ 中的 Lambda 表达式

    C++11引入了许多重要的新特性,其中最显著的一项是Lambda表达式。这一特性极大地简化了代码编写过程,使得开发者能够更方便地创建匿名函数对象。Lambda表达式在很多场景下都可以替代回调函数,并且在诸如STL容器的...

    在Android JNI环境下使用C++ Lambda表达式等

    本apk包只是一个C++可用性的测试,主要展示以下内容: 1、使用std::function和std...2、使用C++11的Lambda表达式 3、在C++代码中回调Java对象的方法 详细描述请参考:http://blog.csdn.net/dyw/article/details/8099947

    C++ 中lambda表达式的编译器实现原理

    C++中的Lambda表达式是C++11引入的一项重要特性,它允许程序员在代码中定义匿名函数,并且可以直接在定义的地方使用。Lambda表达式的引入极大地增强了C++的可读性和简洁性,尤其是在处理函数对象和回调函数时。下面...

    C++11新特性:Lambda表达式.pdf

    C++11新特性:Lambda表达式 Lambda表达式是C++11新特性中的一种,全新的特性听起来很深奥,但却是很多其他语言早已提供(比如C#)或者即将提供(比如Java)的。Lambda表达式就是用于创建匿名函数的,GCC 4.5.x和...

    C++ 之 Lambda 表达式

    C++之Lambda表达式 C++之Lambda表达式是C++11标准中引入的一种新的函数定义方式,用于定义匿名函数,使得代码更加灵活简洁。Lambda表达式与普通函数类似,也有参数列表、返回值类型和函数体,只是它的定义方式更...

    C++ Lambda表达式使用详解

     对于一次性的,带参数表达式,用LB可以节省不必要的class定义和维护,简化程序的设计-维护代价。  比如下面的vector处理代码,简洁明了:  vector&lt;int&gt; v1(10, 1);  int sum = 0;  for_each (v1.begin()...

    基于C++ Lambda表达式的程序优化

    ### 什么是C++ Lambda表达式 C++ Lambda表达式是C++11标准引入的一个重要特性,它允许开发者在程序中直接定义匿名函数,即没有具体函数名的函数。在Objective-C和Swift等其他编程语言中,与之相似的特性分别是block...

    1. lambda 表达式1

    - `[=]`:按值捕获所有变量,意味着在lambda表达式内部创建这些变量的副本,但不能修改它们。 - `[&]`:按引用捕获所有变量,允许在lambda表达式内部读写这些变量。 - `[var]`或`[&var]`:按需捕获单个变量,可以...

    C++11 lambda表达式

    C++11中的lambda表达式是一种强大的特性,它允许程序员在代码中定义匿名函数,这些函数可以在需要的地方直接使用,而无需预先声明。这极大地方便了代码的编写,特别是对于那些只用一次的函数,同时也增加了代码的...

    C++ 11,14,17中的 Lambda 表达式 _ Microsoft Docs[2].pdf

    查Lambda表达式资料时很容易被函数闭包、Lambda演算、形式系统这些深奥名词淹没而放弃学习,其实Lambda表达式就是匿名函数(annoymous function)——允许我们使用一个函数,但不需要给这个函数起名字。还是有点难懂...

    轻松学C#之委托、事件和Lambda表达式.rar

    在C#编程语言中,委托、事件和Lambda表达式是三个非常重要的概念,它们构成了C#强大功能的基础。本文将详细解析这些知识点,帮助你深入理解并熟练运用它们。 首先,我们来谈谈委托(Delegate)。委托在C#中类似于...

    lambda 表达式

    Lambda表达式作为C++0x(即C++11)引入的重大新特性之一,极大地简化了创建函数对象的过程,使得在需要回调函数或自定义比较等场景下,代码更加简洁和直观。lambda表达式允许开发者以更自然的方式编写代码,无需显式...

Global site tag (gtag.js) - Google Analytics