1. 统一初始化(Uniform Initialization)

(1)在C++11之前,很多程序员特别是初学者对如何初始化一个变量或对象的问题很容易出现困惑。因为可以用小括号、大括号或赋值操作符等多种方式进行初始化

(2)基于这个原因,C++11引入了“统一初始化”的概念。这意味着我们可以使用{}这种通用的语法在任何需要初始化的地方。

【实例分析】初始化列表

 
  1. #include <iostream>
  2. #include <vector>
  3. #include <map>
  4. #include <complex>
  5. using namespace std;
  6.  
  7. //编译选项:g++ -std=c++11 test1.cpp -fno-elide-constructors
  8.  
  9. struct Test
  10. {
  11. int x;
  12. int y;
  13. } test {123, 321}; //等价于test = {123,321}
  14.  
  15. int main()
  16. {
  17. int i; //未初始化
  18. int j{}; //j被初始化为0
  19. int* p; //未初始化
  20. int* q{}; //j被初始化为nullptr
  21.  
  22. int* a = new int{123}; //等价于int* x=new int(123);
  23. double b = double{12.12}; //等价于double(12.12)产生一个临时对象,再拷贝初始化
  24. int* arr = new int[3]{1, 2, 3}; //C++11中新增的初始化堆上数组的方式
  25. std::map<std::string, int> mm {{"2", 1},{"2", 2}, {"3", 3}}; //相当于map<string,int> mm = {...};
  26.  
  27. int values[]{1, 2, 3}; //等价于int values[]={1, 2, 3};
  28. vector<int> v{2, 3, 5, 7, 11, 13, 17};
  29.  
  30. complex<double> c{4.0, 3.0}; //等价于c(4.0, 3.0);
  31. cout << test.x << endl;
  32. cout << test.y << endl;
  33.  
  34. return 0;
  35. }
 

2. 列表初始化的使用细节

(1)引入初始化列表(initializer-list)出现的一些模糊概念

 
  1. //x,y究竟为0,0还是123,321?
  2. struct A
  3. {
  4. int x;
  5. int y;
  6.  
  7. A(int,int):x(0), y(0){} //非聚合类型,使用{}初始化时会调用相应的构造函数
  8. } a = {123, 321}; //a.x=0, a.y=0
 

(2)聚合类型的定义

  ①类型是一个普通类型的数组(如int[10]、char[]、long[2][3])

  ②类型是一个类(class、struct或union),且:

    A.无基类、无虚函数以及无用户自定义的构造函数。

    B.无private或protected的普通数据成员(即非静态数据成员)。

    C.不能有{}和=直接初始化的非静态数据成员(“就地初始化”)

【实例分析】聚合类型与非聚合类型的初始化

 
  1. #include <iostream>
  2. using namespace std;
  3.  
  4. //x,y究竟为0,0还是123,321?
  5. struct A
  6. {
  7. int x;
  8. int y;
  9.  
  10. A(int,int):x(0), y(0){} //非聚合类型,使用{}初始化时会调用相应的构造函数
  11. } a = {123, 321}; //a.x=0, a.y=0
  12.  
  13. struct Base{};
  14.  
  15. //聚合类型的定义
  16. struct Foo : public Base //不能有基类
  17. {
  18. private:
  19. double z; //不能有private的普通成员
  20. static int k; //ok,但必须在类外用int Foo::k = 0的方式初始化
  21. public:
  22. Foo(int x, int y, double z):x(x),y(y),z(z) //不能有构造函数!
  23. {
  24. cout<< "Foo(int x, int y, double z)" << endl;
  25. }
  26.  
  27. Foo(const Foo& foo) //不能有构造函数!
  28. {
  29. this->x = foo.x;
  30. this->y = foo.y;
  31. this->z = foo.z;
  32.  
  33. cout<< "Foo(const Foo& foo)" << endl;
  34. }
  35.  
  36. int x;
  37. int y; //不能通过int y=0;或int y{0}来"就地初始化"
  38. virtual void F(){}; //不能有虚函数!
  39.  
  40. };
  41.  
  42. int main()
  43. {
  44. Foo f1(1, 2, 3.0); //直接调用构造函数初始化
  45. Foo f2{4, 5, 6.0}; //由于Foo是个非聚合类型,使用{}时会调用相应的构造函数来初始化。
  46. Foo f3 = {7, 8, 9.0}; //非聚合类型会调用构造函数来初始化
  47.  
  48. cout <<"a.x = " << a.x << ", a.y = " << a.y << endl;
  49.  
  50. return 0;
  51. }
  52. /*输出结果
  53. Foo(int x, int y, double z)
  54. Foo(int x, int y, double z)
  55. Foo(int x, int y, double z)
  56. a.x = 0, a.y = 0
  57. */
 

(3)注意事项

  ①聚合类型的定义是非递归的。简单来说,当一个类的普通成员是非聚合类型时,这个类也有可能是聚合类型,也就是说可以直接用列表初始化。

  ②对于一个聚合类型,可以直接使用{}进行初始化,这时相当于对其中每个元素分别赋值;而对于非聚合类型,则需要先自定义一个合适的构造函数才能使用{}进行初始化,此时使用初始化列表将调用它对应的构造函数。

 

1. 初始化列表的实现

(1)当编译器看到{t1,t2…tn}时便会生成一个initializer_list<T>对象(其中的T为元素的类型),它关联到一个array<T,n>

(2)对于聚合类型,编译器会将array<T,n>内的元素逐一分解并赋值给被初始化的对象。这相当于为该对象每个字段分别赋值

(3)对于非聚合类型。如果该类存在一个接受initializer_list<T>类型的构造函数,则初始化时会将initializer_list<T>对象作为一个整体传给构造函数。如果不存在这样的构造函数,则array内的元素会被编译器分解并传给相应的能接受这些参数的构造函数(比如列表中有2个元素的,就传给带2个参数的构造函数。有3个元素的,就传给带3个参数的构造函数,依此类推……)。

【实例分析】initializer_list<T>初体验

 
  1. #include <iostream>
  2. #include <vector>
  3. #include <map>
  4. #include <complex>
  5. using namespace std;
  6.  
  7. //编译选项:g++ -std=c++11 test1.cpp -fno-elide-constructors
  8.  
  9. class Foo
  10. {
  11. public:
  12. Foo(int)
  13. {
  14. cout << "Foo(int)"<< endl;
  15. }
  16.  
  17. Foo(int, int)
  18. {
  19. cout << "Foo(int, int)"<< endl;
  20. }
  21.  
  22. Foo(const Foo& f)
  23. {
  24. cout << "Foo(const Foo& f)"<< endl;
  25. }
  26. };
  27.  
  28. int main()
  29. {
  30. Foo f1(123);
  31. Foo f2 = 123; //先将调用Foo(int)将123转为Foo对象,再调用拷贝构造函数(后面这步可能被优化)
  32. Foo f3 = {123}; //生成initializer_list<int>,然后分解元素后,由于列表中只有1个元素,所以将其传给Foo(int)
  33. Foo f4 = {123, 321}; //生成initializer_list<int>,然后分解元素后,由于列表中有两个元素,所以将其传给Foo(int, int)
  34.  
  35. //编译器会为以下花括号形成一个initializer_list<string>,背后有个array<string,6>
  36. //调用vector<string>的构造函数时,编译器会找到一个接受initializer_list<string>
  37. //的重载的构造函数。所有的容器均有这样的构造函数。在这个构造函数里会利用
  38. //initializer_list<string>来初始化。
  39. vector<string> city{"Berlin", "New York", "London", "Cairo","Tokyo", "Cologne"};
  40.  
  41. //编译器会为以下花括号形成一个initializer_list<double>,背后有个array<double,2>。
  42. //调用complex<double>的构造函数时,array内的2个元素被分解并传给
  43. //Comlex<double>(double,double)这个带有两个参数的构造函数。因为comlex<double>并无
  44. //任何接受initializer_list的构造函数。
  45. complex<double> c{4.0, 3.0}; //等价于c(4.0, 3.0)
  46.  
  47. return 0;
  48. }
 

2. initializer_list<T>模板

//initializer_list<T>源码分析

 
  1. #include <iostream>
  2.  
  3. template <class T>
  4. class initializer_list
  5. {
  6. public:
  7. typedef T value_type;
  8. typedef const T& reference; //注意说明该对象永远为const,不能被外部修改!
  9. typedef const T& const_reference;
  10. typedef size_t size_type;
  11. typedef const T* iterator; //永远为const类型
  12. typedef const T* const_iterator;
  13. private:
  14. iterator _M_array; //用于存放用{}初始化列表中的元素
  15. size_type _M_len; //元素的个数
  16.  
  17. //编译器可以调用private的构造函数!!!
  18. //构造函数,在调用之前,编译会先在外部准备好一个array,同时把array的地址传入模板
  19. //并保存在_M_array中
  20. constexpr initializer_list(const_iterator __a, size_type __l)
  21. :_M_array(__a),_M_len(__l){}; //注意构造函数被放到private中!
  22.  
  23. constexpr initializer_list() : _M_array(0), _M_len(0){} // empty list,无参构造函数
  24.  
  25. //size()函数,用于获取元素的个数
  26. constexpr size_type size() const noexcept {return _M_len;}
  27.  
  28. //获取第一个元素
  29. constexpr const_iterator begin() const noexcept {return _M_array;}
  30.  
  31. //最后一个元素的下一个位置
  32. constexpr const_iterator end() const noexcept
  33. {
  34. return begin() + _M_len;
  35. }
  36. };
 

(1)initializer_list是一个轻量级的容器类型,内部定义了iterator等容器必需的概念,本质上是一个迭代器

(2)对于std:: initializer_list<T>而言,它可以接收任意长度的初始化列表,但要求元素必须是同种类型(T或可转换为T)。

(3)它有3个成员函数:size()、begin()和end()

(4)拥有一个无参构造函数,可以被直接实例化,此时将得到一个空的列表。之后可以进行赋值操作,如initializer_list<int> list; list={1,2,3,4,5};

(5)initializer_list<T>在进行复制或赋值时,它内部将保存着列表的地址保存在_M_array中,它进行的是浅拷贝,并不真正复制每个元素,因此效率很高。

【编程实验】打印初始化列表的每个元素

 
  1. #include <iostream>
  2.  
  3. //打印初始化列表的每个元素
  4. void print(std::initializer_list<int> vals)
  5. {
  6. //遍历列表中的每个元素
  7. for(auto p = vals.begin(); p!=vals.end(); ++p){
  8. std::cout << *p << " ";
  9. }
  10.  
  11. std::cout << std::endl;
  12. }
  13.  
  14. //std::initializer_list<T>的浅拷贝。以下的返回值应改为std
  15. //以下的返回值应改为std::vector<int>类型,而不是std::initializer_list<int>类型。
  16. std::initializer_list<int> func(void)
  17. {
  18. int a = 1;
  19. int b = 2;
  20.  
  21. return {a, b}; //编译器看到{a, b}时,会做好一个array<int,2>对象(其生命
  22. //期直至func结束),然后再产生一个initializer_list<int>
  23. //临时对象,由于initializer_list<int>采用的是浅拷贝,当
  24. //函数返回后array<int,2>会被释放,所以无法获取到列表中的元素!
  25. }
  26.  
  27. int main()
  28. {
  29. print({1,2,3,4,5,6,7,8,9,10});
  30.  
  31. print(func());
  32.  
  33. return 0;
  34. }
  35. /*测试结果:
  36. e:\Study\C++11\7>g++ -std=c++11 test1.cpp
  37. e:\Study\C++11\7>a.exe
  38. 1 2 3 4 5 6 7 8 9 10
  39. */
 

3. 让自定义的类可以接受任意长度初始化列表

(1)自定义类中重载一个可接受initializer_list<T>类型的构造函数

(2)在该构造函数中,遍历列表元素并赋值给相应的字段。

【编程实验】自定义类的初始化列表

 
  1. #include <iostream>
  2. #include <map>
  3.  
  4. using namespace std;
  5.  
  6. class Foo
  7. {
  8. public:
  9. Foo(int a, int b)
  10. {
  11. cout << "Foo(int a, int b)" << endl;
  12. }
  13.  
  14. Foo(initializer_list<int> list)
  15. {
  16. cout << "Foo(initializer_list<int> list) : ";
  17.  
  18. for(auto i : list){
  19. cout <<i<< " ";
  20. }
  21.  
  22. cout << endl;
  23. }
  24.  
  25. };
  26.  
  27. class FooMap
  28. {
  29. std::map<int, int> content;
  30. using pair_t = std::map<int, int>::value_type;
  31. public:
  32. FooMap(std::initializer_list<pair_t> list)
  33. {
  34. for(auto it = list.begin(); it!=list.end(); ++it){
  35. content.insert(*it);
  36.  
  37. std::cout << "{" << (*it).first <<"," <<(*it).second <<"}" << " ";
  38. }
  39.  
  40. std::cout << std::endl;
  41. }
  42. };
  43.  
  44. int main()
  45. {
  46. Foo f1(77, 5); //Foo(int a, int b), a = 77, b = 5;
  47.  
  48. //注意:由于定义了Foo(initializer_list<int> list)函数,以下3种方
  49. //式的初始化都会将{...}作为一个整体传递给该函数。如果没有定义该函
  50. //数,则由于该类是个非聚合类用{}初始化时,会调用构造函数来初始化。
  51. //但由于Foo类不存在3个参数的构造函数,所以f3那行会编译失败!
  52. Foo f2{77, 5}; //Foo(initializer_list<int> list)
  53. Foo f3{77, 5, 42}; //Foo(initializer_list<int> list)
  54. Foo f4 = {77, 5}; //Foo(initializer_list<int> list)
  55.  
  56. FooMap fm = {{1,2}, {3,4},{5,6}};
  57.  
  58. return 0;
  59. }
  60. /*测试结果:
  61. e:\Study\C++11\7>g++ -std=c++11 test2.cpp
  62. e:\Study\C++11\7>a.exe
  63. Foo(int a, int b)
  64. Foo(initializer_list<int> list) : 77 5
  65. Foo(initializer_list<int> list) : 77 5 42
  66. Foo(initializer_list<int> list) : 77 5
  67. {1,2} {3,4} {5,6}
  68. */

参考文章:

C++11之initialization_list

列表初始化 分析initializer_list<T>的实现的更多相关文章

  1. initializer_list 列表初始化

    initializer_list 列表初始化 用花括号初始化器列表初始化一个对象,其中对应构造函数接受一个 std::initializer_list 参数. #include <iostrea ...

  2. initializer_list、初始化列表、列表初始化

    什么是列表初始化 使用一个花括号来初始化变量,表现形式如下: std::vector<int>a{1,2,3,4,5}; 或者 std::vector<int>a = {1,2 ...

  3. C++11 列表初始化

    在我们实际编程中,我们经常会碰到变量初始化的问题,对于不同的变量初始化的手段多种多样,比如说对于一个数组我们可以使用 int arr[] = {1,2,3}的方式初始化,又比如对于一个简单的结构体: ...

  4. 大括号之谜:C++的列表初始化语法解析

    有朋友在使用std::array时发现一个奇怪的问题:当元素类型是复合类型时,编译通不过. struct S { int x; int y; }; int main() { int a1[3]{1, ...

  5. 第8课 列表初始化(3)_防止类型收窄、explicit关键字

    1. 防止类型收窄 (1)类型收窄:指的是导致数据内容发生变化或者精度丢失的隐式类型转换. (2)类型收窄的几种情况: ①从浮点数隐式转换为整型数,如int i=2.2; ②从高精度浮点数隐式转换为低 ...

  6. c++11——列表初始化

    1. 使用列表初始化 在c++98/03中,对象的初始化方法有很多种,例如 int ar[3] = {1,2,3}; int arr[] = {1,2,3}; //普通数组 struct A{ int ...

  7. C++2.0新特性(二)——<一致性初始化、Initializer_list 、for循环、explicit>

    一.一致性初始化(uniform initialization) 之前初始化时存在多个版本,让使用者使用时比较混乱,现在提供一种万用的初始化方法,就是使用大括号. 原理解析:当编译器看到大括号包起来的 ...

  8. C++11之列表初始化

    1. 在C++98中,标准允许使用花括号{}来对数组元素进行统一的集合(列表)初始化操作,如:int buf[] = {0};int arr[] = {1,2,3,4,5,6,7,8}; 可是对于自定 ...

  9. C++11常用特性介绍——列表初始化

    一.列表初始化 1)C++11以前,定义初始化的几种不同形式,如下: int data = 0;   //赋值初始化 int data = {0};   //花括号初始化 int data(0); / ...

随机推荐

  1. prometheus学习系列七: Prometheus promQL查询语言

    Prometheus promQL查询语言 Prometheus提供了一种名为PromQL (Prometheus查询语言)的函数式查询语言,允许用户实时选择和聚合时间序列数据.表达式的结果既可以显示 ...

  2. JavaSE理论篇

    将已学过的知识记录在此,既能便于以后温习又能方便知识共享,做到共同成长. 计算机语言发展简史 主要分为三个阶段 机器语言:打点机,有点表示1,没有表示0,打点计时器 低级语言:汇编语言 高级语言:Ja ...

  3. 《剑指Offer》-004 -Java版二叉树先序和中序遍历返回原二叉树

    如题 (总结要点) 注意空值 假定数据是没有问题的 前序(根左右) ,中序(左根右), 故每次的第一个节点就是根节点 没用数组的库函数,自己手写了两个方法 用Java代码写二叉树很舒服, 没有啥指针, ...

  4. canvas小案列-绚丽多彩的倒计时

    本次随笔中,我将实现一个绚丽的倒计时效果,这个效果主要是结合canvas和js实现的,具体代码如下 index.html文件 <!DOCTYPE html> <html> &l ...

  5. 项目Beta冲刺(团队7/7)

    项目Beta冲刺(团队) --7/7 作业要求: 项目Beta冲刺(团队) 1.团队信息 团队名 :男上加男 成员信息 : 队员学号 队员姓名 个人博客地址 备注 221600427 Alicesft ...

  6. 20180524模拟赛T3——Word

    [题目描述] 有一个星球要创造新的单词,单词有一些条件: 字母集有\(p\)个元音和\(q\)个辅音,单词由字母构成 每个单词最多有\(n\)个元音和\(n\)个辅音(同一元音或辅音可重复使用) 每个 ...

  7. cmds在线重定义增加列

    --输出信息采用缩排或换行格式化 EXEC DBMS_METADATA.set_transform_param(DBMS_METADATA.session_transform, 'PRETTY', T ...

  8. 13、Python文件处理、os模块、json/pickle序列化模块

    一.字符编码 Python3中字符串默认为Unicode编码. str类型的数据可以编码成其他字符编码的格式,编码的结果为bytes类型. # coding:gbk x = '上' # 当程序执行时, ...

  9. DSL的本质:领域构建的半成品

    DSL的本质是使用通用和专用语言构建领域的半成品: 实际上是构建了一个世界观.小宇宙的半成品: 这个半成品包含领域的基本要素.联系方式和基本运行规律: 开发者使用这个半成品平台进行开发能达到事半功倍. ...

  10. Good Article Good sentence HDU - 4416 (后缀自动机)

    Good Article Good sentence \[ Time Limit: 3000 ms\quad Memory Limit: 32768 kB \] 题意 给出一个 \(S\) 串,在给出 ...