Lambda表达式
用法
C++11加入了Lambda表达式,这是现代编程语言的一个特点,lambda表达式的优点:
- 声明式的编程风格:就地匿名定义目标函数或者函数对象,不需要额外写一个声明函数或者函数对象。类似匿名内部类。
- 简洁:避免了代码膨胀和功能分散,让开发更加高效。
- 在需要的时间和地点实现功能闭包,是程序更加灵活。
lambda表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。
语法形式:
[capture](params)opt->ret{body;};
其中capture是捕获列表,params是参数列表,opt是函数选项,ret是返回值类型,body是函数体;下面逐个详细介绍。
-
捕获列表capture:捕获一定范围内的变量,具体使用方式
[]:不捕获任何变量。
[&]:捕获外部作用域中所有变量,并作为引用在函数体内使用(按引用捕获)
[=]:捕获外部作用域中所有变量,并作为副本在函数体内使用(按值捕获),拷贝的副本在匿名函数体内部是只读的。如果想要修改需要添加函数选项mutable。
#include <iostream> using namespace std; void func(int x, int y) { int a = 7; int b = 9; [=, &x](int z) mutable { int c = a; int d = x; b++; cout << "bb: " << b << endl; }(88); //没有(88)就只是匿名函数的定义,并没有调用。(88)是调用。因为有参数,所有传入的参数是88,如果没有参数就是(); cout << "b: " << b << endl; } int main() { func(1, 2); system("pause"); return 0; }如果没有mutable变量b++就会报错。且这里是按值捕获所以这里在lambda里面的修改不会影响到外部变量。
[=,&foo]:按值捕获外部作用域中所有变量,并按照引用捕获外部变量foo。
[bar]:按值捕获bar变量,同时不捕获其他变量。
[&bar]:按引用捕获bar变量,同时不捕获其他变量。
[this]:捕获当前类中的this指针。让lambda表达式拥有和当前类成员函数同样的访问权限。如果已经使用了&或者=,默认添加此选项。
#include <iostream> using namespace std; class Test { public: void output(int x, int y) { auto x1 = [] {return m_number; }; //error auto x2 = [=] {return m_number + x + y; }; //ok auto x3 = [&] {return m_number + x + y; }; //ok auto x4 = [this] {return m_number; }; //ok auto x5 = [this] {return m_number + x + y; }; //error auto x6 = [this, x, y] {return m_number + x + y; }; //ok auto x7 = [this] {return m_number++; }; //ok } int m_number = 100; };x1:错误,没有捕获外部变量,不能使用类成员 m_number
x2:正确,以值拷贝的方式捕获所有外部变量
x3:正确,以引用的方式捕获所有外部变量
x4:正确,捕获this指针,可访问对象内部成员
x5:错误,捕获this指针,可访问类内部成员,没有捕获到变量x,y,因此不能访问。
x6:正确,捕获this指针,x,y
x7:正确,捕获this指针,并且可以修改对象内部变量的值 -
参数列表params:和普通函数的参数列表一样,如果没有参数参数列表可以省略不写。
auto f = [](){return 1;} //没有参数,参数列表为空 auto f = []{return 1;} //没有参数,参数列表省略不写。 -
函数选项opt:
有两个类型 mutable 和 exception 两种,不需要可以省略。
mutable:可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。
exception:指定函数抛出的异常,如抛出整数类型的异常,可以使用
throw(); -
返回值类型:在C++11中,lambda表达式的返回值是通过返回值后置语法来定义的。
很多时候,lambda表达式的返回值是非常明显的,因此在c++11中允许省略lambda表达式的返回。
//完整的lambda表达式定义 auto f = [](int a) -> int{ return a+10; }; //忽略返回值的lambda表达式定义 auto f = [](int a) { return a+10; };一般情况下,不指定lambda表达式的返回值,编译器会根据return语句自动推导返回值的类型,但需要注意的是初始化列表不能用于返回值的自动推导。
//可以自动推导出返回值类型 auto f = [](int i){ return i; } //不能推导出返回值类型 auto f1 = [](){ return {1, 2}; //基于列表初始化推导返回值,error。 }因为通过初始化列表可以初始化的类型很多,比如初始化一个数组int[],初始化一个结构体等很多,所以不能确定初始化的类型,因此需要把初始化的类型写出来。
-
函数体:函数的实现,这部分不能省略,但函数体可以为空。
lambda本质
lambda可以看作是一种 可调用对象 。
C++中的可调用对象有四种:(1)函数指针、(2)仿函数、(3)可以转换为函数指针的函数对象、(4)类里面的成员函数以及类里面的成员变量。
在c++中lambda可以被看做是一个仿函数。operator()的类。
按照c++标准,lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量值的。
mutable选项的作用就是在于取消operator()的const属性。
因为lambda表达式在c++中会被看作是一个仿函数,因此可以使用std::function(可调用函数包装器)进行包装称为函数指针,或者用std::bind绑定称为仿函数;但是如果没有捕获列表可以直接当一个函数指针看待。
#include <iostream>
#include <functional>
using namespace std;
void func(int x, int y) {
int a;
int b;
using ptr = void(*)(int); //定义一个函数指针。
ptr p1 = [](int x) {
cout << "x:" << x << endl;
};
p1(11);
function<void(int)> fff = [=](int x) { //function<返回值类型(参数类型)>
cout << "x:" << x << endl;
};
fff(11);
function<void(int)> ffu = bind([=](int x) { //也可以用auto进行推导类型,用auto推导的类型和function<void(int)>不是一个类型。因为auto推导的是仿函数类型,function<void(int)>是包装器对象类型。但是包装器类型可以包装auto推导出的类型
cout << "x: " << x << endl;
},placeholders::_1); //placeholders::_1是一个占位符
ffu(11);
}
int main() {
system("pause");
return 0;
}

浙公网安备 33010602011771号