- lamada表达式
- lamada表达式语法
- 捕捉列表说明
- 函数对象和lambda表达式
- 包装器
- function包装器
- bind
可调用对象的类型包括
- 仿函数
- 函数指针
- function包装器
- lamada表达式
lamada表达式其实就是一个匿名的函数,可以在参数传参的时候构造这个可调用对象
lamada表达式语法lambda表达式书写格式:
[capture-list] (parameters) mutable -> return-type { statement }
-
[capture-list]
捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用
-
(parameters)
参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
-
mutable
默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)
-
->returntype
返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
-
{statement}
函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
使用示例:
int main() {
//最简单的lamada表达式,表示什么都不做
[] {};
//实现一个add lamada表达式
int a = 3, b = 4;
auto add1 = [](int x, int y)->int {return x + y; };
std::cout << add1(a, b) << std::endl;
//通过捕捉列表实现add lamada表达式
auto add2 = [=]{return a + b; };
std::cout << add2() << std::endl;
//实现一个swap lamaba表达式
a = 1, b = 2;
auto swap1 = [](int& x, int& y) {
int tmp = x;
x = y;
y = tmp;
};
swap1(a, b);
//通过捕捉列表实现swap lamada表达式
auto swap2 = [&] {
int tmp = a;
a = b;
b = tmp;
};
swap2();
return 0;
}
通过上述例子可以看出,lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量。
捕捉列表说明
捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用
[var]//表示值传递方式捕捉变量var [=]//表示值传递方式捕获所有父作用域中的变量(包括this) [&var]//表示引用传递捕捉变量var [&]//表示引用传递捕捉所有父作用域中的变量(包括this)
注意:
- 父作用域指包含lambda函数的语句块
- 语法上捕捉列表可由多个捕捉项组成,并以逗号分割,比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
- 捕捉列表不允许变量重复传递,否则就会导致编译错误,比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
- 在块作用域以外的lambda函数捕捉列表必须为空
- 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错
- lambda表达式之间不能相互赋值,即使看起来类型相同
函数对象和lambda表达式
函数对象,又称为仿函数,即可以像函数一样使用的对象,就是在类中重载了operator()运算符的类对象
class Rate{
public:
Rate(double rate) : _rate(rate)
{}
double operator()(double money, int year){
return money * _rate * year;
}
private:
double _rate;
};
int main(){
// 函数对象
double rate = 0.49;
Rate r1(rate);
r1(10000, 2);
// lamber
auto r2 = [=](double monty, int year)->double {return monty * rate * year; };
r2(10000, 2);
return 0;
}
从使用方式上来看,函数对象与lambda表达式完全一样
函数对象将rate作为其成员变量,在定义对象时给出初始值即可,lambda表达式通过捕获列表可以直接将该变量捕获到
lambda底层是一个仿函数,无论是捕捉列表捕捉还是参数列表,都是通过函数传参实现的
每个lambda表达式都会生成唯一的uuid,所以仿函数的名称不一样,返回的类型也不一样
实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的
即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。
包装器 function包装器
function包装器也叫作适配器,C++中的function本质是一个类模板,也是一个包装器
上面提到了几种可调用对象,函数指针,仿函数,lambda表达式,这几种对象是完全不同的类型,而且使用起来不是很方便
- 函数指针类型太复杂,不方便使用和理解
- 仿函数类型是一个类名,没有指定调用参数和返回值,得去看operator()的实现才能看出来
- lambda表达式在语法层,看不到类型,只能在底层看到其类型,基本都是lambda_uuid
而且这几种对象是不同的类型,如果说这几个对象所作的事情完全相同,参数返回值也都相同,这时把对象作为模板参数传递,尽管对象执行的操作都相同,但是对象是不同的类型, 需要实例化出多个类,浪费了资源。
为了解决上面多种可调用对象的类型问题,就引入了function包装器,function包装器其实是对可调用对象的一层封装,让参数和返回值相同的不同类型的可调用对象封装成一个类型,使用起来更加方便
// 类模板原型如下 templatefunction; // undefined template class function ;
模板参数说明:
- Ret: 被调用函数的返回类型
- Args…:被调用函数的形参
使用包装器包装多个不同类型的可调用对象:
#includeint f(int a, int b){ return a + b; } struct Functor{ int operator() (int a, int b) { return a + b; } }; class Plus{ public: static int plusi(int a, int b){ return a + b; } double plusd(double a, double b){ return a + b; } }; int main(){ //包装函数名(函数指针) std::function func1 = f; std::cout << func1(1, 2) << std::endl; //包装函数对象(仿函数) std::function func2 = Functor(); std::cout << func2(1, 2) << std::endl; //包装lamber表达式 std::function func3 = [](const int a, const int b) {return a + b;}; std::cout << func3(1, 2) << std::endl; //包装类的静态成员函数 std::function func4 = Plus::plusi; std::cout << func4(1, 2) << std::endl; //包装类的非静态成员函数 std::function func5 = &Plus::plusd;//对于普通成员的包装一定要加上&,需要通过指针进行调用成员函数 std::cout << func5(Plus(), 1.1, 2.2) << std::endl;//传入类对象,通过对象进行调用 return 0; }
bind
bind是一个函数模板,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表,也就是调整可调用类型参数
class A{
public:
int printf2(int b = 1){
return b + 2;
}
};
int main(){
//把plus绑定成一个值+10
std::function f2 = std::bind([](int a, int b) { return a + b; }, 10, std::placeholders::_1);//表示传入的第一个参数
//绑定后的f2,只需要传入一个参数就可以调用,10也就是第二个参数已经被绑定好了
std::cout << f2(5) << std::endl;
//绑定固定的可调用对象
//在非静态成员函数中需要传入对象,我们就绑定该对象,每次调用时就不用传入了
std::function f3 = std::bind(&A::printf2, A(), std::placeholders::_1);
std::cout << f3(10) << std::endl;
//调整参数顺序
std::function f4 = std::bind([](int a, int b) { return a - b; }, std::placeholders::_2, std::placeholders::_1);
std::cout << f4(1, 2) << std::endl;
return 0;
}
bind是对包装器的可调用类型的进一步封装,可以根据自己的需要进行调整参数的数据及位置,绑定类对象能有优化成员函数的包装使用,更加符合使用习惯



