栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

【C++】lamada表达式和包装器

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【C++】lamada表达式和包装器

文章目录
    • lamada表达式
      • lamada表达式语法
      • 捕捉列表说明
      • 函数对象和lambda表达式
    • 包装器
      • function包装器
      • bind

可调用对象的类型包括

  • 仿函数
  • 函数指针
  • function包装器
  • lamada表达式
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包装器其实是对可调用对象的一层封装,让参数和返回值相同的不同类型的可调用对象封装成一个类型,使用起来更加方便

// 类模板原型如下
template  function; // undefined
template 
class function; 

模板参数说明:

  • Ret: 被调用函数的返回类型
  • Args…:被调用函数的形参

使用包装器包装多个不同类型的可调用对象:

#include 
int 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是对包装器的可调用类型的进一步封装,可以根据自己的需要进行调整参数的数据及位置,绑定类对象能有优化成员函数的包装使用,更加符合使用习惯

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1004747.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号