当前位置:   article > 正文

【C++】c++11学习-常用特性总结_c++11特性学习

c++11特性学习

前言 

        由于种种历史原因,C++的C++11版本更新带来了很多有用的东西~,在C++98的基础语法体系之上,来看看C++11新增了哪些实用的特性吧~

 (加把劲~~(๑╹◡╹)ノ""")

目录 

一、列表初始化

1.原始的列表{}初始化

2.内置类型的列表初始化

3.自定义类型的列表初始化

二、推导类型

1.auto

2.typeid().name()

3.decltype

三、范围for

四、final、override关键字

五、智能指针

六、新增容器

七、默认成员函数控制

1.显示缺省函数

2.删除默认成员函数

八、引用

1.左值引用和右值引用

1.1左值和右值

1.2引用交叉使用

2.左值引用无法解决的问题

3.移动拷贝和移动赋值

4.万能引用&完美转发

5.总结

九、可变参数

1.利用模板参数递归推导

 2.利用数组去实例化每一个参数

十、lambda表达式

十一、包装器、绑定

1.包装器

2.绑定

十二、线程库


一、列表初始化

1.原始的列表{}初始化

        说到最原始的列表初始化,就不得不从我们接触到的第一个数组开始说起~

  1. void test1()
  2. {
  3. // 列表初始化
  4. int arr[] = {1, 2, 3, 4};
  5. for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
  6. {
  7. cout << arr[i] << " ";
  8. }
  9. cout << endl;
  10. }

        但是对于自定义类型的话,就不能像上面的数组那样进行连续的赋值:

    vector<int> v = {1, 2, 3};

         那么这样的话,每次对于自定义类型想要一次性连续赋值构造就无法实现,只能自己手动利用循环进行赋值,很不方便。

2.内置类型的列表初始化

        C++11语法对{}的功能进行扩大化,使得{}变的万物皆可。

内置类型:

        进行初始化的时候,均可使用{}。连续赋值对应每个元素之间加上,即可。并且'='可以省略。

  1. int x = {1};
  2. int y {2};
  3. int ar1[]{1, 2, 3};
  4. int ar2[3]{1};
  5. int* ar3 = new int[3]{2, 4, 5};

        一般不推荐把'='省略掉。

3.自定义类型的列表初始化

         C++11语法将{}扩大化就是为了让自定义类型能够更好的进行初始化,首先是STL内的容器想要初始化连续的元素值的话:

  1. vector<int> v = {1, 2, 3};
  2. unordered_map<string, string> m = {{"language", "语言"}, {"practical", "实用"}};
  3. for (auto e : v) cout << e << " ";
  4. cout << "\n" << m["language"] << m["practical"] << endl;

         同时=也可以类似操作。

  1. unordered_map<string, string> m = {{"language", "语言"}, {"practical", "实用"}}; // 首先支持initalizer_list构造,其次就是默认类型转换。
  2. m = {{"玉子", "饼藏"}};
  3. cout << m["玉子"] << endl;

自定义类型:

        {}首先支持了多参数的默认类型转换

        {}在C++11中可以看做是一个新的类型initializer_list。此类型模板主要有着迭代器的相关操作以及获取其中元素个数的方法size()。 

        库里的容器之所以可以使用{}进行初始化或者=使用列表进行初始化实际上就是重载了参数类型为initializer_list的构造方法或者赋值重载而已,所以如果想让自己定义的类型可以实现列表初始化就重载initializer_list的相关方法即可。

initializer_list类型文档:

initializer_list - C++ Reference (cplusplus.com)

        测试{}类型:

  1. auto e = {1, 2};
  2. cout << typeid(e).name() << endl;

        以下面这个自定义程序为例,测试默认类型转换和进行 initializer_list构造方法编写,使其列表可以一次性初始化多个值。

  1. #include <initializer_list>
  2. class Student
  3. {
  4. public:
  5. Student(string name, size_t id)
  6. :_name(name), _id(id)
  7. {}
  8. const string& PutName()
  9. {
  10. return _name;
  11. }
  12. size_t PutId()
  13. {
  14. return _id;
  15. }
  16. private:
  17. string _name;
  18. size_t _id;
  19. };
  20. class StudentManagement
  21. {
  22. public:
  23. // 实现列表初始化
  24. StudentManagement(const initializer_list<Student>& l)
  25. :_size(l.size())
  26. {
  27. for (auto& e : l) // 底层有迭代器 - 可以使用范围for
  28. {
  29. _v.push_back(e);
  30. }
  31. }
  32. void Printlist() // 打印测试
  33. {
  34. for (auto& e : _v)
  35. {
  36. cout << e.PutName() << ":" << e.PutId() << endl;
  37. }
  38. }
  39. private:
  40. vector<Student> _v;
  41. size_t _size;
  42. };
  43. void test2()
  44. {
  45. // 测试自定义类型列表初始化
  46. Student s = {"张三", 1}; // 默认类型转换
  47. cout << s.PutName() << ":" << s.PutId() << endl << endl;
  48. StudentManagement ss{{"李四", 2}, {"王五", 3}, {"小明", 4}, {"小美", 5}}; // 列表初始化对自定义类型进行多个对象初始化:默认类型转换 + 列表初始化构造函数
  49. ss.Printlist();
  50. }

注:里面所用的范围for在下面会细讲哦~ 

二、推导类型

1.auto

        在类型很长的情况下,我们书写代码会变得非常的不方便,比如如下的一段代码就可体现出来:

  1. void test3()
  2. {
  3. // 类型推导
  4. unordered_map<string, string> m{{"我不高兴", "不愉快です"}, {"我很好奇!", "わたし、気になります!"}};
  5. unordered_map<string, string>::iterator ib = m.begin();
  6. while (ib != m.end())
  7. {
  8. cout << ib->first << ":" << ib->second << endl;
  9. ++ib;
  10. }
  11. }

        可以看到上述代码在写迭代器的变量的时候很长,不是很方便,但是auto关键字可以自动推导类型方便省事:

  1. // unordered_map<string, string>::iterator ib = m.begin();
  2. auto ib = m.begin();

 

        可以看到此时就可以省去很多书写的时间啦~

auto:

        auto后面的变量必须初始化,此时auto就会变为此变量初始化对应的类型。

2.typeid().name()

        和auto不同,如果单纯想要知道一个对象的类型的话,这种或许可以对你的胃口。

头文件:#include <typeinfo>

typeid(对象/类型).name() ->string 

注意:返回是一个字符串,不能像auto那样当做变量的类型。

    cout << typeid(ib).name() << endl;  // 之前迭代器的对象

  

3.decltype

        还有一种,根据表达式的实际类型推导出定义变量时候的类型。注意是定义变量的时候的类型,也就是说可以利用此来定义变量。

decltype(表达式) -> 根据表达式推导出来的类型。

表达式:可以是一个元素、函数、函数返回值等等。不会去执行,而是去推演。

  1. int x = 2;
  2. decltype(x) y; // 定义对象
  3. y = x;
  4. cout << y << endl;
  5. double z = 2.1;
  6. cout << typeid(decltype(x * y)).name() << endl; // int int
  7. cout << typeid(decltype(x * z)).name() << endl; // int double

  

三、范围for

使用格式:

        for(auto 变量名 : 可迭代对象)

        {

                //..... 

        }

        自动从可迭代对象中取每个元素,自动往后迭代。

        对于auto 变量名来说,可以auto& 变量名使用改变可迭代对象里元素的值(如果可以修改的话),当然也可以减少拷贝。

(底层通过迭代器调用实现。只要对象类型实现了迭代器就可以进行范围for)

  1. vector<int> vv = {1, 2, 3, 4, 5, 6};
  2. for (auto& e : vv)
  3. {
  4. cout << e << " ";
  5. e++; // 加上引用修改里面的值
  6. }
  7. cout << endl;
  8. for (auto e : vv)
  9. {
  10. cout << e << " "; // 正常范围for遍历,并且验证上面是否修改
  11. }
  12. cout << endl;

四、final、override关键字

final:

        1.在定义类的后面即class/struct 类名 添加关键字final表示此类为最终类,无法被继承。

        2.在定义虚函数的后面即virtual 虚函数名() final表示该虚函数不可被重写。

override:

        检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。同样的跟在虚函数后面。

        验证final: 

         验证override:标记了覆盖,必须覆盖基类的虚函数。注意是虚函数

五、智能指针

        智能指针的出现能够让我们从前申请的资源必须手动释放变成自动托管生命周期结束自动释放。当然也是C++填没有垃圾回收的坑,并且随着异常的出现,也就更加需要智能指针的存在。

        智能指针在另一篇文章详细介绍哦~ 未完待续......

六、新增容器

        在C++11中,STL增加了array、forward_list以及unordered系列的容器。其中unordered是利用哈希表数据结构实现的,作用显著,其他的相对起来比较鸡肋。

array

        -可以把这个容器理解为静态的顺序表。

        -支持 class T size_t N   []  迭代器
        -C++11支持array的初衷--想要替代C语言的数组  -- 越界检查、容器化
        -C语言的数组,越界读基本检查不出来,越界写时抽查。但是array就不一样了,都可以被检查出来。
        -但是用的很少 -- 一方面用C的数组用惯了,另一方面用array不如vector->vector + resize  另外array可能导致栈溢出。是在栈上开辟空间的哦。

        相关文档:array - C++ Reference (cplusplus.com)

forward_list 单链表

        -优势:比其list(双向链表)空间节省那么一丁点
        -但是给的接口比较怪:erase删除下一个,insert_after  插入后面

        相关文档:forward_list - C++ Reference (cplusplus.com)

        unordered系列也是在另一篇文章详细介绍哦~未完待续......

        另外,容器里面也新增了一些方法:

容器里方法的变化:
    1.都支持了initializer_list构造,用来支持列表初始化。
    2.比较鸡肋的一些接口:cbegin、cend系列(不够明确?)
    *3.移动构造和移动赋值。set(set&& x)  set& operator = (set&& x);
    *4.右值引用参数的插入。insert、emplace    

    // 3 - 4提高效率(右值引用下面会进行详细介绍)

七、默认成员函数控制

1.显示缺省函数

        在C++11中,可以在默认函数定义或者声明时加上=default,从而显式的指示编译器生成该函数的默认版本,用=default修饰的函数称为显式缺省函数。

         常用于显示写构造函数后还需要一个默认版本的,此时就可以如上进行操作。

2.删除默认成员函数

        如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且不给定义,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数

         注意:避免删除函数和explicit一起使(explicit是防止函数进行隐式类型转换)。

八、引用

1.左值引用和右值引用

        C++98定义了引用。即引用就是给一个变量取别名,然后两个变量共享一个地址空间,比如swap函数的实现。详细介绍可参考【C++】基础学习笔记_柒海啦的博客-CSDN博客

        其实在C语言中就区分了左值和右值。在C++11中加强了这种区分,并且也增加了一种引用,区别于C++98的引用,叫做右值引用。C++98的引用为左值引用。在了解引用之前,首先了解一下左值和右值有着如何区别?

1.1左值和右值

左值

        1.普通类型的变量,有名字,可以取地址,认为其为左值。

        2.const修饰的变量,也可以去地址,认为其为左值。

        3.左值可以出现在等号的左边或者右边。

        左值引用:类型& 别名 = 左值;

右值

        1.常量,无法获取地址。

        2.产生的临时对象,将亡值(表达式产生的中间结果,函数按值返回)。

        3.只能出现在等号的右边。

        右值引用:类型&& 别名 = 右值; (右值被引用后也有变量名,可以被取地址和赋值,所以就变成了左值)

        以下代码验证左值和右值:

  1. int add(int x, int y)
  2. {
  3. return x + y; // 传值返回
  4. }
  5. void test5()
  6. {
  7. // 引用
  8. // 左值
  9. int a = 10;
  10. int& _a = a;
  11. cout << &a << endl;
  12. const int b = 12;
  13. cout << &b << endl;
  14. a = b; // 可以出现等号的左边
  15. // 右值
  16. int&& d = 10; // 常量 - 纯右值
  17. int&& e = a + b; // 表达式相加返回的是一个临时对象
  18. int&& f = add(1, 2); // 将亡值或者也是一个临时对象
  19. int& g = f; // f此时也是一个左值
  20. }

        实际上,临时对象是临时的,所以也可以认为其是一个将亡值。

1.2引用交叉使用

         那么在C++11之前就无法引用右值吗?不是的。对于左值引用来说,加上const关键字修饰就可以引用右值了。表示其对象不可修改。

  1. const int& a = 10; // const修饰后的左值引用可以引用右值
  2. const int& b = add(1, 2); // 同上

        既然左值引用可以引用右值,那么右值引用可以引用左值吗?可以的。对于左值使用库中的std::move(左值)转化为右值就可以给右值引用了。只不过需要注意move的作用是转移资源,实际上不做任何事情,只是可被右值引用而已,左值没变。但是可以实现转移语义(如果实现了移动构造就需要注意了,下面会讲)。

  1. int a = 10;
  2. int&& c = move(a);
  3. cout << c << endl;
  4. cout << a << endl;

         那么既然右值可以被左值引用进行引用,那么我们为什么需要右值引用呢?我们利用下面这个例子进行模拟需要用右值引用的场景:

2.左值引用无法解决的问题

  1. class Student
  2. {
  3. public:
  4. Student(string name, size_t id)
  5. :_name(name), _id(id)
  6. {}
  7. Student() = default; // 生成默认构造函数
  8. // 拷贝构造
  9. Student(const Student& stu)
  10. {
  11. _name = stu._name; // 调用string类的赋值重载
  12. _id = stu._id;
  13. cout << "Student发生了拷贝构造" << endl;
  14. }
  15. const string& PutName()
  16. {
  17. return _name;
  18. }
  19. size_t PutId()
  20. {
  21. return _id;
  22. }
  23. private:
  24. string _name;
  25. size_t _id;
  26. };
  27. class StudentManagement
  28. {
  29. public:
  30. // 实现列表初始化
  31. StudentManagement(const initializer_list<Student>& l)
  32. :_size(l.size())
  33. {
  34. for (auto& e : l) // 底层有迭代器 - 可以使用范围for
  35. {
  36. _v.push_back(e);
  37. }
  38. }
  39. void Printlist() // 打印测试
  40. {
  41. for (auto& e : _v)
  42. {
  43. cout << e.PutName() << ":" << e.PutId() << endl;
  44. }
  45. }
  46. // 假设上述程序实现id去重操作
  47. Student IdOfStudent(size_t id) // 根据id返回一个新学生对象(不是vector内的),不存在就设置一个空对象
  48. {
  49. for(auto& e : _v)
  50. {
  51. if (e.PutId() == id) return e; // 传值返回
  52. }
  53. return Student("null", id); // 不存在返回空的新对象
  54. }
  55. private:
  56. vector<Student> _v;
  57. size_t _size;
  58. };

        上述代码是上面的一个例子,现在新增了一个IdOfStudent函数,意思是如果学生管理库中存在id一样的学生对象,返回其的拷贝对象,如果不存在就返回一个空名字的新对象。

         由于是传值返回,我们显示写了元素类型的拷贝函数,打印出对应调用其信息,利用如下测试代码在debug模式下运行(为了减少迭代器的优化,查看最原始的效果,我在Linux下的g++后加-fno-elide-constructors进行取消优化),查看代码及其结果:

  1. StudentManagement sm = {{"张三", 1}, {"李四", 2}, {"王五", 3}};
  2. cout << endl << endl;
  3. Student s1 = sm.IdOfStudent(2); // 2次拷贝构造:vector中的对象-临时对象-外面的对象
  4. cout << endl;
  5. Student s2 = sm.IdOfStudent(4); // 2次拷贝构造:匿名对象-临时对象-外面的对象

(注意初始化的时候拷贝信息没有截上,无用)

 

        针对上面的情况,可以简单的用如下图进行展示,就可以看的出来问题了:

        第二种情况同理。可以发现,如果是传值返回的话,发生这种情况每次对象转移资源的时候就会拷贝构造一次,对象里面的值比如string也就是进行一次深拷贝。即使编译器优化也会发生拷贝构造,而发生拷贝构造后,原本的空间又被释放掉了。这不仅仅是对空间的浪费,程序效率也会十分低下。赋值同理。

        针对此种情况,以前的解决方法一般就是设置一个输出型参数,这样就可以借助引用不会发生多余的空间浪费了。C++11引入了两个默认成员函数,移动拷贝和移动赋值。

3.移动拷贝和移动赋值

移动拷贝

        类名(类名&& 名){ //转移资源即可 }

注意:

        1.对进行深拷贝的对象有效,此时是转移资源,而不是重新进行深拷贝。(上面的例子只是举例,实际上上面对象string类型官方已经实现了移动赋值和移动拷贝,这里只是模拟演示)

        2.移动拷贝如果需要默认生成,需要满足没有实现析构、拷贝构造、拷贝赋值中的其中一个。默认移动拷贝就会做 内置类型值拷贝,自定义成员有移动构造调用移动,没有就调用拷贝构造。

(实际上如果存在需要进行深拷贝,上述的成员函数一定都要实现的,原因就是深拷贝)

移动赋值

        类名& operator=(类名&& 名){  // 转移资源即可 }

注意:

        1.同上类似。

        2.移动赋值如果需要默认生成,需要需要满足没有实现析构、拷贝构造、拷贝赋值中的其中一个。内置值拷贝,自定义类型有实现移动赋值就调用,没有就调用拷贝赋值。

(在大多数条件下,如果出现了深拷贝,都不会让编译器自己默认生成的)

        利用上面的知识我们对上面的程序进行改造一下(假设string类没有实现移动构造和赋值)

  1. // 移动构造
  2. Student(Student&& stu) // 右值识别进入函数
  3. {
  4. stu._name.swap(_name); // 转移资源
  5. _id = stu._id;
  6. cout << "Student发生了移动构造" << endl;
  7. }
  8. // 移动赋值
  9. Student& operator=(Student&& stu)
  10. {
  11. stu._name.swap(_name); // 转移资源
  12. _id = stu._id;
  13. cout << "Student发生了移动赋值" << endl;
  14. return *this;
  15. }

        此时,上面的测试程序中,第一个如果学生管理里有的话首先是进行拷贝构造,因为e为引用,不会被识别为将亡值,自然进入左值引用的区域,然后在临时对象给外面的对象的时候,临时对象被识别为右值,发生移动构造。第二个同理,只不过第一个也是移动构造(匿名对象-临时对象-右值),此时在加一个赋值同理:

  1. Student s1 = sm.IdOfStudent(2); // 拷贝构造+移动构造:vector中的对象-临时对象-外面的对象
  2. cout << endl;
  3. Student s2 = sm.IdOfStudent(4); // 2次移动构造:匿名对象-临时对象-外面的对象
  4. cout << endl;
  5. Student s4;
  6. s4 = Student("xxx", 0); // 赋值 - 移动赋值

 

        注意右值引用并不是像左值引用那样直接触发(也就是作为返回值的出现,作为返回值的话,到外面去还是被识别为左值,不会产生任何影响,该拷贝还是拷贝) 

        右值可以大胆的进行交换资源,因为右值是一个将亡值,但是左值就不行了。

4.万能引用&完美转发

        在模板中,如果将模板类型定义为T&& 此时T&&就为万能引用,也就是说左值可以传入,右值也可以传入(实际上const T&类似,右值也可以传入)。但是呢,这样的话会发生引用折叠。也就是说此时此万能引用类型的对象的属性会被统统识别为左值(其实只要是引用对象名都会被识别为左值),那么我们如果想要将右值的类型延续下去该怎么办呢?使用完美转发就可以了。

万能引用:

ed:

        template<class T>

        test(T&& t) ->t就是一个万能引用

        注意:

                此时t传递下去被识别为左值

完美转发:

        std::forward<T>(万能引用对象); 

        此时返回的对象就是其原本的左值或者右值属性。

        以下面的代码作为例子进行测试:

  1. // 重载不同类型参数的相同函数名
  2. void Func(int& x)
  3. {
  4. cout << "左值" << endl;
  5. }
  6. void Func(int&& x)
  7. {
  8. cout << "右值" << endl;
  9. }
  10. void Func(const int& x)
  11. {
  12. cout << "const左值" << endl;
  13. }
  14. void Func(const int&& x)
  15. {
  16. cout << "const右值" << endl;
  17. }
  18. template<class T>
  19. void test(T&& t) // 万能引用
  20. {
  21. Func(forward<T>(t)); // 完美转发
  22. }
  23. void test6()
  24. {// 完美转发
  25. test(1); // right
  26. int x = 1;
  27. test(x); // left
  28. test(move(x)); // right
  29. const int y = 2;
  30. test(y); // const left
  31. test(move(y)); // const right
  32. }

  

5.总结

        综上,C++11新增的右值引用的作用如下:

1.能够实现移动语义,即移动拷贝和移动赋值。增加效率。

2.给右值取名字。

3.实现完美转发。

九、可变参数

        早在C语言的printf函数内,我们就见到了可变参数。那么我们如果想要自己定义一个可变参数怎么办?这就要用到可变参数包了:

template<class ...Args>

void test(Args... args){} 

        此时向test函数传参就是0到n个参数了。底层是通过数组进行接收的。

        sizeof...(args) 可以计算可变参数包大小。

        要使用此函数参数包,args... 进行使用。

        那么现在如何拿到对应的参数呢?直接拿是不行的哦~

1.利用模板参数递归推导

  1. void test()
  2. {
  3. cout << endl;
  4. }
  5. template<class T, class ...Args>
  6. void test(const T& val, Args... args)
  7. {
  8. cout << val << ":" << sizeof...(args) << endl;
  9. test(args...);
  10. }
  11. void test7()
  12. {
  13. test(1, 'a', 'b', "ds", 343);
  14. test('A', 2, 'c');
  15. }

 2.利用数组去实例化每一个参数

  1. template<class T>
  2. int PrintArgs(const T& val)
  3. {
  4. cout << val << endl;
  5. return 0;
  6. }
  7. template<class ...Args>
  8. void test(Args... args)
  9. {
  10. int arr[] = {PrintArgs(args)...};
  11. cout << endl;
  12. }

        另外,可变参数在容器新加emplace即可变函数包有些作用。 

十、lambda表达式

        另外C++11引进了一个匿名函数。匿名函数顾名思义,也是能够像函数使用的一个对象。只不过此对象没有特定的类型,所以一般只能auto或者下面的包装器进行接收。

lambda表达式的使用:匿名函数

        使用格式

                [捕捉列表](参数列表)mutable->返回值类型 {函数体实现};

                 (红色的是必须要写的,黑色可以不写)

        详细介绍

                1.捕捉列表:能够捕捉当前作用域中的变量提供给lambda函数使用

                        (假设x和y为当前作用域的两个变量)

                        [x, y]  捕捉x和y,默认为const拷贝。

                        [x, y]()mutable 捕捉x和y,值拷贝,无const属性。

                        [&x, &y] 引用捕捉x和y。

                        [=] 传值捕捉当前作用域所有变量,默认为const类型。

                        [&] 引用传递当前作用域所有的变量。

                        注意:上述的每个使用可以混合使用,只不过满足一个前提:引用传递不能和引用传递一起,传值捕捉不能和传值捕捉一起。(相同类型捕捉不可混在一起)

                2.参数列表:传给此lambda表达式的参数。像正常函数那样使用即可。捕捉列表捕捉了就可以不用传参了。

                3.mutable:捕捉列表传值捕捉默认const,加上此关键字是其const属性消除。

                4.->返回值类型:和函数类似。一般不用写,会自行推导。

                5.函数体实现:函数体的实现,不限制行数。

        代码演示上述lambda表达式-匿名函数的功能。

        捕捉列表:

  1. int x = 10, y = 2;
  2. auto func1 = [x, y](){ return x + y; };
  3. cout << func1() << endl;
  4. cout << endl;
  5. // 默认const 使用关键字mutable,去掉const
  6. auto func2 = [x, y]()mutable{ y -= 6; return x + y; };
  7. cout << func2() << endl;
  8. cout << y << endl; // 只是传值返回
  9. cout << endl;
  10. // 引用捕捉
  11. auto func3 = [&x, &y](){ int tmp = x; x = y; y = tmp; };
  12. cout << "x=" << x << " y=" << y << endl;
  13. func3();
  14. cout << "x=" << x << " y=" << y << endl;
  15. cout << endl;
  16. // 混合使用
  17. int z = 8;
  18. auto func4 = [&x, &y, z](){
  19. x = 10;
  20. y = 2;
  21. cout << z << endl;
  22. };
  23. func4();
  24. cout << "x=" << x << " y=" << y << endl;
  25. auto func5 = [=, &x](){ // 符号=先 x引用捕捉,其余传值捕捉
  26. x = 2;
  27. cout << y << " " << z << endl;
  28. };
  29. func5();
  30. cout << "x=" << x << endl;
  31. auto func6 = [&, x](){ // 符号在前 x传值捕捉,其余引用捕捉
  32. cout << x << endl;
  33. y = z = 0;
  34. };
  35. cout << "y=" << y << " z=" << z << endl;
  36. cout << endl;

         参数列表:

  1. // 参数列表
  2. auto func7 = [](int& x, int& y){
  3. int tmp = x;
  4. x = y;
  5. y = tmp;
  6. };
  7. x = 4;
  8. cout << "x=" << x << " y=" << y << endl;
  9. func7(x, y);
  10. cout << "x=" << x << " y=" << y << endl;
  11. cout << endl;

    

十一、包装器、绑定

1.包装器

        学到目前的程度,我们所能了解的C++函数对象有哪几种呢?函数指针,仿函数对象、lambda表达式对象

        那么我们能否将这些函数对象统一包装成一份去供我们使用呢?C++11提供了包装器可以让我们进行统一的去包装。

包装器:

        所需头文件:functional

        类型展示:

                    template<class Ret, class... Args>
                    class function<Ret(Args...)>

                Ret-返回类型 Args... 参数包

        使用:

                function<返回值类型(参数包)> 对象名 = 函数指针/仿函数对象/lambda表达式/成员函数(成员函数(包括静态)-&类名::成员函数指针)

                注意:如果调用成员函数(非静态),必须要传一个对象 返回类型(类名,参数),因为成员函数存在一个隐藏的this指针哦~。之后调用的时候也就需要传入此类的一个对象。

        相关文档:function - C++ Reference (cplusplus.com)

        以下代码简单展示包装器的使用:

  1. void func1()
  2. {
  3. cout << "わたし、気になります!" << endl; // 我很好奇
  4. }
  5. struct func2
  6. {
  7. void operator()()
  8. {
  9. cout << "不愉快です" << endl; // 我不高兴
  10. }
  11. };
  12. class test
  13. {
  14. public:
  15. int fun4(int a, int b)
  16. {
  17. return a + b;
  18. }
  19. static int fun5(int a, int b)
  20. {
  21. return a + b;
  22. }
  23. };
  24. void test9()
  25. {
  26. // 包装器的使用
  27. function<void()> Func1 = func1; // 函数指针
  28. function<void()> Func2 = func2(); // 仿函数对象 - 此处为匿名对象
  29. function<void(string, string)> Func3 = [](const string& x = "玉子", const string& y = "市场"){ // 注意虽然给了缺省参数,但是包装器上层包装必须要传参的哦~
  30. cout << x + y << endl;
  31. }; // lambda表达式
  32. function<int(test, int, int)> Func4 = &test::fun4; // 成员函数
  33. function<int(int, int)> Func5 = &test::fun5; // 静态成员函数,不存在this指针。
  34. Func1();
  35. Func2();
  36. Func3("玉子", "市场");
  37. cout << Func4(test(), 1, 1) << endl; // 别忘了传入对象
  38. cout << Func5(2, 2) << endl;
  39. }

 

2.绑定

绑定:

        头文件:functional

        类型展示:

            template<class Fn, class... Args>
            bind(Fn&& fn, Args&&... args);  - 适配器 - 对参数的适配、调整参数

         传参:placeholder

                 fn首先为函数对象,函数对象可以是函数指针,lambda表达式,仿函数对象,成员函数。如上。绑定直接传参即可,无需进行显示传模板参数。

                args placeholder-占位符。在此命名空间里_1, _2, _3....分别对应传入之后实际调用的第一个参数,第二个参数..... 可以实现调整参数顺序等操作。

                返回的也是bind相关的函数对象,function可以接收。和function联合一起可以做到类型统一等操作。


        相关文档:bind - C++ Reference (cplusplus.com)

         下面利用简单代码对上述功能进行展示:

  1. class test
  2. {
  3. public:
  4. int fun4(int a, int b)
  5. {
  6. return a + b;
  7. }
  8. static int fun5(int a, int b)
  9. {
  10. return a - b;
  11. }
  12. };
  13. void test9()
  14. {
  15. //......
  16. auto t1 = bind(func1); // 此时返回的也是一个函数对象
  17. t1();
  18. cout << typeid(t1).name() << endl;
  19. auto t2 = bind([](int i){cout << i << " 个" << endl;}, placeholders::_1); // 此时lambda匿名对象有参数,那么就要用到placeholders中的占位了。
  20. t2(2);
  21. // 我们想统一test::func4 和 func5 的function怎么办 - 原本是:<int(int, int)> <int(test, int, int)>
  22. function<int(int, int)> Func6 = bind(&test::fun4, test(), placeholders::_1, placeholders::_2); // 在bind中首先传参,这样就能保证function一致了
  23. function<int(int, int)> Func7 = bind(&test::fun5, placeholders::_1, placeholders::_2);
  24. // 类型统一很有意义,可以看下面的一段代码
  25. unordered_map<char, function<int(int, int)>> m;
  26. m['+'] = Func6;
  27. m['-'] = Func7;
  28. char flag;
  29. int a, b;
  30. while(cin >> flag)
  31. {
  32. cin >> a >> b;
  33. cout << m[flag](a, b) << endl;
  34. }
  35. }

  

十二、线程库

        在另一篇文章进行详细介绍,未完待续......

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/765355
推荐阅读
相关标签
  

闽ICP备14008679号