• C++11 lambda表达式 可调用对象包装器function bind


    目录

    lambda表达式

    lambda表达式是什么,应用场景

    场景引入:

    lambda表达式是什么?

    lambda表达式的语法

    语法:

    捕捉列表的说明:

    注意:

    lambda表达式的本质

    包装器function

    function 概念说明

    function 使用说明

    function可调用对象包装器的作用:

     包装器bind

    bind 概念说明 && 使用说明

    bind 可调用对象包装器使用实例:


    lambda表达式

    lambda表达式是什么,应用场景

    场景引入:

    1. void test1()
    2. {
    3. int array[] = {4, 1, 8, 5, 3, 7, 0, 9, 2, 6};
    4. // 默认按照小于比较,排出来结果是升序
    5. std::sort(array, array + sizeof(array) / sizeof(array[0]));
    6. // 如果需要降序,需要改变元素的比较规则
    7. std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
    8. }
    9. struct Goods
    10. {
    11. string _name; // 名字
    12. double _price; // 价格
    13. int _evaluate; // 评价
    14. Goods(const char* str, double price, int evaluate)
    15. :_name(str)
    16. , _price(price)
    17. , _evaluate(evaluate)
    18. {}
    19. };

    如上,对于整型数组使用sort库函数排序时,若想要改变比较规则和排序规则,引入function库函数的greater仿函数类即可。

    但是对于下方的Goods这样的自定义类型对象来说,如果想要按照某个数据成员进行排序,可是它又没有重载operator<(sort默认调用对象的operator<进行大小比较),我们此时就必须使用函数对象,仿函数来传给sort的第三个参数。

    对于一个算法sort还要再定义一个仿函数类,是很麻烦的,如果是对于多个类的属性都要进行排序,就要定义多个仿函数类,很不方便,代码可读性差且给仿函数类命名也是个问题。

    lambda表达式就是为了解决上方场景。

    1. void test2()
    2. {
    3. vector v = {{"1",1.1,1}, {"2",2.2,2},{"3",3.3,3}};
    4. sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){return g1._name < g2._name;});
    5. sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){return g1._name > g2._name;});
    6. sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){return g1._price < g2._price;});
    7. sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){return g1._evaluate < g2._evaluate;});
    8. }

    lambda表达式是什么?

    lambda表达式在使用上就是一个函数(匿名函数,上层看没有名字),本质上是一个仿函数,函数对象(也就是某个类实现了operator(),这个类的实例化对象)。

    恰当使用lambda表达式可以让代码变得简洁,并且可以提高代码的可读性。

    lambda表达式的语法

    语法:

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

    1. [capture-list] : 捕捉列表,该列表总是出现在lambda表达式的开始位置,编译器根据[]来判断接下来的代码是否为lambda表达式,捕捉列表能够捕捉上下文中的变量供lambda表达式使用。

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

    3. mutable:见下方lambda表达式本质说明。
    4. ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
    5. {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

    综上,其实lambda表达式和普通函数非常相似:有参数列表,有返回值,有函数体,就是没有函数名。还多了一个捕捉列表。

    捕捉列表的说明:

    捕捉列表描述了上下文中哪些数据可以被lambda使用,以及使用的方式是传值还是传引用。

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

    [=]:表示值传递方式捕获所有父作用域中的变量(包括this)

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

    [&]:表示引用传递捕捉所有父作用域中的变量(包括this)

    [this]:表示值传递方式捕捉当前的this指针

    注意:

    1. 这里的父作用域:指的是lambda表达式所在的函数栈帧(所在函数的块作用域)。如果在一个if里面,父作用域还是整个函数栈帧。不包括全局作用域和其他作用域!

    2. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
    比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
    [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量

    3. 捕捉列表不允许变量重复传递,否则就会导致编译错误。(这里理解了lambda表达式的底层原理之后会更好理解)

    4. lambda表达式之间不能相互赋值,但是可以拷贝构造一个新的。auto f1 = []{}; auto f2(f1);(和底层原理有关,不重要)

    lambda表达式的本质

    lambda表达式的本质:一个未命名类的未命名函数对象,也就是编译器将lambda表达式实现为一个类,这个类重载operator();operator()的参数列表,返回值,函数体和lambda的一样。捕捉列表里的变量将根据某些规则实现为类的数据成员。

    6个月前看C++Primer时,写过一篇lambda的本质博客,见下方链接。
    http://t.csdn.cn/KDwb6

    默认情况下lambda表达式不可以改变它值捕获的变量。因为重载函数调用运算符的函数默认情况下会被定义为const。而语法中的参数列表后的mutable就是改变operator()的const属性,使其可以改变值捕获的变量(值捕获变量会存为类的成员变量(引用捕获不会),const成员函数不能改变成员变量,mutable取消operator()的const属性)

    包装器function

    function 概念说明

    function是一种函数包装器(可调用对象包装器),也叫作适配器。可以对可调用对象进行包装。(目前为止,可调用对象包括,函数指针(函数名),函数对象(仿函数),lambda表达式。)

    C++中function本质是一个类模板(类模板的模板实参传类型,函数模板实参可根据传递实参对象类型推断出)

    1. // std::function在头文件
    2. template <class Ret, class... Args>
    3. class function<Ret(Args...)>;

    模板参数说明:

    Ret: 被包装的可调用对象的返回类型
    Args…:被包装的可调用对象的形参列表

    (这里用到了C++11的可变参数模板,也就是Args可以接收任意数量的模板实参,组合成一个模板参数包,因为被包装的可调用对象的参数列表个数和类型都不定。)

    function 使用说明

    为什么要使用function将一个可调用对象包装起来呢?完全可以直接调用可调用对象啊。

    是因为function可以将函数指针,函数对象,lambda表达式不同类型的可调用对象包装为统一的function类型。只要可调用对象的返回值,和参数列表相同即可!

    1. // 函数,函数指针
    2. int add(int x, int y)
    3. {
    4. return x + y;
    5. }
    6. // 仿函数,函数对象
    7. class Sub
    8. {
    9. public:
    10. int operator()(int x, int y) {
    11. return x - y;
    12. }
    13. };
    14. // 类的成员函数
    15. class F
    16. {
    17. public:
    18. static int mul(int x, int y) {
    19. return x * y;
    20. }
    21. int divi(int x, int y) {
    22. return x / y;
    23. }
    24. double divd(double x, double y) {
    25. return x / y;
    26. }
    27. int Mul(int x, int y, int rate)
    28. {
    29. return x * y * rate;
    30. }
    31. };
    32. void mytest1()
    33. {
    34. // 函数指针
    35. function<int(int, int)> funcAdd = add;
    36. // 仿函数,函数对象
    37. function<int(int, int)> funcSub = Sub();
    38. // 类的静态成员函数
    39. function<int(int, int)> funcMul = F::mul;
    40. // 类的非静态成员函数
    41. // 类的普通成员函数的第一个参数是一个隐藏的this指针!!!!!!
    42. // F为this指针的类型
    43. function<int(F, int, int)> funcDivi = &F::divi;
    44. function<double(F, double, double)> funcDivd = &F::divd;
    45. function<int(F, int, int, int)> funcMul_ = &F::Mul;
    46. // lambda表达式,本质就是一个未命名类的未命名函数对象
    47. function<int(int, int)> funcLam = [](int x, int y)->int{ return x % y; };
    48. int x = 10;
    49. int y = 5;
    50. cout << funcAdd(x, y) << endl;
    51. cout << funcSub(x, y) << endl;
    52. cout << funcMul(x, y) << endl;
    53. F f;
    54. cout << funcDivi(f, x, y) << endl;
    55. cout << funcDivd(F(), x, y) << endl;
    56. cout << funcMul_(F(), x, y, 1) <
    57. cout << funcLam(x, y) << endl;
    58. }

    对于参数列表和返回值相同的任意可调用对象,都可以包装为统一的function类型。无论它是函数指针,函数对象,还是lambda表达式。

    注意,对于类的非静态成员函数的包装,需要使用&运算符,且类的非静态成员函数的第一个参数为隐藏的this指针,故function的模板实参传递时,可调用对象的参数列表的第一个实参为类类型。

    在使用封装了类的非静态成员函数的function类型可调用对象时,第一个实参要传递类类型对象,也就是上方的F() 或 f,对应this指针类型。

    function可调用对象包装器的作用:

    1. function可以用于map> 这样的场景,将不同可调用对象包装起来,放在map中,用string索引,调用对应的可调用对象。
    因为可调用对象类型可能不同,且即使都是函数指针,函数指针也没有function便捷,可读性高。   具体使用场景需要后期不断学习。

    2.

    1. template < class F, class T >
    2. T useFunc(F func, T x)
    3. {
    4. static int count = 0;
    5. cout << ++count << endl;
    6. cout << &count << endl;
    7. return func(x);
    8. }
    9. double func1(double d) {
    10. return d;
    11. }
    12. class func2
    13. {
    14. public:
    15. double operator()(double d) {
    16. return d;
    17. }
    18. };
    19. void test1()
    20. {
    21. useFunc(func1, 10.1);
    22. useFunc(func2(), 10.2);
    23. useFunc([](double d){return d;}, 10.3);
    24. }
    25. void test2()
    26. {
    27. function<double(double)> f1 = func1;
    28. function<double(double)> f2 = func2();
    29. function<double(double)> f3 = [](double d){return d;};
    30. useFunc(f1, 10.1);
    31. useFunc(f2, 10.1);
    32. useFunc(f3, 10.1);
    33. }

    如上有一个函数模板,第一个模板参数对应一个可调用对象类型,若不对不同类型可调用对象进行包装,对于返回值,参数列表相同的函数指针,仿函数,lambda表达式。这个函数模板将实例化出很多份。而如果将不同类型可调用对象用function包装起来,则这个函数模板将只针对function类型实例化出一份。
    可根据函数模板内的静态变量的地址以及值变化判断。

    test1

    test2 

     

     包装器bind

    bind 概念说明 && 使用说明

    bind也是一种函数包装器(可调用对象),也叫做适配器。

    它可以接受一个可调用对象,返回一个新的可调用对象来“适应”原对象的参数列表

    C++中的bind本质是一个函数模板。(函数模板,使用时直接传实参对象即可,模板实参类型可根据对象类型推断出,比如sort...)

    bind函数模板原型:

    1. template <class Fn, class... Args>
    2. /* unspecified */ bind(Fn&& fn, Args&&... args);
    3. template <class Ret, class Fn, class... Args>
    4. /* unspecified */ bind(Fn&& fn, Args&&... args);

    fn :要包装的可调用对象 (此处为万能引用)

    args :传递给被包装的可调用对象的实参列表(由实际值(实际对象)和占位符组成,占位符见下文)

    调用bind的一般形式:auto newCallable = bind(callable,arg_list);

    其中callable是被包装的可调用对象,newCallable是生成的新的可调用对象,arg_list是逗号分割的参数列表,对应callable的参数列表。

    当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。

    arg_list中的参数,可能是实际值(10,20),或者实际对象(a, b, v)。
    也可能是形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable接收的实参,它们占据了传递给newCallable的实参列表的“位置”。数值n表示生成的可调用对象中实参的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。

    除了用auto接收包装后的可调用对象,也可以用function类型指明返回值和形参类型后接收包装后的可调用对象。

    bind 可调用对象包装器使用实例:

    1. 给可调用对象的某个实参设置缺省值,改变参数个数。

    1. // 类的成员函数
    2. class F
    3. {
    4. public:
    5. static int mul(int x, int y) {
    6. return x * y;
    7. }
    8. int divi(int x, int y) {
    9. return x / y;
    10. }
    11. double divd(double x, double y) {
    12. return x / y;
    13. }
    14. int Mul(int x, int y, int rate)
    15. {
    16. return x * y * rate;
    17. }
    18. };
    19. void mytest1()
    20. {
    21. // bind
    22. using namespace placeholders;
    23. function<int(F, int, int)> func1 = bind(&F::divi, _1, _2, _3);
    24. function<int(int, int)> func2 = bind(&F::divi, F(), _1, _2);
    25. func1(F(), 10, 2);
    26. func2(10,2);
    27. function<int(F, int, int, int)> func3 = bind(&F::Mul, _1, _2, _3, _4);
    28. function<int(int, int, int)> func4 = bind(&F::Mul, F(), _1, _2, _3);
    29. function<int(int, int)> func5 = bind(&F::Mul, F(), _1, _2, 10);
    30. func3(F(), 2,5,10);
    31. func4(2,5,10);
    32. func5(2,5);
    33. }

    说明:

    占位符定义在命名空间placeholders中,_1表示传递给新的可调用对象的第一个参数,_2... 调用新的可调用对象时,如果在bind内将要包装的可调用对象的某个参数绑定为某个具体值,则效果就是设定形参缺省值。

    这样一来 全局的int add(int, int); 和成员函数的 int sub(int, int); 本身它们用function包装后的参数列表不同,但是,就可以通过bind之后,都可以包装为一个function类型,因为我们可以把成员函数的第一个隐藏this参数固定为某固定值。如上所示。

    2. 通过占位符改变可调用对象的参数顺序

    1. void show(int i1, int i2)
    2. {
    3. cout << i1 << endl;
    4. cout << i2 << endl;
    5. }
    6. void mytest2()
    7. {
    8. int a = 10;
    9. int b = 20;
    10. show(a,b);
    11. function<void(int,int)> func1 = bind(show, placeholders::_2, placeholders::_1);
    12. func1(a,b);
    13. auto func2 = [](const string& s1, const string& s2)->void{cout << s1 << endl; cout << s2 << endl;};
    14. func2("haha", "hehe");
    15. function<void(string,string)> func3 = bind(func2, placeholders::_2, placeholders::_1);
    16. func3("haha", "hehe");
    17. }
    18. int main()
    19. {
    20. mytest2();
    21. return 0;
    22. }

    妈的有点烦

  • 相关阅读:
    花了三个月,终于把个人网站写完了
    P02014012 王文艺 (信息论课程作业)
    mysql中如何使用乐观锁和悲观锁
    Spring Boot常用注解@ConfigurationProperties、松散绑定、数据校验
    家庭宽带相关知识及工具
    怎么把视频转换成音频
    CSS 文本超出省略
    C#/VB.NET 将PDF转为Excel
    日期类练习题
    九、从0开始卷出一个新项目之瑞萨RZN2L生产烧录固件(jflash擦写读外挂flash)
  • 原文地址:https://blog.csdn.net/i777777777777777/article/details/128066607