初始化
2018/9/19 17:46:55 这篇罗列概念很没水平,就别看了。
后记:这篇笔记限于罗列书中的概念,自己的理解并不多,组织章节也不好。后来又整理过一篇: 《再谈初始化》 ,两者不分先后,没有依赖关系。
参考《C++ Primer》和网上资源誊抄完正文后,自己总结如下:
定义一个变量/对象时,如果不进行显式地初始化,就是默认初始化(除了使用
extern
进行纯粹的声明的)。需要关注的是,默认初始化时编译器究竟赋予了变量/对象什么内容。默认初始化变数太多,比如造成内置类型未定义行为,对于使用编译器合成的默认构造函数的类也可能产生不良影响,所以在定义变量时最好进行初始化!初始化的语法格式有好多种呢:
- 根据是否使用等号分为拷贝初始化和直接初始化;
- 根据是否使用了花括号,区分出列表初始化;
想在初始化时偷懒,不想写太多参数(
vector
值初始化);但又操心默认初始不好使(new
动态分配时值初始化)。所以,又出现了值初始化。
值初始化和(拷贝初始化、直接初始化、列表初始化)是不同的概念。后者说明的是“用什么样的语法格式来初始化变量”,而值初始化不但要说明“用什么样的语法格式来初始化变量 “( C++中用
new
开辟的空间通过在要初始化的空间的类型名后跟()
来启用值初始化,而库类型则自动对未初始化的变量启用值初始化 ),而且还要说明是”用什么值来初始化变量”。
- 当变量为内置类型,值初始化用
0
来对其初始化。- 当变量为类类型,值初始化用该类的默认构造函数初始化。
- 当变量为类类型且没有默认构造函数时,值初始化要求程序员提供初始化值。
说明:值初始化通常用在初始化一段连续的内存区( 如
vector
容器,new
一段空间 )。
在个人编码过程中推荐列表初始化方式,整篇文章整理的知识点其实主要针对阅读代码。在 C++11 之后,列表初始化其实是最一般、最通用的初始化方式:
1 | [new] T [object] { arg1, arg2, ... }; |
Default Initialization 默认初始化(P39)
如果定义变量时没有指定初值,则变量被默认初始化,此时变量被赋予了“默认值”。默认值到底是什么由变量类型决定,同时定义变量的位置也会对此有影响。
内置类型的变量未被显式初始化时:
- 定义于任何函数体之外的变量被初始化为
0
; - 定义在函数体内部的内置类型变量将不被初始化
1 |
|
1 | niel@debian8light:~/code/test_initialize$ g++ --version |
类的对象如果没有显式地初始化,则其值由类确定
std::string
不是内置类型,而是类!
Initialization 初始化(P39)
C++语言定义了初始化的好几种不同方式:
1 | int units_sold = 0; |
写在前面:等号 =
可以用来初始化,也可以用来赋值。我们可能更熟悉、更习惯 =
的赋值操作。事实上在 C++ 语言中,初始化和赋值是两个完全不同的概念,所以 =
在这两种情况下的应用也是两个完全不同的操作。
显式初始化(P76):强调“显式”是为了与“默认初始化”的“未指定初值”区分。在大多情况下,谈及“初始化”都是指非“默认初始化”的情况。“初始化”一般不包括“默认初始化”!
按照初始化时是否使用等号分为拷贝初始化和直接初始化;按照是否使用了花括号,区分出列表初始化。
copy initialization 拷贝初始化
如果使用等号(=
)初始化一个变量,实际上执行的是拷贝初始化(copy initialization),编译器把等号右侧的初始值拷贝到新创建的对象中去。
direct initialization 直接初始化
与拷贝初始化相反,如果不使用等号,则执行的是直接初始化(direct initialization)。
List Initialization 列表初始化(P39)
作为 C++11 新标准的一部分,用花括号来初始化变量得到了全面应用,而在此之前,这种初始化的形式仅在某些受限的场合下才能使用。
1 | int units_sold = {0}; |
注意事项(P88)
在大多数情况下这些初始化方式(显式的)可以相互等价地使用,不过也并非一直如此:
使用拷贝初始化时(即使用
=
时),只能提供一个初始值;当(显式地)初始化只使用一个值时,使用直接初始化或拷贝初始化都行。如果像
string s4(n, 'c');
这样初始化时要用到的值有多个,一般来说只能使用直接初始化的方式:(P76)1
2
3string s5 = "hiya"; // 拷贝初始化
string s6("hiya"); // 直接初始化
string s7(5, 'c'); // 直接初始化,s7 的内容是 ccccc如果提供的是一个类内初始值(in-class initializer),则只能使用拷贝初始化或使用花括号的形式初始化;
创建对象时,类内初始值将用于初始化数据成员。没有初始值的成员将被默认初始化。对类内初始值的限制与之前介绍的类似(原文:In-class initializers are restricted as to the form we can use: They must either be enclosed inside …):或者放在花括号里,或者放在等号右边,记住不能使用圆括号。(P65)
1
2
3
4
5struct S ales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};如果提供的是初始元素值的列表,则只能把初始值都放在花括号里进行列表初始化,而不能放在圆括号里;
1
2vector<string> v1{"a", "an", "the"}; // 列表初始化
vector<string> v1("a", "an", "the"); // 错误
Value initialization 值初始化
初始化 vector
对象(P88)
我们可以只提供 vector
对象容纳的元素数量而略去初始值(原文:We can usually omit the value and supply only a size. )。此时库会创建一个值初始化的元素初值(原文:a value-initialized element initializer),并把它赋给容器中的所有元素,这个初值由 vector
对象中元素的类型决定。
如果 vector
对象的元素是内置类型,比如 int
,则元素初始值自动设为0
。如果元素是某种类类型,比如 std::string
,则元素由类默认初始化。
1 | vector<int> ivec(10); //10个元素,每个都初始化为0 |
对这种初始化的方式有两个特殊限制:
有些类要求必须明确地提供初始值,如果
vector
对象中元素的类型不支持默认初始化,我们就必须提供初始的元素值。对这种类型的对象来说,只提供元素的数量而不设定初始值无法完成初始化工作。如果只提供了元素的数量而没有设定初始值,只能使用直接初始化:
1
vector<int> vi = 10; //错误,必须使用直接初始化的形式指定向量的大小
这里的
10
是用来说明如何初始化vector
对象的,我们用它的本意是想创建含有 10 个值初始化了的元素的vector
对象,而非数字 10 “拷贝”vector
中。因此,此时不宜使用拷贝初始化。
使用 new
动态分配和初始化对象(P407)
默认初始化
默认情况下,动态分配的对象是默认初始化的,这意味着内置类型或组合类型的对象的值是未定义的,而类类型对象将执行默认构造函数进行初始化。
1 | string *ps = new string; //初始化为空string |
直接初始化
我们可以使用直接初始化方式来初始化动态分配的对象。我们可以使用传统的构造方式,在新标准下,也可以使用列表初始化(使用花括号):
1 | int *pi = new int(1024); //pi指向的对象的值为1024 |
值初始化
也可以对动态分配的对象进行值初始化,只需在类型名之后跟一对空括号即可:
1 | string *ps1 = new string; //默认初始化为空string |
对于定义了自己的构造函数的类类型来说,要求值初始化是没有意义的;不管采用什么方式,对象都会通过默认构造函数来初始化。但对于内置类型,两种形式的差别就大了;值初始化的内置类型对象有着良好定义的值,而默认初始化的对象的值则是未定义的。类似的,对于类中那些依赖于编译器合成的默认构造函数的内置类型成员,如果它们未在类内初始化,那么它们的值也是未定义的。