C++ 11---lambda表达式与包装器

2024-08-22 03:28
文章标签 c++ 表达式 lambda 包装

本文主要是介绍C++ 11---lambda表达式与包装器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

lambda表达式

lambda语法

capture-list捕获列表

        1.[var]:表示值传递方式捕捉变量var

        2.[=]:表示值传递方式捕获所有父作用域中的变量

        3. [&var]:表示引用传递捕捉变量var

        4.[&]:表示引用传递捕捉所有父作用域中的变量

        5.混合使用

        6.注意事项

lambda与仿函数(函数对象)

lambda优点

function包装器


lambda表达式

        Lambda表达式是一种在被调用的位置或作为参数传递给函数的位置定义匿名函数对象的简便方法。如下情景我们要对一些数进行排序,就可以调用sort函数,根据要求不同传递参数不同。如下代码。

struct SortDown
{bool operator()(const int& x,const int &y ){return x > y;}
};int main()
{vector<int> a = { 1,5,2,62,61,2,22,36,6,10 };//默认升序sort(a.begin(), a.end());for (int e : a)cout << e << " ";cout << endl;//降序sort(a.begin(), a.end(), SortDown());for (int e : a)cout << e << " ";cout << endl;return 0;
}

        运行结果如下图

        上述代码我们也可以实现对不同要求的排序,但每次每次实现要求时要单独写一个类,相较而言是比较复杂的,就可以用lambda简化如下代码。

int main()
{vector<int> a = { 1,5,2,62,61,2,22,36,6,10 };//默认升序sort(a.begin(), a.end(), [](const int& x, const int& y)->bool {return x < y; });for (int e : a)cout << e << " ";cout << endl;//降序sort(a.begin(), a.end(), [](const int& x, const int& y)->bool{return x > y; });for (int e : a)cout << e << " ";cout << endl;return 0;
}

        运行结果如下。

        相较于使用类,使用lambda表达式更加的简洁些,可以直接看出sort的排序不用向上查找类实现。

lambda语法

        Lambda表达式可以看为是匿名函数对象,语法如下。

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

        首先 return-type是返回值类型, statement是函数体,parameters是函数参数,这三个与普通函数基本一样,按照普通函数用法去写即可。

        其中有些特殊规定如下。

        (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以 连同()一起省略。

        ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回 值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推 导。

        以上两处在特定的情况下都可以省略,所以可以说lambda表达式基本结构如下。

[]   { }

        捕获列表加上函数体。

capture-list捕获列表

        1.[var]:表示值传递方式捕捉变量var

        按照值传递方式捕捉变量var,相当于拷贝一份var变量,在函数体内是不支持修改的,具有const属性,发生修改操作就会报错。如下图。

        如果想要var变量可以修改就要加上mutable关键字。如下图就可以修改了。

        接下来通过调试观察a,b值变化。

        由于lambda表达式类型是匿名的,我们就可以用auto接受,让编译器自己推导。如何使用这个匿名函数对象,我们就可以直接调用()就可以了,把他视为一个对象,在对象中将()进行了重载。

        如下代码。

int main()
{int a = 0, b = 2;auto fun=[a, b]()mutable{a = 1;b = 1;};fun();return 0;
}

        

        单纯只写函数名是不可以修改外部变量的!!

       2.[=]:表示值传递方式捕获所有父作用域中的变量

        如果只在捕获列表写一个=,说明所有要使用的外部变量都按照值传递的方式,在函数体内部修改不影响外面。如下代码。

        

int main()
{int a = 0, b = 2;cout << a <<" " << b << endl;auto fun=[=]()mutable{a = 1;b = 1;};fun();cout << a << " " << b << endl;return 0;
}

        运行结果如下,在函数内修改不影响外面。

        如果我们想要影响函数外面就可以传引用的方式。

       3. [&var]:表示引用传递捕捉变量var

        如上代码修改后结果如下。

int main()
{int a = 0, b = 2;cout << a <<" " << b << endl;auto fun=[&a,&b]()mutable{a = 1;b = 1;};fun();cout << a << " " << b << endl;return 0;
}

        这里的&a表示按引用传递为不是取地址,是对&运算符的再次利用赋予新的含义。

        4.[&]:表示引用传递捕捉所有父作用域中的变量

        捕获列表如果只写一个&,表示所有使用的变量都以引用的方式传递。

int main()
{int a = 0, b = 2;cout << a <<" " << b << endl;auto fun=[&]()mutable{a = 1;b = 1;};fun();cout << a << " " << b << endl;return 0;
}

        引用之后修改的就与外层变量是同一个。

        5.混合使用

        除了上述使用外,我们可能会有多个变量要引用,一个要值传递就可以采用如下方式。

int main()
{int a = 0, b = 2;int c = 3;cout << a <<" " << b <<" " << c << endl;auto fun=[&,c]()mutable{a = 1;b = 1;c = 1;};fun();cout << a << " " << b << " " << c << endl;return 0;
}

        c按照值传递,a,b按照引用传递。这样就可以避免写多个引用或者值传递。

        同理也可以按照如下使用,=加引用传递

int main()
{int a = 0, b = 2;int c = 3;cout << a <<" " << b <<" " << c << endl;auto fun=[=,&c]()mutable{a = 1;b = 1;c = 1;};fun();cout << a << " " << b << " " << c << endl;return 0;
}

        6.注意事项

        1.不可以重复捕获一个变量。

        c表示值传递,=又把变量c值传递,编译器就会报错。但可以将c改为&c,按引用传递,那么就不会再对c进行引用传递了。

        2.lambda表达式只可以捕获当前父作用域变量。

        父作用域指包含lambda函数的语句块。通常被{}包围。

        3.lambda表达式内可以使用全局变量。

        如下代码

int c = 3;
void test()
{int a = 0, b = 2;cout << a << " " << b << " " << c << endl;auto fun = [=]()mutable {a = 1;b = 1;c = 1;};fun();cout << a << " " << b << " " << c << endl;
}int main()
{test();return 0;
}

        运行结果如下。

lambda与仿函数(函数对象)

        lambda表达式的结果就是匿名函数对象,我们无法直接的得到他的类型,只能通过编译器推导,即使用auto。

        运行如下代码。我们可以得到如下结果。

struct SortDown
{bool operator()(const int& x,const int &y ){return x > y;}
};void test()
{vector<int> a = { 1,5,2,62,61,2,22,36,6,10 };auto fun = [](const int& x, const int& y) {return x > y;};//lambda表达式sort(a.begin(), a.end(), fun);for (int e : a)cout << e << " ";cout << endl;//仿函数sort(a.begin(), a.end(), SortDown());for (int e : a)cout << e << " ";cout << endl;
}
int main()
{test();return 0;
}

        我们可以通过仿函数得到与lambda一样的效果。

        当然也可以让他们两个更像一些。此时都是匿名对象。

void test()
{vector<int> a = { 1,5,2,62,61,2,22,36,6,10 };//lambda表达式sort(a.begin(), a.end(), [](const int& x, const int& y) {return x > y;});for (int e : a)cout << e << " ";cout << endl;//仿函数sort(a.begin(), a.end(), SortDown());for (int e : a)cout << e << " ";cout << endl;
}

        我们可以看些底层汇编加深他们之间的关系。运行如下代码

struct SortDown
{bool operator()(const int& x,const int &y ){return x > y;}
};int main()
{auto fun = [](const int& x, const int& y) {return x > y;};//lambda表达式fun(1, 2);SortDown sd;//仿函数sd(1, 2);return 0;
}

        因此可以说lambda表达式就是经过特殊处理的函数对象,对象名在运行时由编译器决定,可以使用auto推出,函数使用与仿函数一样,都是调用重载的()函数。

lambda优点

        相较于类而言,lambda表达式更加的简洁,明了。可以在可以在传递参数的位置让程序员明白意图,不必向上寻找类,总而言之面对较简单的逻辑时,就可以考虑用lambda表达式替换类对象简化代码。

包装器

为什么要有包装器(适配器)

        在使用包装器之前,我们要包含头文件<functional>。

        通过上述lambda表达式的学习,我们知道一个名字加()可以形成具有函数作用的语句有三种。如下代码

        1.函数名,这也是我们最早学的,用的最多的。

int cmp(const int& x, const int& y)
{return x < y;
}//调用语句
cout<<cmp(1, 2);

        2.类重载()

struct up
{bool operator()(const int& x, const int& y){return x < y;}
};
up t;
//调用语句
cout<<t(1, 2);

        3.lambda表达式

auto fun = [](const int& x, const int& y) {return x < y;};//调用语句
cout<<fun(1, 2);

        假如把上述3种的调用写在一起如下。

    //调用语句
    cout<<cmp(1, 2);
    cout<<t(1, 2);
    cout<<fun(1, 2);

        光看名称很难做出判断,这个名字究竟是类名?函数名?此时我们在设计深一层的接口时就十分难办,如排序接口,第三个参数写出什么?指针么?传对象不可以。对象么?指针又不可以。在标准库中sort第三个参数就是开放的,可以传类或者指针。如下图。

        于是就引入了适配器的概念,我们不关心你是通过函数还是对象实现的,只关注你要什么参数,返回值是什么,统一模板。如下代码。

int main()
{up t;function<bool(int, int)> f1 = cmp;function<bool(int, int)> f2 = t;function<bool(int, int)> f3 = fun;//调用语句cout<<f1(1, 2)<<endl;cout<<f2(1, 2)<<endl;cout<<f3(1, 2)<<endl;return 0;
}

        这里的类型不要求完全相同,可以走隐式类型转化,当然完全相同最好。

        经过上述适配器后,我们就不需要关注是怎么是实现的,只关注如何使用,运行结果如下。

function

        function实际上是一个模板类,可以根据提供的参数,实例化不同的类。function - C++ Reference (cplusplus.com)

        底层构造函数根据类型不同进行不同的初始化,这里就不过多赘述了,感兴趣可以点击上面网站查询。

        他的语法如下

function<返回值类型(参数列表)> 变量名 = 函数名/类名/lambda表达式;

        通过function也可以解决类模板效率底下的问题。如下代码。

template<class F, class T>
T fun(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()
{// 函数名cout << fun(f, 11.11) << endl;// 函数对象cout << fun(Functor(), 11.11) << endl;// lamber表达式cout << fun([](double d)->double { return d / 4; }, 11.11) << endl;return 0;
}

        程序在运行时应为参数不同,会实例化出三份模板,但如果将代码改为如下就可以提高效率

template<class F, class T>
T fun(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()
{// 函数名(函数指针)std::function<double(double)> func1 = f;cout << fun(func1, 11.11) << endl;// 函数对象std::function<double(double)> func2 = Functor();cout << fun(func2, 11.11) << endl;return 0;
}

        fun1与fun2的类型一样,最终只会实例化一份代码,提高效率。

bind

        std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可 调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而 言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个参数的新函数。同时,使用std::bind函数还可以实现参数顺 序调整等操作。

        模板如下,bind - C++ Reference (cplusplus.com)

       如果为函数语法如下。返回值可以用auto接收,也可以用function接受。

bind(函数名,参数列表)

参数列表中_1 表示调用函数时传递的第一个参数,_2表示调用函数时传递的第二个参数等,他们存在类域placeholders中。

其中参数列表可以掺入常量,表示那个参数绑定死某个值,具体用法看下述代码。

        如果为类对象语法如下。返回值可以用auto接收,也可以用function接受。

bind(函数名,类对象,参数列表)

参数列表中_1 表示调用函数时传递的第一个参数,_2表示调用函数时传递的第二个参数等,他们存在类域placeholders中。

其中参数列表可以掺入常量,表示那个参数绑定死某个值,具体用法看下述代码。

        运行如下代码

int Add(int a, int b)
{return a + b;
}
class Sub
{
public:int sub(int a, int b){return a - b;}
};
int main()
{//表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定std::function<int(int, int)> func1 = std::bind(Add, placeholders::_1,placeholders::_2);//Add前两个参数绑死1 2auto  func2 = std::bind(Add, 1, 2);cout << func1(1, 2) << endl;cout << func2() << endl;Sub s;// 绑定成员函数std::function<int(int, int)> func3 = std::bind(&Sub::sub, s,placeholders::_1, placeholders::_2);// 参数调换顺序std::function<int(int, int)> func4 = std::bind(&Sub::sub, s,placeholders::_2, placeholders::_1);cout << func3(1, 2) << endl;cout << func4(1, 2) << endl;return 0;
}

        结果如下图。

        func3的顺序没有改变如下图。

        func4的顺序有改变如下图。

        也可如下写。结果为4.

//Add第一个个参数绑死2
auto  func3 = std::bind(Add,2, placeholders::_1);
cout << func3(2) << endl;

           包装器进一步封装了代码,提供了简洁的使用方式。

这篇关于C++ 11---lambda表达式与包装器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1095101

相关文章

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

Java中八大包装类举例详解(通俗易懂)

《Java中八大包装类举例详解(通俗易懂)》:本文主要介绍Java中的包装类,包括它们的作用、特点、用途以及如何进行装箱和拆箱,包装类还提供了许多实用方法,如转换、获取基本类型值、比较和类型检测,... 目录一、包装类(Wrapper Class)1、简要介绍2、包装类特点3、包装类用途二、装箱和拆箱1、装

c++中std::placeholders的使用方法

《c++中std::placeholders的使用方法》std::placeholders是C++标准库中的一个工具,用于在函数对象绑定时创建占位符,本文就来详细的介绍一下,具有一定的参考价值,感兴... 目录1. 基本概念2. 使用场景3. 示例示例 1:部分参数绑定示例 2:参数重排序4. 注意事项5.

使用C++将处理后的信号保存为PNG和TIFF格式

《使用C++将处理后的信号保存为PNG和TIFF格式》在信号处理领域,我们常常需要将处理结果以图像的形式保存下来,方便后续分析和展示,C++提供了多种库来处理图像数据,本文将介绍如何使用stb_ima... 目录1. PNG格式保存使用stb_imagephp_write库1.1 安装和包含库1.2 代码解

C++实现封装的顺序表的操作与实践

《C++实现封装的顺序表的操作与实践》在程序设计中,顺序表是一种常见的线性数据结构,通常用于存储具有固定顺序的元素,与链表不同,顺序表中的元素是连续存储的,因此访问速度较快,但插入和删除操作的效率可能... 目录一、顺序表的基本概念二、顺序表类的设计1. 顺序表类的成员变量2. 构造函数和析构函数三、顺序表

使用C++实现单链表的操作与实践

《使用C++实现单链表的操作与实践》在程序设计中,链表是一种常见的数据结构,特别是在动态数据管理、频繁插入和删除元素的场景中,链表相比于数组,具有更高的灵活性和高效性,尤其是在需要频繁修改数据结构的应... 目录一、单链表的基本概念二、单链表类的设计1. 节点的定义2. 链表的类定义三、单链表的操作实现四、

使用C/C++调用libcurl调试消息的方式

《使用C/C++调用libcurl调试消息的方式》在使用C/C++调用libcurl进行HTTP请求时,有时我们需要查看请求的/应答消息的内容(包括请求头和请求体)以方便调试,libcurl提供了多种... 目录1. libcurl 调试工具简介2. 输出请求消息使用 CURLOPT_VERBOSE使用 C

C++实现获取本机MAC地址与IP地址

《C++实现获取本机MAC地址与IP地址》这篇文章主要为大家详细介绍了C++实现获取本机MAC地址与IP地址的两种方式,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 实际工作中,项目上常常需要获取本机的IP地址和MAC地址,在此使用两种方案获取1.MFC中获取IP和MAC地址获取

C/C++通过IP获取局域网网卡MAC地址

《C/C++通过IP获取局域网网卡MAC地址》这篇文章主要为大家详细介绍了C++如何通过Win32API函数SendARP从IP地址获取局域网内网卡的MAC地址,感兴趣的小伙伴可以跟随小编一起学习一下... C/C++通过IP获取局域网网卡MAC地址通过win32 SendARP获取MAC地址代码#i