• C++11常见语法


    目录

    lambda 表达式

    可变模板参数

    C++11新类的默认函数

    包装器

    function

    bind


    lambda 表达式

    • lambda 表达式也是可调用对象,在C语言中就有函数指针,但是函数指针比较复杂。

    • 而在C++11之前,也有仿函数,使用仿函数,还需要再定义一个类,专门实现仿函数,所以还是比较麻烦。

    • 而 lambda 还是比较简单的。

    下面看这一段代码

    1. class fruit
    2. {
    3. public:
    4. fruit(string name, int price, int evaluate)
    5. :_name(name)
    6. ,_price(price)
    7. ,_evaluate(evaluate)
    8. {}
    9. bool operator<(fruit& f)
    10. {
    11. return _price < f._price;
    12. }
    13. string _name;
    14. int _price;
    15. int _evaluate;
    16. };
    17. void test1()
    18. {
    19. auto lamd = [](int x, int y)->int {return x + y; };
    20. vector fruits{ {"香蕉", 10,9}, {"苹果", 13, 8}, {"葡萄", 16, 7}, {"梨",17,4}, {"榴莲", 50, 8}};
    21. sort(fruits.begin(), fruits.end());
    22. sort(fruits.begin(), fruits.end());
    23. sort(fruits.begin(), fruits.end());
    24. }

    这里如果排序的话,那么时不可以的,如果想排序除非重载比较函数,但是比较函数也只能重载一个。

    其实还可以写仿函数,而如果我们像使用多个成员变量的比较也是比较方便的,下面看一下仿函数的处理方法。

    1. class nameLess
    2. {
    3. public:
    4. bool operator()(fruit& f1, fruit& f2)
    5. {
    6. return f1._name < f2._name;
    7. }
    8. };
    9. class priceLess
    10. {
    11. public:
    12. bool operator()(fruit& f1, fruit& f2)
    13. {
    14. return f1._price < f2._price;
    15. }
    16. };
    17. class evaluateLess
    18. {
    19. public:
    20. bool operator()(fruit& f1, fruit& f2)
    21. {
    22. return f1._evaluate < f2._evaluate;
    23. }
    24. };
    25. void test1()
    26. {
    27. auto lamd = [](int x, int y)->int {return x + y; };
    28. vector fruits{ {"香蕉", 10,9}, {"苹果", 13, 8}, {"葡萄", 160, 7}, {"梨",17,4}, {"榴莲", 50, 8}};
    29. sort(fruits.begin(), fruits.end(), nameLess());
    30. sort(fruits.begin(), fruits.end(), priceLess());
    31. sort(fruits.begin(), fruits.end(), evaluateLess());
    32. }

    下面只需要把该类的对象传过去,就可以调用仿函数了,其实还可以使用 lambda 表达式,首先看一下 lambda 表达式如果写

    • lambda 表达式没有类型,所以可以直接写 lambda 的对象

    • 而如果要定义的话,那么可以把类型定义为 auto

    • lambda 表达式里面的内容有四个主体:

      1. 捕捉列表

      2. 参数列表

      3. 返回值

      4. 函数体

    • auto lamd = [捕捉列表](参数列表)->返回值{函数体;};

      上面就是 lambda 表达式的定义,而该对象也可以直接用作可调用对象

    1. void test1()
    2. {
    3. auto lamd = [](int x, int y)->int {return x + y; };
    4. vector fruits{ {"香蕉", 10,9}, {"苹果", 13, 8}, {"葡萄", 160, 7}, {"梨",17,4}, {"榴莲", 50, 8}};
    5. auto name_less = [](fruit& x, fruit& y)->bool {return x._name < y._name; };
    6. auto price_less = [](fruit& x, fruit& y)->bool {return x._price < y._price; };
    7. auto evaluate_less = [](fruit& x, fruit& y)->bool {return x._evaluate < y._evaluate; };
    8. sort(fruits.begin(), fruits.end(), name_less);
    9. sort(fruits.begin(), fruits.end(), price_less);
    10. sort(fruits.begin(), fruits.end(), evaluate_less);
    11. }

    上面就是调用使用 lambda 表达式来控制 sort 排序, lambda 比仿函数还是要容易一点

    下面就仔细介绍一下 lambda 表达式

    首先是参数列表:

    • 参数列表就是和正常函数一样,传参数的,所以这个参数列表和函数是一样的。

    • 如果没有参数的话,也是可以省略的。

    返回值:

    • 返回值的声明是箭头(->) 后加 返回值类型

    • 但是返回值是可以不写的,因为可以自动推导,所以返回值是可以省略的

    • auto lamd = [](){};

    捕捉列表:

    • 捕捉列表里面写的就是想要捕捉的值,如果不想再参数列表里面写的话,就可以写再捕捉列表里面。

    • 捕捉列表里面可以写的值:

      1. value: 也就是局部的变量都可以写

      2. &value:表示以引用的方式捕捉该变量

      3. =:还有=符号,表示将所有的局部变量都捕捉,不过捕捉的局部变量是不能修改的

      4. &:表示将所有的局部变量都以引用的方式捕捉

      5. 还可以混着写

    函数体:

    • 函数体和函数是一样的,所以这里也不多介绍了

    以值方式捕捉

    1. int a = 10;
    2. auto lamd = [a] {
    3. };

    以值方式捕捉的话,是不能被修改的

    1. int a = 10;
    2. auto lamd = [a] {
    3. a = 20;
    4. };

    上面这样写是会报错的,那么如果想要修改怎么办呢?

    可以加 mutable 让值捕捉的可以被修改,但是值捕捉指数拷贝,所以修改也并不会影响原来的值

    1. int a = 10;
    2. auto lamd1 = [a]() mutable {
    3. a = 20;
    4. };

    但是如果要加 mutable 的时候,就不能省略参数列表了。

    以引用捕捉的

    1. auto lamd2 = [&a] {
    2. a = 20;
    3. };

    如果是以引用捕捉的话,是可以修改的

    值捕捉所有的变量

    1. int b = 20;
    2. auto lamd2 = [=] {
    3. cout << a << endl;
    4. cout << b << endl;
    5. };

    同样是以值捕捉的话,是不能修改的,而上面定义的变量 a 和 b 都可以被访问

    引用捕捉所有的变量

    1. auto lamd2 = [&] {
    2. cout << ++a << endl;
    3. cout << b++ << endl;
    4. };

    以引用捕捉的话,也可以访问所有的变量,同时也可以修改

    混合使用

    1. auto lamd2 = [&, b] {
    2. cout << a << endl;
    3. cout << b << endl;
    4. };

    这种混合使用也是可以的,但是不能是重复的,也就是继续捕捉 &b,如果是 = 的话,那么也就不能以值捕捉其他变量,同时也不能即 = 又 &。

    可变模板参数

    • 可变模板参数和可变参数列表很相似

    • 而之前再C语言中见过的可变参数列表是 printf 和 scanf

    下面看一下可变模板参数的使用:

    1. template<class ...Args>
    2. void func(Args ...args)
    3. {
    4. }
    • 上面就是声明一个可变模板参数类型的函数

    • 其中上面的 Args 就是函数包,再写的时候必须有 ... 三个点,而这三个点也表示模板参数的展开

    • Args 就是模板传过来的类型,也就是类似于 K/T/V 这种一样,只是一个名字,所有不一定叫 Args

    1. template<class T>
    2. void func(const T& val)
    3. {
    4. cout << val << endl;
    5. }
    6. template<class T, class ...Args>
    7. void func(const T& val, Args ...args)
    8. {
    9. cout << val << endl;
    10. func(args...);
    11. }
    12. void test2()
    13. {
    14. func(1, 2.5, string("hello world"));
    15. }

    这里写一个可以打印可变参数列表的一个函数:

    • 首先是 func 函数里面传入了三个值,一个值传到了 T, 另外的值传到了参数包里面

    • func 函数打印第一个值,然后将参数包中的值递归式的调用自己没传给自己,这样就可以实现打印

    • 但是这里需要一个截至函数,也就是只剩下一个的时候,那么就是递归结束的条件

    可变模板参数的应用不过如此,还有其他的用处。

    看下面这一段代码:

    1. class Date
    2. {
    3. public:
    4. Date(int year, int month, int day)
    5. :_year(year)
    6. ,_month(month)
    7. ,_day(day)
    8. {}
    9. void print()
    10. {
    11. cout << _year << "-" << _month << "-" << _day << endl;
    12. }
    13. private:
    14. int _year = 1;
    15. int _month = 1;
    16. int _day = 1;
    17. };
    18. template<class ...Args>
    19. Date* Create(Args ...args)
    20. {
    21. return new Date(args...);
    22. }
    23. void test3()
    24. {
    25. Date* ret = Create(2001, 10, 10);
    26. ret->print();
    27. }

    上面 test 函数调用 Create 函数, Cretare 函数是可变模板参数,所以可以传任意类型个数的值,这里传入三个整型,然后让 Create 函数调用 Date的构造函数,然后构造一个 date 对象。

    实际上,还可以直接传入 date 对象

    1. void test3()
    2. {
    3. Date* d1 = Create(2001, 10, 10);
    4. d1->print();
    5. Date* d2 = Create(*d1);
    6. d2->print();
    7. }

    这里将 d1 解引用后传入到 Create 函数中,由于是可变模板参数,所以可以接受任意类型个数的参数,所以这里将 d1 传入后调用Date 的拷贝构造函数,然后拷贝构造一个 d2

    结果:

    1. 2001-10-10
    2. 2001-10-10

    介绍完这个之后,可以看一下C++11 里面 stl 容器里面的 emplace 接口:

    1. template <class... Args>
    2. void emplace_back (Args&&... args);

    那么它就是利用了模板的可变参数,如果 args 是一个插入的对象的参数,那么就该函数就调用 new 对应的节点构造函数,或者是拷贝构造,如果直接是对象的话,那么就是拷贝构造(但是如果写了移动构造,就有可能是移动构造),如果是参数的话,那么就是构造函数

    看下面代码:

    1. void test4()
    2. {
    3. listint, int>> lp;
    4. lp.emplace_back(10, 10);
    5. lp.emplace_back(make_pair(20, 20));
    6. }

    上面代码里面第一次调用emplace_back 是传的是参数,所以会调用构造函数,而第二个会走移动构造。

    C++11新类的默认函数

    • 有了上面的右值引用,实际上到C++11里面,多了两个默认函数。

    • 移动构造

    • 移动赋值

    但是既然时默认函数,那么其默认时如何生成的,默认生成的又会做什么?

    • 其实移动构造和移动赋值的默认生成行为一样!

    • 对于移动构造来说,如果没有显示的写移动构造和移动赋值,并且还没有写拷贝构造、复制重载、析构函数,那么就会默认生成一个,默认生成的移动构造,会对内置成员变量进行浅拷贝,对于自定义类型会调用其移动构造,如果自定义类型没有移动构造,那么就会调用其拷贝构造。

    • 对于移动赋值来说,如果没有显示的写移动构造和移动赋值,并且还没有写拷贝构造、复制重载、析构函数,那么就会默认生成一个,默认生成的移动赋值,会对内置成员变量进行浅拷贝,对于自定义类型会调用其移动赋值,如果自定义类型没有移动赋值,那么就会调用其复值重载。

    上面就是C++11新类的默认函数。

    再C++11之前,默认函数就有6个,但是我们常用的只有4个,再加上C++11的两个,那么就是6个默认函数。

    • 默认构造函数:

    • 默认拷贝构造:

    • 默认复制重载:

    • 默认析构函数:

    • 默认移动构造:

    • 默认移动赋值:

    包装器

    function

    这里先看这个函数:

    1. template<class F, class T>
    2. T Fun(F f, T val)
    3. {
    4. static int count = 0;
    5. cout << (++count) << endl;
    6. cout << (&count) << endl;
    7. return f(val);
    8. }
    • 该函数里面有一个 static 的 count 对象,如果时相同类型调用该函数的话,那么都使用的是一个 count.

    • 但是如果是不同的类型的话,那么这里的 count 和 count 的地址都是不同的。

    • 下面我们用三个可调用对象传给该函数,这三个对象的参数返回值都一样,只是里面的实现不同,我们能不能调用到同一个函数呢?

    1. // 函数
    2. double Func1(double val)
    3. {
    4. return val / 10;
    5. }
    6. // 仿函数
    7. class Func2
    8. {
    9. public:
    10. double operator()(double val)
    11. {
    12. return val / 20;
    13. }
    14. };
    15. void test1()
    16. {
    17. // lambda 表达式
    18. auto Func3 = [](double val) {return val / 30; };
    19. cout << Fun(Func1, 15) << endl<< endl;
    20. cout << Fun(Func2(), 15) << endl << endl;
    21. cout << Fun(Func3, 15) << endl << endl;
    22. }
    • 这里我们分别由三个可调用对象来调用该函数:

    • 函数指针

    • 仿函数

    • lambda 表达式

    结果:

    1. 1
    2. 00A7A140
    3. 1
    4. 1
    5. 00A7A144
    6. 0
    7. 1
    8. 00A7A148
    9. 0
    • 这里看到这里的地址和值基本都是不一样的。

    • 说明这三个函数都调用到了不同的函数。

    • 那么这三个函数的参数返回值都相同为什么调用不到同一个函数呢?

    • 因为他们的类型不相同。

    • 那么我们想要让他们调用到同一个函数怎么办?

    • 下面就看一下包装器,我们使用包装器,使他们的的类型都相同。

    先看一下他的类型:

    1. template <class Ret, class... Args>
    2. class function<Ret(Args...)>;
    • 这里的包装器使一个模板。

    • 他的参数好像模板的特化,而 Ret 表示的就是函数的返回值,而后面的参数包里面写的就是函数的参数。

    • 由于不知道函数由多少个参数,所以就使用可变模板参数。

    下面我们将那三个函数都包装成相同的类型:

    1. void test2()
    2. {
    3. auto Func3 = [](double val) {return val / 30; };
    4. // 包装
    5. function<double(double)> fun1 = Func1;
    6. function<double(double)> fun2 = Func2();
    7. function<double(double)> fun3 = Func3;
    8. //       返回值   参数
    9. //现在这三个函数的类型相同,现在调用 Fun函数
    10. cout << Fun(fun1, 15) << endl << endl;
    11. cout << Fun(fun2, 15) << endl << endl;
    12. cout << Fun(fun3, 15) << endl << endl;
    13. }

    结果:

    1. 1
    2. 009FE4FC
    3. 1
    4. 2
    5. 009FE4FC
    6. 0
    7. 3
    8. 009FE4FC
    9. 0
    • 现在看到,这里的 count 的地址也一样了,并且我们对 count 加了三次。

    bind

    除了上面这样包装函数,那么我们还可以怎么做?

    我们还可以将参数与位置绑定:

    1. void test3()
    2. {
    3. auto sub = [](double a, double b) {return a - b; };
    4. function<double(double, double)> sub1 = bind(sub, placeholders::_1, placeholders::_2);
    5. cout << sub1(10, 5) << endl;
    6. }
    • 这里我们使用 bind 函数,bind 函数里面的第一个参数是想要绑定的函数。

    • 后面的参数是每个调用绑定函数的时候,按顺序传给绑定的函数。

    • 而这个 placeholder::x 这个表示给 sub1 传参时候的第几个参数,第一个参数就会 placeholders::1 的位置,同理第二个参数就会传给第二个。

    下面看一下结果其实就明白了。

    结果:

    5

    下面我们再换一下参数的位置:

    1. void test3()
    2. {
    3. auto sub = [](double a, double b) {return a - b; };
    4. function<double(double, double)> rsub = bind(sub, placeholders::_2, placeholders::_1);
    5. cout << rsub(10, 5) << endl;
    6. }

    结果:

    -5

    其实 bind 不光可以绑定参数的位置,还可以绑定固定的参数:

    1. void test4()
    2. {
    3. auto Fun = [](int a, int b, double rate) {
    4. return (a + b) * rate;
    5. };
    6. function<double(int, int)> fun1 = bind(Fun, placeholders::_1, placeholders::_2, 1.1);
    7. cout << fun1(10,20) << endl;
    8. }
    • 这里将 1.1 固定的绑定再 rate 上。

    结果:

    33

    那么这个有什么用呢?

    • 这个可以将特定参数与位置绑定,那么有一些函数经常需要传固定参数的函数,那么就可以使用 bind 减少我们传参。

    • 还可以实现打折,如果是 vip 的话,那么计算价钱的方式打折就更多一点,如果是普通用户,那么就是打折的话少一点。

    • 其实还可以有很多用处。

  • 相关阅读:
    Cilium 1.11:服务网格的未来已来
    索引的设计原则
    Linux基本操作和基础命令(Linux修改IP地址以及修改网卡地址)
    模板 树状数组套主席树查询动态区间k小,区间修改版
    产品力如何驱动SaaS企业新增长?
    MySQL大数据量高速迁移,500GB只需1个小时
    Nginx 在 Linux 系统上安装 - 细节狂魔
    记ZooKeeper3.7在win下的单机部署
    苹果手机的祛魅时刻,国产厂商的颠覆征程
    LeetCode 2656. K 个元素的最大和【数学】简单
  • 原文地址:https://blog.csdn.net/m0_73455775/article/details/133670644