• C++11 lambda+包装器+可变参数模板


    lambda表达式

    (1).什么是lambda

    假设有这样一个类

    struct Goods
    {
    string _name;  // 名字
    double _price; // 价格
    int _evaluate; // 评价
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    现在要将商品分别按照名字,价格三种方式排序,为此我们必须写三个仿函数,但这个有点麻烦,因为一旦比较的逻辑不一样,就得多实现一个类,所以c++11出现了与局部深度绑定的lambda表达式,其本质上是一个匿名函数。
    eg:

    vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
    3 }, { "菠萝", 1.5, 4 } };
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
    return g1._price < g2._price; });
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
    return g1._price > g2._price; });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (2).lambda基本规则

    格式[capture-list](parameters) mutable->return-type-{statement}

    [capture-list] :捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文的变量供lambda函数使用
    (parameters):参数列表,与普通函数的参数列表一致,如果不需要传参,则可以与()一起省略
    mutable:默认情况下,lambda函数总是一个const函数,即捕捉过来的参数自动加了const,如果需要改变参数const’属性,需在()后加mutable
    ->returntype:返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略,返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
    {statement}:函数体。除可以使用参数外,还可以使用所有捕获到的变量
    小结:参数和返回值可以省略,捕捉列表和函数体不能省略。

    [capture-list] (parameters) mutable -> return-type { statement }
        	 捕捉列表  参数  返回值  函数体
    	最简单的lambda表达式,该表达式无意义
    	[] {};
    	auto Add1 = [](int x, int y)->double {return (x + y) / 3.0; };
    	cout << Add1(2, 3) << endl;//Add1其实就是一个局部匿名函数
    	int x = 3;
    
    	int a = 4, b = 5;
    	auto Add2 = [a, b,x] ()mutable//无参数可以直接省略
    	{
    		a = a + 3;
    		b = b + 3;//加了mutable只能在函数体内部改变值,但是出了作用域还是无法改变的
    		return a + b+x;
    	};
    	cout << Add2() << endl;
    	cout << a << " " << b << endl;//4 5
    	auto Swap = [](int& x, int& y)
    	{
    		int tmp = x;
    		x = y;
    		y = tmp;
    	};
    	Swap(a, b);
    	cout << a << " " << b << endl;//5  4
    	通过上述的例子可以看出,lambda表达式实际上可以理解成一个匿名函数
    	该函数无法直接调用,若想直接调用,可借助auto将其赋值给一个变量
    	
    	捕捉列表描述了上下文哪些数据可以被lambda使用,以及使用的方式是传值还是传引用
    
    	/*  [var]:表示值传递方式捕捉变量var
    		[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
    		[&var]:表示引用传递捕捉变量var
    		[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
    		[this]:表示值传递方式捕捉当前的this指针*/
    	
    	/*a.父作用域指包含lambda函数的语句块 即{}中的语句块
    		b.语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
    		比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
    		[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
    		c.捕捉列表不允许变量重复传递,否则就会导致编译错误。
    		比如:[=, a]: = 已经以值传递方式捕捉了所有变量,捕捉a重复*/
    	捕捉列表只能捕捉局部变量,不能捕捉全局变量
    	int c = 100, d = 200;
    	static int g = 20;
    	auto Addn = [c, d,20] {return c + d; };//此时20是静态变量 不能捕捉
    	cout << Addn() << endl;
    	auto Swap2 = [&c, &d](int x, int y) {c = 2; d = 3; return c + d + x + y; };//引用捕捉
    	 此时并不是传地址而是传引用
    	auto Swap2 = [&](int x, int y) {c = 2; d = 3; return c + d + x + y; };//引用捕捉与上述作用一样,捕捉变量都是引用传递
    	auto Swap2 = [=](int x, int y) {c = 2; d = 3; return c + d + x + y; };//报错,值传递,此时不加mutabl,捕捉的变量具有const
    	auto Swap2 = [=](int x, int y) mutable{c = 2; d = 3; return c + d + x + y; };
    	报错,值传递,此时不加mutabl,捕捉的变量具有const
    	但此时无法修改c 和d的值
    	int t = 500;
    	auto Swap2 = [=, &c](int x, int y) mutable {c = 2; d = 3; t = 1000; return c + d +t+ x + y; };
    	=表示捕捉的都是值传递,但是c是引用传递
    	此时在函数体内部用的是修改后的值
    	但因为是值传递,此时只能成功修改c的值,d和t在函数体中修改的都是其临时拷贝
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    总结: lambda就是定义了一个匿名的可调用的对象,一般定义在局部,特点是跟普通变量相比可以深度绑定局部的数据,比如说参数很多可以直接捕捉,不用传参了,有一些便捷性。
    所以lambda可以提到仿函数吗?
    不行,仿函数既可以传类型也可以传对象,但是lambda整体是一个对象,他只能用于那些传递对象的场景,eg:sort用lambda非常好,因为sort传的就是对象,但是在模板参数的时候lambda可能就不怎么好用。

    (3).lambda实现原理

    先补充一个lambda的规则:lambda表达式之间不能相互赋值,即使看起来类型相同
    eg:

    auto f1 = []{cout << "hello world" << endl; };
    auto f2 = []{cout << "hello world" << endl; };
    f1 = f2 会编译失败
    void(*PF)()
    但是可以将lambda表达式赋值给相同类型的指针
    PF = f1;
    但是不建议这样做
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    UUID 是 通用唯一识别码(Universally Unique Identifier)的缩写,是一种软件建构的标准,亦为开放软件基金会组织在分布式计算环境领域的一部分。其目的,是让分布式系统中的所有元素,都能有唯一的辨识信息,而不需要通过中央控制端来做辨识信息的指定。如此一来,每个人都可以创建不与其它人冲突的UUID。在这样的情况下,就不需考虑数据库创建时的名称重复问题。最广泛应用的UUID,是微软公司的全局唯一标识符(GUID),而其他重要的应用,则有Linux ext2/ext3文件系统、LUKS加密分区、GNOME、KDE、Mac OS X等等。另外我们也可以在e2fsprogs包中的UUID库找到实现。

    所以上述的即使f1与f2表面上看上去函数是一样的,但是在其所生成的仿函数名称确实完全不一样的,所以不能赋值。

    包装器

    function包装器也叫做适配器。c++中的function本质是一个类模板,也是一个包装器,为什么需要它?
    eg:ret = func(x)
    上面的func可能是什么?
    func可能是函数名?函数指针?函数对象(仿函数对象)?也有可能是lambda表达式对象?这些都是可调用的类型,所以如此丰富的类型可能也会导致模板效率降低。
    eg:

    template<class F, class T>
    T useF(F f, T x)
    {
    static int count = 0;
    cout << "count:" << ++count << endl;
    cout << "count:" << &count << endl;
    return f(x);
    }
    double f(double i)
    {
    return i / 2;
    }
    struct Functor
    {
    double operator()(double d)
    {
    return d / 3;
    }
    };
    int main()
    {
    通过上面的程序验证,我们会发现useF函数模板实例化了三份。
    包装器可以很好的解决上面的问题
    // 函数名
    cout << useF(f, 11.11) << endl;
    // 函数对象
    cout << useF(Functor(), 11.11) << endl;
    // lamber表达式
    cout << useF([](double d)->double{ return d/4; }, 11.11) << endl;
    return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    在这里插入图片描述模板参数说明:
    Ret: 被调用函数的返回类型
    Args…:被调用函数的形参
    使用方法如下:

    	function<int(int, int)>func1 = f;
    	cout << func1(1, 2) << endl;
    	function<int(int, int)>func2 = Functor();
    	cout << func2(2, 4) << endl;
    	//静态成员函数的包装跟其他的包装一样
    	function<int(int, int)>func3 = &Plus::plusi;//取地址符号可加可不加,但最好加上
    	cout << func2(5, 4) << endl;
    	//非静态成员函数有点区别
    	function<double(Plus, double, double)>func4 = &Plus::plusd;
    	cout << func4(Plus(), 5.2, 3.5) << endl;
    	//Plus()匿名对象,因为非静态成员函数要用this指针去调用
    	//静态成员函数不用,上述需要靠匿名对象调用函数
    	function<int(int, int)>func5 = [](int a, int b) {return a + b; };
    	cout << func5(100, 200) << endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    可以看到包装器统一的特点是统一类型
    c++中常用命令对应函数

    map<string,function>
    命令对应函数
    eg
    cout << opFuncMap["普通函数指针"](1, 2) << endl;
    	cout << opFuncMap["函数对象"](1, 2) << endl;
    	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述
    所以可以像下面这样

    int f(int a, int b)
    {
    	return a - b;
    }
    
    struct Functor
    {
    public:
    	int operator() (int a, int b)
    	{
    		return a + b;
    	}
    };
    
    
    class Plus
    {
    public:
    	Plus(int x = 2)
    		:_x(x)
    	{}
    
    	int plusi(int a, int b)
    	{
    		return (a + b) * _x;
    	}
    private:
    	int _x;
    };
    void Teste()
    {
    	map<string, std::function<int(int, int)>> opFuncMap =
    	{
    		{ "普通函数指针", f },
    		{ "函数对象", Functor() },
    		{ "成员函数指针", std::bind(&Plus::plusi, Plus(10), placeholders::_1, placeholders::_2) }
    	};
    
    	cout << opFuncMap["普通函数指针"](1, 2) << endl;
    	cout << opFuncMap["函数对象"](1, 2) << endl;
    	cout << opFuncMap["成员函数指针"](1, 2) << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    可变参数模板

    c++98/03,类模板和函数模板中只能包含固定数量的模板参数,c++11新特性可以接受可变参数的函数模板和类模板,使用起来稍微一点技巧。

    //Argss是一个模板参数包,args是一个函数形参参数包
    //声明一个参数包Args...args,这个参数包中包含0到任意个模板参数
    template<class ...Args>
    void ShowList1(Args... args){}
    
    • 1
    • 2
    • 3
    • 4

    上面的参数args前面有省略号,所以它就是一个可变模板参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模板参数,我们无法直接获取参数包args中的每个参数,只能通过展开参数包的方式来获取参数包重点每个参数,这是使用可变模板参数的一个主要特点,也是最大的难点,下面我用一张图来演示如果展开。
    在这里插入图片描述
    可变参数在STL中的运用
    在这里插入图片描述
    首先看到emplace_back系列的接口,支持模板的可变参数,且是万能引用。

    list<pair<string, int>>li;
    	li.push_back(make_pair("zjt", 20));
    	li.emplace_back("nidie", 18);
    
    
    • 1
    • 2
    • 3
    • 4

    emplace_back支持可变参数,拿到pair对象参数后自己去创建对象,
    在这看到除了用法上面,和push_back没有太大区别。
    在这里插入图片描述

  • 相关阅读:
    分布式系统架构理论与组件
    计算机毕业设计之java+javaweb的烯烃厂压力管道管理平台
    基于 Python 的地理空间绘图(附源码)
    docker 部署 若依 Ruoyi springboot+vue分离版
    精心整理高频js手写题请查收
    RabbitMQ基础组件笔记
    二叉树的遍历
    spfa算法_C++详解
    Idea 编译SpringBoot项目Kotlin报错/Idea重新编译
    【kerberos】使用 curl 访问受 Kerberos HTTP SPNEGO 保护的 URL
  • 原文地址:https://blog.csdn.net/cxy_zjt/article/details/127836498