首页 > 代码库 > 值初始化和默认初始化的区别

值初始化和默认初始化的区别

直接初始化和拷贝初始化

如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化,编译器把等号右侧的初始值拷贝到新创建的对象中去。与之相反,如果不使用等号,则执行的是直接初始化。

当初始值只有一个时,使用直接初始化或拷贝初始化都行。如果用多个值进行初始化的情况,非要用拷贝初始化的方式处理也不是不可以,不过需要显式地创建一个(临时)对象用于拷贝。

string s8=string(10,‘c‘); //拷贝初始化,s8的内容是cccccccccc。

  • C++支持两种初始化形式:直接初始化复制初始化。复制初始化使用“=”符号,而直接初始化将初始化式放在圆括号中。
  • 当用于类类型对象时,初始化的复制形式和直接形式有所不同。
  • 支持初始化的复制形式主要是为了与C的用法兼容。当情况许可时,可以允许编译器跳过复制构造函数直接创建对象,但编译器没有义务这样做。
    • 直接初始化直接调用与实参匹配的构造函数
    • 复制初始化首先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象。
  • 通常直接初始化和复制初始化仅在低级别优化上存在差异。
  • 对于不支持复制的类型,或者使用explicit构造函数,不能进行复制初始化。

值初始化

通常情况下,可以只提供vector对象容纳的元素数量而不用略去初始值。此时库会创建一个值初始化的元素初值,并把它赋给容器中的所有元素,这个初值由vector对象中元素的类型决定。

如果vector对象的元素是内置类型,比如int,则元素初始值自动设为0.如果元素时某种类类型,比如string,则元素由类默认初始化。

vector<int> ivec(10);            //10个元素,每个都初始化为0

vector<string> svec(10);      //10个元素,每个都是空string对象

对这种初始化的方式有两个特殊限制:其一,有些类要求必须明确地提供初始值,如果vector对象中元素的类型不支持默认初始化,我们就必须提供初始的元素值。对这种类型的对象来说,只提供元素的数量而不设定初始值无法完成初始化工作。其二,如果只提供了元素的数量而没有设定初始值,只能使用直接初始化:

vector<int> vi=10;  //错误,必须使用直接初始化的形式指定向量的大小

这里的10是用来说明如何初始化vector对象的,我们用它的本意是想创建含有10个值初始化了的元素的vector对象,而非数字10”拷贝“到vector中。因此,此时不宜使用拷贝初始化。

列表初始值还是元素数量?

在某些情况下,初始化的真实含义依赖于传递初始值时用的是花括号还是圆括号,例如,用一个整数来初始化vector<int>时,整数的含义可能是vector对象的容量也可能是元素的值。类似的,用两个整数来初使化vector<int>时,这两个整数可能一个是vector对象的容量,另一个是元素的初值,也可能它们是容量为2的vector对象中两个元素的初值。通过使用花括号或圆括号可以区分上述这些含义:

vector<int>v1(10);//v1有10个元素,每个的值都是0

vector<int> v2{10};//v2有1个元素,该元素的值时10

vector<int> v3(10,1);//v3有10个元素,每个的值都是1

vector<int> v4{10,1}; //v4有两个元素,值分别是10,1

如果用圆括号,可以说提供的值是用来构造vector对象的。例如,v1的初始值说明了vector对象的容量;v3的两个初始值则分别说明了vector对象的容量和元素的初值。

如果用的是花括号,可以表述成我们想列表初始化该vector对象。也就是说,初始化过程会尽可能地把花括号内的值当成是元素初始值的列表来处理,只有在无法执行列表初始化时才会考虑其他初始化方式。在上例中,给v2和v4提供的初始值都作为元素的值,所以它们都会执行列表初始化,vector对象v2包含一个元素而vector对象v4包含两个元素。

另一方面,如果初始化时使用了花括号的形式但是提供的值又不能用来列表初始化,就要考虑用这样的值来构造vector对象了。例如,要想列表初始化一个含有string对象的vector对象,应该提供能赋给string对象的初值。此时不难区分到底是要列表初始化vector对象的元素还是用给定的容量值来构造vector对象:

vector<string> v5{"hi};  //列表初始化,v5有一个元素

vector<string> v6("hi");//错误,不能使用字符串字面值构造vector对象

vector<string> v7(10);  //v7有10个默认初始化的元素

vector<string> v8{10,"hi"}; //v8有10个值为”hi“的元素

使用new动态分配的默认初始化和值初始化

默认情况下,动态分配的对象时默认初始化的,这意味着内置类型或组合类型的对象的值时未定义的,而类类型对象将默认构造函数进行初始化:

string *ps=new string;  //初始化为空string

int *pi =new int;   //pi指向一个未初始化的int

我们可以使用直接初始化方式来初始化以动态分配的对象。我们可以使用传统的构造方式,在新标准下,也可以使用列表初始化(使用花括号):

int *pi=new int(1024);  //pi指向的对象的值为1024

string *ps=new string(10,‘9‘);   //*派生为“9999999999”

//vector有10个元素,值依次从0-9

vector<int> *pv=new vector<int>{0,1,2,3,4,5,6,7,8,9};

也可以对动态分配的对象进行值初始化,只需在类型名之后跟一对空括号即可

string *ps1=new string ;//默认初始化为空string

string *ps=new string(); //值初始化为空string

int *pi1=new int;   //默认初始化;*pi1的值未定义

int *pi2=new int(); //值初始化为0;*pi2的值为0

对于定义了自己的构造函数的类类型来说,要求值初始化是没有意义的;不管采用什么方式,对象都会通过默认构造函数来初始化。但对于内置类型,两种形式的差别就大了;值初始化的内置类型对象有着良好定义的值,而默认初始化的对象的值则是未定义的。类似的,对于类中那些依赖于编译器合成的默认构造函数的内置类型成员,如果它们未在类内初始化,那么它们的值也是未定义的。