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

这些不同的初始化方法都有各自的适用范围和作用,且对于类来说不能用这种初始化的方法,最主要的是没有一种可以通用的初始化方法适用所有的场景,因此C++11中为了统一初始化方式,提出了列表初始化(list-initialization)的概念。

统一的初始化方法

在C++98/03中我们只能对普通数组和POD(plain old data,简单来说就是可以用memcpy复制的对象)类型可以使用列表初始化,如下:

数组的初始化列表: int arr[3] = {1,2,3}

POD类型的初始化列表:

在C++11中初始化列表被适用性被放大,可以作用于任何类型对象的初始化。如下:

由上面的示例代码可以看出,在C++11中,列表初始化不仅能完成对普通类型的初始化,还能完成对类的列表初始化,需要注意的是a3 a4都是列表初始化,私有的拷贝并不影响它,仅调用类的构造函数而不需要拷贝构造函数,a4,a6的写法是C++98/03所不具备的,是C++11新增的写法。

让人惊奇的是在C++11中可以使用列表初始化方法对堆中分配的内存的数组进行初始化,而在C++98/03中是不能这样做的。如下:

int* a = new int {  };
double b = double{ 12.12 };
int * arr = new int[] {, , };

表初始化的一些使用细节

虽然列表初始化提供了统一的初始化方法,但是同时也会带来一些使用上的疑惑需要各位苦逼码农需要注意,比如对下面的自定义类型的例子:

  1. struct A
  2. {
  3. int x;
  4. int y;
  5. }a = {123, 321};
  6. //a.x = 123 a.y = 321
  7. struct B
  8. {
  9. int x;
  10. int y;
  11. B(int, int) :x(0), y(0){}
  12. }b = {123,321};
  13. //b.x = 0  b.y = 0

对于自定义的结构体A来说模式普通的POD类型,使用列表初始化并不会引起问题,x,y都被正确的初始化了,但看下结构体B和结构体A的区别在于结构体B定义了一个构造函数,并使用了成员初始化列表来初始化B的两个变量,,因此列表初始化在这里就不起作用了,b采用的是构造函数的方式来完成变量的初始化工作。

那么如何区分一个类(class struct union)是否可以使用列表初始化来完成初始化工作呢?关键问题看这个类是否是一个聚合体(aggregate),首先看下C++中关于类是否是一个聚合体的定义:

(1)无用户自定义构造函数。
(2)无私有或者受保护的非静态数据成员
(3)无基类
(4)无虚函数
(5)无{}和=直接初始化的非静态数据成员。下面我们逐个对上述进行分析。

1、首先存在用户自定义的构造函数的情况,示例如下:

  1. struct Foo
  2. {
  3. int x;
  4. int y;
  5. Foo(int, int){ cout << "Foo construction"; }
  6. };
  7. int _tmain(int argc, _TCHAR* argv[])
  8. {
  9. Foo foo{ 123, 321 };
  10. cout << foo.x << " " << foo.y;
  11. return 0;
  12. }

输出结果为:Foo construction -858993460 -858993460

可以看出对于有用户自定义构造函数的类使用初始化列表其成员初始化后变量值是一个随机值,因此用户必须以用户自定义构造函数来构造对象。

2、类包含有私有的或者受保护的非静态数据成员的情况

  1. struct Foo
  2. {
  3. int x;
  4. int y;
  5. //Foo(int, int, double){}
  6. protected:
  7. double z;
  8. };
  9. int _tmain(int argc, _TCHAR* argv[])
  10. {
  11. Foo foo{ 123,456,789.0 };
  12. cout << foo.x << " " << foo.y;
  13. return 0;
  14. }

实例中z是一个受保护的成员变量,该程序直接在VS2013下编译出错,error
C2440: 'initializing' : cannot convert from 'initializer-list' to
'Foo',而如果将z变量声明为static则,可以用列表初始化来,示例:

  1. struct Foo
  2. {
  3. int x;
  4. int y;
  5. //Foo(int, int, double){}
  6. protected:
  7. static double z;
  8. };
  9. int _tmain(int argc, _TCHAR* argv[])
  10. {
  11. Foo foo{ 123,456};
  12. cout << foo.x << " " << foo.y;
  13. return 0;
  14. }

程序输出:123 456,因此可知静态数据成员的初始化是不能通过初始化列表来完成初始化的,它的初始化还是遵循以往的静态成员的额初始化方式。

3、类含有基类或者虚函数

  1. struct Foo
  2. {
  3. int x;
  4. int y;
  5. virtual void func(){};
  6. };
  7. int _tmain(int argc, _TCHAR* argv[])
  8. {
  9. Foo foo {123,456};
  10. cout << foo.x << " " << foo.y;
  11. return 0;
  12. }

上例中类Foo中包含了一个虚函数,该程序也是非法的,编译不过的,错误信息和上述一样cannot convert from 'initializer-list' to 'Foo'。

  1. struct base{};
  2. struct Foo:base
  3. {
  4. int x;
  5. int y;
  6. };
  7. int _tmain(int argc, _TCHAR* argv[])
  8. {
  9. Foo foo {123,456};
  10. cout << foo.x << " " << foo.y;
  11. return 0;
  12. }

上例中则是有基类的情况,类Foo从base中继承,然后对Foo使用列表初始化,该程序也一样无法通过编译,错误信息仍然为cannot convert from 'initializer-list' to 'Foo',

4、类中不能有{}或者=直接初始化的费静态数据成员

  1. struct Foo
  2. {
  3. int x;
  4. int y= 5;
  5. };
  6. int _tmain(int argc, _TCHAR* argv[])
  7. {
  8. Foo foo {123,456};
  9. cout << foo.x << " " << foo.y;
  10. return 0;
  11. }

在结构体Foo中变量y直接用=进行初始化了,因此上述例子也不能使用列表初始化方法,需要注意的是在C++98/03中,类似于变量y这种直接用=进行初始化的方法是不允许的,但是在C++11中放宽了,是可以直接进行初始化的,对于一个类来说如果它的非静态数据成员使用了=或者{}在声明同时进行了初始化,那么它就不再是聚合类型了,不适合使用列表初始化方法了。

在上述4种不再适合使用列表初始化的例子中,需要注意的是一个类声明了自己的构造函数的情形,在这种情况下使用初始化列表是编译器是不会给你报错的,操作系统会给变量一个随机的值,这种问题在代码出BUG后是很难查找到的,因此这种情况不适合使用列表初始化需要特别注意,而其他不适合使用的情况编译器会直接报错,提醒你这些场景下使用列表初始化时不合法的。

那么是否有一种方法可以使得在类不是聚合类型的时候可以使用列表初始化方法呢?相信你肯定猜到了,作为一种很强大的语言不应该也不会存在使用上的限制。自定义构造函数+成员初始化列表的方式解决了上述类是非聚合类型使用列表初始化的限制。看下面的例子:

  1. struct Foo
  2. {
  3. int x;
  4. int y= 5;
  5. virtual void func(){}
  6. private:
  7. int z;
  8. public:
  9. Foo(int i, int j, int k) :x(i), y(j), z(k){ cout << z << endl; }
  10. };
  11. int _tmain(int argc, _TCHAR* argv[])
  12. {
  13. Foo foo {123,456,789};
  14. cout << foo.x << " " << foo.y;
  15. return 0;
  16. }

输出结果为
789 123 456
,可见,尽管Foo中包含了私有的非静态数据以及虚函数,用户自定义构造函数,并且使用成员列表初始化方法可以使得非聚合类型的类也可以使用列表初始化方法,因此在这里给各位看官提个建议,在对类的数据成员进行初始化的时候尽量在类的构造函数中用成员初始化列表的方式来对数据成员进行初始化,这样可以防止一些意外的错误。

初始化列表

在上面的使得一个类成为非聚合类的例子2、3、4中,这些非法的用法编译器都报出的错误是cannot
convert from 'initializer-list' to
'Foo',那么这个initializer-list是什么呢?为什么使用列表初始化方法是将initializer-list转换成对应的类类型呢?下面我们就来看看这个神秘的东西

1、任何长度的初始化列表

在C++11中,对于任意的STL容易都与和为显示指定长度的数组一样的初始化能力,如:
  1. int arr[] = { 1, 2, 3, 4, 5 };
  2. std::map < int, int > map_t { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
  3. std::list<std::string> list_str{ "hello", "world", "china" };
  4. std::vector<double> vec_d { 0.0,0.1,0.2,0.3,0.4,0.5};

STL容易跟数组一样可以填入任何需要的任何长度的同类型的数据,而我们自定义的Foo类型却不具备这种能力,只能按照构造函数的初始化列表顺序进行依次赋值。实际上之所以STL容易拥有这种可以用任意长度的同类型数据进行初始化能力是因为STL中的容器使用了std::initialzer-list这个轻量级的类模板,std::initialzer-list可以接受任意长度的同类型的数据也就是接受可变长参数{...},那么我们是否可以利用这个来改写我们的Foo类,是的Foo类也具有这种能力呢?看下面例子:

  1. struct Foo
  2. {
  3. int x;
  4. int y;
  5. int z;
  6. Foo(std::initializer_list<int> list)
  7. {
  8. auto it= list.begin();
  9. x = *it++;
  10. y = *it++;
  11. z = *it++;
  12. }
  13. };
  14. int _tmain(int argc, _TCHAR* argv[])
  15. {
  16. Foo foo1 {123,456,789};
  17. Foo foo2 { 123, 456};
  18. Foo foo3{ 123};
  19. Foo foo4{ 123, 456, 789,258 };
  20. cout << foo1.x << " " << foo1.y << " " << foo1.z<<endl;
  21. cout << foo2.x << " " << foo2.y << " " << foo2.z << endl;
  22. cout << foo3.x << " " << foo3.y << " " << foo3.z << endl;
  23. cout << foo4.x << " " << foo4.y << " " << foo4.z << endl;
  24. return 0;
  25. }
  1. 程序的输出结果为:
  1. 123 456 789
  2. 123 456 -858993460
  3. 123 -858993460 -858993460
  4. 123 456 789

在上面的例子中我们用std::initialzer-list将类改写,是的类Foo也具有了接受可变长参数的能力,在Foo类中定义了三个变量,分别在main函数中使用1个2个3个4个参数的列表初始化方法来初始化foo变量,可见,由程序的输出结果可知,对于这种拥有固定数目的数据成员来说使用std::initialzer-list来改写后,如果列表初始化的参数刚好是3个则数据成员完全初始化,如果列表初始化的个数小于3个则未给予的值是一个随机值,而大于3个的话超出的列表初始化参数将被抛弃。虽然std::initialzer-list可以改写我们自定义的类,但是对于用于固定的数据成员的类来说这种改写意义不大,若列表初始化个数少于数据成员个数则会导致某些数据成员未被初始化,容易引起程序出BUG,BUT如果我们自定义的类也是一个容器类情况呢?

  1. class FooVec
  2. {
  3. public:
  4. std::vector<int> m_vec;
  5. FooVec(std::initializer_list<int> list)
  6. {
  7. for (auto it = list.begin(); it != list.end(); it++)
  8. m_vec.push_back(*it);
  9. }
  10. };
  11. int _tmain(int argc, _TCHAR* argv[])
  12. {
  13. FooVec foo1 { 1, 2, 3, 4, 5, 6 };
  14. FooVec foo2 { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  15. return 0;
  16. }

可见,用std::initialzer-list改写我们自定义的容器类可以使得我们自定义的容器类和STL中的容器类一样拥有接受可变长相同数据类型的数据的能力,注意数据类型必须相同。
std::initialzer-list不仅可以用于自定义类型的列表初始化方法,也可以用于传递相同类型数据的集合:

  1. void func(std::initializer_list<int> list)
  2. {
  3. for (auto it = list.begin(); it != list.end(); it++)
  4. {
  5. cout << *it << endl;
  6. }
  7. }
  8. int _tmain(int argc, _TCHAR* argv[])
  9. {
  10. func({});//传递一个空集
  11. func({ 1, 2, 3 });//传递int类型的数据集
  12. return 0;
  13. }

因此在以后碰到需要使用可变长度的同类型的数据时,可以考虑使用std::initialzer-list。

2、std::initialzer-list的使用细节

简单了解了initialzer-list后,看看它拥有哪些特点呢?
1、它是一个轻量级的容器类型,内部定义了迭代器iterator等容器必须的一些概念。
2、对于initialzer-list<T>来说,它可以接受任意长度的初始化列表,但是元素必须是要相同的或者可以转换为T类型的。
3、它只有三个成员接口,begin(),end(),size(),其中size()返回initialzer-list的长度。
4、它只能被整体的初始化和赋值,遍历只能通过begin和end迭代器来,遍历取得的数据是可读的,是不能对单个进行修改的。
类似下面的操作时不合法的
  1. std::initializer_list<int> list_t ={ 1, 2, 3, 4 };
  2. int _tmain(int argc, _TCHAR* argv[])
  3. {
  4. for (auto it = list_t.begin(); it != list_t.end; it++)
  5. (*it) = 1;
  6. return 0;
  7. }

此外initialzer-list<T>保存的是T类型的引用,并不对T类型的数据进行拷贝,因此需要注意变量的生存期。比如我们不能这样使用:

  1. std::initializer_list<int> func(void)
  2. {
  3. auto a = 2, b = 3;
  4. return{ a, b };
  5. }

虽然看起来没有任何问题,且能正常编译通过,但是a,b是在func内定义的局部变量,但程序离开func时变量a,b就销毁了,initialzer-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++98/03的方式类型收窄并不会编译报错,但是将会导致一些隐藏的错误,导致出错的时候很难定位,而利用C++11的列表初始化方法定义变量从源头了遏制了类型收窄,使得不恰当的用法就不会用在程序中,避免了某些位置类型的错误,因此建议以后再实际编程中尽可能的使用列表初始化方法定义变量。

C++11 列表初始化的更多相关文章

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

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

  2. C++11的初始化列表

      初始化是一个非常重要的语言特性,最常见的就是对对象进行初始化.在传统 C++ 中,不同的对象有着不同的初始化方法,例如普通数组.POD (plain old data,没有构造.析构和虚函数的类或 ...

  3. c++11之初始化列表

    一.前言     C++的学习中.我想每一个人都被变量定义和申明折磨过,比方我在大学笔试过的几家公司.都考察了const和变量,类型的不同排列组合,让你差别有啥不同.反正在学习C++过程中已经被折磨惯 ...

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

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

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

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

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

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

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

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

  8. 列表初始化 分析initializer_list<T>的实现

    列表初始化(1)_统一初始化 1. 统一初始化(Uniform Initialization) (1)在C++11之前,很多程序员特别是初学者对如何初始化一个变量或对象的问题很容易出现困惑.因为可以用 ...

  9. C++统一初始化语法(列表初始化)

    引言 要是世上不曾存在C++14和C++17该有多好!constexpr是好东西,但是让编译器开发者痛不欲生:新标准库的确好用,但改语法细节未必是明智之举,尤其是3年一次的频繁改动.C++带了太多历史 ...

随机推荐

  1. copy 深浅复制

  2. hdu 2647 Reward(拓扑排序+反图)

    题目链接:https://vjudge.net/contest/218427#problem/C 题目大意: 老板要给很多员工发奖金, 但是部分员工有个虚伪心态, 认为自己的奖金必须比某些人高才心理平 ...

  3. 获取AFP共享的文件夹及其权限

    获取AFP共享的文件夹及其权限   获取AFP服务的认证信息后,渗透测试人员就可以使用afp-showmount脚本获取共享的文件夹信息,以及各级用户权限信息.其中,用户包括所有者.组.Everyon ...

  4. 一些日常工具集合(C++代码片段)

    一些日常工具集合(C++代码片段) ——工欲善其事,必先利其器 尽管不会松松松,但是至少维持一个比较小的常数还是比较好的 在此之前依然要保证算法的正确性以及代码的可写性 本文依然会持久更新,因为一次写 ...

  5. Unity3D 入门 游戏开发 Unity3D portal game development

    Unity3D 入门 游戏开发 Unity3D portal game development 作者:韩梦飞沙 Author:han_meng_fei_sha 邮箱:313134555@qq.com ...

  6. Servlet中的过滤器

    在web.xml中配置:(用eclipse工具,可以在创建filter的时选择,web.xml中的配置可以自动生成) <filter> <display-name>LoginF ...

  7. oracle 列相减——(Oracle分析函数Lead(),Lag())

    lag和lead函数,用于取出数据的前n行的数据和后n行的数据,当然要和over(order by)一起组合 其实这2个函数的作用非常好理解,Lead()就是取当前顺序的下一条记录,相对Lag()就是 ...

  8. event对象中 target和currentTarget 属性的区别。

    首先本质区别是: event.target返回触发事件的元素 event.currentTarget返回绑定事件的元素

  9. Launch 启动全屏 隐藏上方状态栏

    1:statusBar字体为白色 在plist里面设置View controller-based status bar appearance 为 NO:设置statusBarStyle 为 UISta ...

  10. windows环境下面批量新建文件夹

    md D:批量新建文件夹\2026 md D:批量新建文件夹\2030 md D:批量新建文件夹\2032 md D:批量新建文件夹\1835 md D:批量新建文件夹\1832 电脑桌面新建文档 - ...