经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C++ » 查看文章
C++11中列表初始化机制的概念与实例详解
来源:jb51  时间:2021/11/9 15:50:39  对本文有异议

概述

定义:列表初始化是C++11引入的新标准,目的是统一初始化方式

C++11以前只能使用列表初始化来初始化内置类型数组和POD类型对象,C++11中列表初始化可以用于初始化任何类型对象

  • POD(plain old data)类型:仅由内置类型变量构成且不含指针的类,简单来说是可以直接使用memcpy复制的对象
  • 聚合体(aggregate):聚合体一定是POD类型
    • 无自定义构造函数
    • 无私有或保护的非静态数据成员(静态成员与单独对象无关,故不影响初始化)
    • 无基类
    • 无虚函数
    • 无类内已经初始化的非静态数据成员

注意:区分列表初始化和初始化列表

        列表初始化:用{}进行初始化的方式

        初始化列表:构造函数体前对对象成员直接进行初始化的列表

        initializer_list:一种用于未定参数的轻量STL容器

实现机制详解

对内置类型对象、POD对象和类对象的列表初始化实现细节是不同的

POD类型的列表初始化

  • 此处POD类型包括:内置类型、聚合体类
  • 内置类型数组按照顺序初始化
    • C++11标准中列表初始化会防止可能导致潜在信息丢失的类型缩小(即不能像赋值一样将大类型如int隐式转换成小类型如char)
  • 聚合体类按照成员定义顺序依次初始化

含有构造函数的类的列表初始化(C++11)

  • 通过{}进行初始化和()结果一致【即通过()调用构造函数的地方都可以完全等价地用{}代替】,都是直接用括号内的值调用对应构造函数直接初始化对象,并不会先生成临时对象再拷贝
  • ={}与{}是等价的语法【即加不加=对初始化行为没有影响】,均不会调用拷贝运算符或拷贝构造函数
  • 与内置类型的列表初始化一致,C++11的列表初始化只能用于初始化,不能用于已初始化对象的赋值
  • 实际机制猜想:传递的实际参数为initializer_list类型,通过匹配重载函数实现调用【我不知道怎么验证这个过程,求大佬解答】

列表初始化用于函数返回值

  • 在返回值类型为对象(不能是对象的引用)的函数中可以返回{}的列表初始化
  • {}返回值的实际类型为initiallizer list(但不能声明为std::initializer_list),相当于返回构造函数的表达式,因此类型不能是对象的引用

引入std::initializer_list

  • initializer_list为一个轻量级STL模板,声明在头文件<initializer_list>中,定义在命名空间std中
  • 任意的STL容器都与未指定长度的数组有一样的初始化能力,可以填入任何数量的同类型数据,因此可以用STL容器轻易对固定类型的类进行赋值
  • initializer_list是一个轻量级的模板,可以接受任意长度的同类型的数据也就是接受可变长参数,同时作为STL容器它具有STL容器的共同特征(如迭代器)
    • 只有三个成员接口:begin() end() size()
    • 只能被整体的初始化和赋值,迭代器遍历的数据仅可读,不能对单个数据进行修改
  • 所有{}对象都是隐式创建的std::initializer_list类型字面量(右值),广泛用于实现列表初始化(不需要头文件)

代码验证

  1. class testClass
  2. {
  3. private:
  4. int a;
  5. int b;
  6. public:
  7. testClass() :a(0), b(0) {
  8. cout << "default init\n";
  9. }
  10. testClass(int a) :a(a), b(a) {
  11. cout << "sing-val init\n";
  12. }
  13. testClass(int a, int b) :a(a), b(b) {
  14. cout << "val init\n";
  15. }
  16. testClass(testClass& temp) :a(temp.a), b(temp.b) {
  17. cout << "copy init\n";
  18. }
  19. testClass& operator=(testClass& temp) {
  20. //testClass& newobj = *this;
  21. a = temp.a;
  22. b = temp.b;
  23. cout << "copy assign\n";
  24. return *this;
  25. }
  26. testClass& operator=(int x) {
  27. a = x;
  28. b = x;
  29. cout << "int-convert assign\n";
  30. //testClass& newobj = *this;
  31. return *this;
  32. }
  33. testClass& operator++() {
  34. a++;
  35. b++;
  36. }
  37. void printVal(ostream& os) {
  38. os << "a=" << a << "\n";
  39. os << "b=" << b << "\n";
  40. }
  41. };
  42. using tc = testClass;
  43. tc& makeObj(int x, int y)
  44. {
  45. return { x,y };
  46. }
  47. int main()
  48. {
  49. tc a(1, 1); //val init
  50. tc b{ 1,1 }; //val init
  51. tc c = { 1,1 }; //val init
  52. tc d = tc{ 1,1 }; //val init
  53. cout << endl;
  54. tc* e = new tc[2]; //default init *2
  55. cout << endl;
  56. tc* f = new tc[3]{ {1,1},{2,2},{3,3} }; //val init *3
  57. cout << endl;
  58. tc* g = new tc[5]{ {1,1},{1} }; // val init + sing-val init + default init *3
  59. cout << endl;
  60. cout << "testing return val of init_list\n";
  61. tc h = makeObj(2, 2); //val init
  62. tc i = h; //copy init
  63. i = d; //copy assign
  64. i.printVal(cout);
  65. return 0;
  66. }

以下为运行截图

列表初始化测试

添加initializer_list为参数的构造函数后

  1. testClass::testClass(initializer_list<int> list) :a(0), b(0)
  2. {
  3. int ab = 1;
  4. for (auto it = list.begin(); it != list.end(); it++)
  5. {
  6. if (ab)
  7. a += *it;
  8. else
  9. b += *it;
  10. }
  11. cout << "init_list init\n";
  12. }
  13. int main()
  14. {
  15. tc a(1, 1); //val init
  16. tc b{ 1,1 }; //val init
  17. tc c = { 1,1 }; //val init
  18. tc d = tc{ 1,1 }; //val init
  19. cout << endl;
  20. tc* e = new tc[2]; //default init *2
  21. cout << endl;
  22. tc* f = new tc[3]{ {1,1},{2,2},{3,3} }; //val init *3
  23. cout << endl;
  24. tc* g = new tc[5]{ {1,1},{1} }; // val init + sing-val init + default init *3
  25. cout << endl;
  26. cout << "testing return val of init_list\n";
  27. tc h = makeObj(2, 2); //val init
  28. tc i = h; //copy init
  29. i = d; //copy assign
  30. i.printVal(cout);
  31. cout << endl;
  32. cout << "testing argument init_list\n";
  33. tc j = { 1,2,3,4,5,6 };
  34. tc k = { 9 };
  35. return 0;
  36. }

以下为运行截图


添加init_list后测试截图

由此可见所有列表初始化都调用了含有initializer_list为参数的构造函数,证实了列表初始化是基于隐式转换并以initializer_list为底层实现的构想

应用

  • 在声明时直接初始化堆上分配的对象(数组)
    • 类:可以显式指定使用的构造函数(默认会执行无参数的构造函数)
    • 内置类型:可以在分配时直接指定值
  • 在函数返回对象时避免自动存储期对象销毁的问题
  • 手动调用std::initializer_list实现可变参数初始化

列表初始化防止类型收窄

C++11的列表初始化还有一个额外的功能就是可以防止类型收窄,也就是C++98/03中的隐式类型转换,将范围大的转换为范围小的表示,在C++98/03中类型收窄并不会编译出错,而在C++11中,使用列表初始化的类型收窄编译将会报错:

  1. int a = 1.1; //OK
  2. int b{ 1.1 }; //error
  3. float f1 = 1e40; //OK
  4. float f2{ 1e40 }; //error
  5. const int x = 1024, y = 1;
  6. char c = x; //OK
  7. char d{ x };//error
  8. char e = y;//error
  9. char f{ y };//error

总结

列表初始化通过C++11引入的initializer_list容器实现了初始化方式的统一,可以看作一种语法糖

初始化类对象时,通过()调用构造函数的地方都可以完全等价地用{}代替

={}不会生成临时对象再拷贝初始化

到此这篇关于C++11中列表初始化机制的文章就介绍到这了,更多相关C++11列表初始化机制内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号