您的位置:首页 > 编程语言 > C语言/C++

C++11的Lambda表达式

2016-06-23 00:05 190 查看
随着函数式编程语言的应用,很多语言都提供了 lambda 表达式,如 C#,Python,Java 8。lambda 表达式可以方便地构造匿名函数,如果代码里面存在大量的小函数,而这些函数一般只被调用一次,那么将他们重构成 lambda 表达式。C++11新标准里添加了lambda表达式这个语言特性,有着自己特定的格式和要求。

C++11 lambda格式

lambda表达式的通用格式如下:

[capture list](param list)mutable/noexcept/throw -> return type { function body}


capture list: 捕获列表,用来访问定义lambda函数中的局部变量的列表

param list:参数列表,将lambda表达式作为调用对象进行调用的参数,可以忽略

return type:返回类型,可以显示指定,可以忽略

function body:与普通函数一样,定义函数体

可以接受的语法如下:

[var1](int a, int b)mutable ->int { return a + b - val1; }//全部语法
[val1](int a, int b) {return a+b-val1;}//省略返回类型
[] { return 123; }//省略参数列表


返回类型

上述语法中的return type可以省略,但是其前提条件是只有单一的return语句。如果function body包含return以外的语句,编译器就会在省略return type时假定返回类型为void,此时,如果多条语句中任然包含return语句且不为void,那么就会出错。

//返回类型为int
[](int i) { return i > 0 ? i : -i; }

//多条语句,推导为void,但是实际return不为void,故出错
[](int i) { if(i > 0) return i; else return -i; }


如果包含多条语句,而要求返回类型不为void,就要显式指定返回类型。因此上述错误的lambda可以修改为如下结构:

//修改为显示指定返回类型即可
[](int i) -> int {if (i>0) return i; else return -i; }


调用参数

参数传递就和普通函数的形参传递一样,lambda表达式得到的可调用对象直接使用函数调用运算符使用传递的形参进行函数调用。

捕获参数

由于lambda本质上就是一个少量代码构成的完成一定功能的函数,在实现函数功能的同时,可以传递给这个lambda表达式定义它所在的执行上下文中的局部变量作为捕获参数。捕获参数可以有如下几种方式:

1.值捕获

类似值传递,值捕获会对参数进行拷贝,必须要求相应参数能进行拷贝。

[ca](int i) { return ca + i; }


2.引用捕获

如果捕获参数不能进行拷贝,可以使用引用捕获。与其他引用类似,由于捕获的是执行上下文中局部变量,因此必须保证lambda表达式执行的时候引用捕获的局部变量存在,这个问题与普通函数不能返回局部变量的引用的情形相同。

[&out, c](const string & s){out << s << c;}


上述的out参数为引用捕获,这是因为不能拷贝ostream对象,只能使用引用捕获方式。

lambda捕获从定义开始到执行这段时间内,必须确保所有执行期间需要的信息有意义,因此要尽量避免捕获如引用、指针、迭代器等类型的变量,因为这些类型的变量会在lambda调用前,在别处进行修改而且也可能会失效。

3.隐式捕获

如果希望编译器自行推断使用的lambda表达式外部的参数,可以使用隐式捕获来进行,其中
[=]
表示隐式值捕获,
[&]
表示隐式引用捕获。

如果希望对一部分变量显式指定,其他进行隐式捕获,可以混用。但是前提是,所有混用的捕获方式必须放在捕获列表最开头,而后面的显式指定的变量必须与隐式捕获不同的方式进行捕获。

//隐式值捕获,捕获局部变量b
[=](int a){ return a + b;}

//隐式引用捕获,捕获引用变量out
[&](const string & s){out << s;}

//混合捕获,隐式在第一个,显式使用与隐式不同的捕获方式
[&, c](const string & s){out << s << c;}//值捕获c
[=, &out](const string & s){out << s << c;}//引用捕获out


lambda表达式的本质

定义一个lambda时,编译器实质上生成了一个与lambda对应的匿名类类型,当定义一个lambda表达式就定义了一个匿名类,捕获参数就是这个匿名类的常量数据成员(如果lambda定义中使用了mutable修饰,就可以在lambda表达式内部修改捕获参数),数据成员在lambda表达式对象创建时被初始化,定义一个变量时就生成了这个匿名类的对象:

auto fun = [ca](int i) mutable { return ++ca + i;};


上述定义的语句中,编译器根据等号右边的lambda表达式生成了一个匿名类,拥有的类拥有一个ca数据成员(使用mutable修饰,故可以在lambda函数体内修改ca)。定义fun变量就实例化了一个这个匿名类的对象。函数体和参数就是一个类成员函数,fun就是一个函数对象,可以直接调用。

总结:lambda表达式是众多函数式编程的基础,C++11引入的这个语言特性使得快速创建使用一两次的小片段功能代码非常方便,但是如果需要在多个地方使用相同操作,还是应该定义函数;另外如果需要大量语句完成较复杂的功能,也应该定义函数来实现。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: