C++ 对象初始化

初始化是在创建对象时赋予其一个初始值(对象是指一块能存储数据并具有某种类型的内存空间)。

注意初始化与赋值不同,赋值是把对象的当前值擦除,并以新值代替。

注意:并不是靠“=”判断是否为赋值(“=”出现也可能是拷贝初始化);判断是赋值还是初始化要看其本质的区别:若对象尚无初值且正在赋予其初始值则为初始化);若对象已有的当前值被擦除,并且被新值的代替,则为赋值。

一、内置类型初始化

int test{5}; //列表初始化
int test = 5; //拷贝初始化 (调用了拷贝构造函数)
int test={5}; //拷贝初始化
int test(5);  //直接初始化; 注意:如果内置类型作为类的数据成员则不能使用()的方式初始化。
int test;    //执行默认初始化;注意:若定义在函数体外部则被初始化为0,若定义在函数体内部则它的值是未定义的,试图拷贝或以其他形式访问此类值将引发错误。

 注意:定义在块中的内置类型或复合类型(如数组和指针)的对象如果执行默认初始化,则他们的值是未被定义的。未定义的变量含有一个不确定的值,会带来无法预计的后果,包括程序崩溃、或者程序可以执行但结果时对时错无法把握。因此,内置类型和复合类型执行默认初始化并不安全,建议初始化每一个内置类型和复合类型的变量(无论是单独出现的内置类型,还是在类内作为数据成员)。

二、类的数据成员初始化

类通过构造函数控制其对象的初始化。

  • 默认构造函数无任何实参。
  • 如果用户未定义任何构造函数,编译器会合成默认构造函数。
  • 一旦用户定义了构造函数,编译器就不再自动生成默认构造函数;如果用户仍然需要使用合成默认构造函数,需要使用=default定义;

类的数据成员按照在类中声明的顺序初始化。其初始化方式按照优先级排列如下:

  1. 构造函数初始值列表:如果构造函数初始值列表中存在显示初始化的数据成员,则优先使用构造函数初始值列表初始化其数据成员。构造函数初始值列表名字后面紧跟的初始值可以用(),也可以用{}括起来。 
  2. 类内初始值:如果类的数据成员存在类内初始值,则使用类内初始值初始化数据成员。类内初始值可以放在花括号{}或者拷贝初始化(放在等号=右边),注意:类内初始值不能使用圆括号!!
  3. 默认初始化:如果构造函数初始值列表和类内都没有对应的数据成员初始值,则执行默认初始化。类的数据成员如果是内置类型的或者是复合类型的,当被执行默认初始化时,它们的值是未被定义的。因此,注意:如果类的数据成员中含有内置类型或者复合类型的,必须在构造函数初始值列表初始化或者提供类内初始值。

注意:构造函数的函数体运行之前,类的数据成员就已经通过以上3步完成了初始化。因此,构造函数的函数体用于执行初始化后的一些修改操作,不要指望在构造函数的函数体内初始化。

  • 如果构造函数初始值列表和类内初始值提供的值不同,则以构造函数初始值列表提供的值为准。
  • 合成的默认构造函数无构造函数初始化列表,对数据成员初始化时依次使用类内初始值和默认初始化方式执行(即执行上述步骤2和3)。

2.1 类的数据成员为内置类型

如果数据成员中含有内置类型,必须提供类内初始值或用户必须自定义构造函数初始化(即使用构造函数初始化列表)。不能使用默认初始化。

内置类型包括:bool,char, wchar_t, char16_t, char32_t,short,int, long, long long, float, double, long double。

注意:容器vector, string等不是内置类型。

class test_class{
    test_class(int member_):member1(member_),member2{5},member3(5){}

private: 
    int member1; //利用构造列表初始值提供的初始值进行初始化,构造函数初始值列表可以使用()或者{}
    int member2;
    int member3;

    int member4{5}; //类内初始值可以使用{}或=,但不能使用()
    int member5 = 6;

    int member6;  //错误:类的数据成员是内置类型,必须在构造函数列表初始化或提供类内初始值,否则它的值是未定义的。
}

2.2 类的数据成员为复合类型

如果数据成员中含有复合类型,必须提供类内初始值或用户必须自定义构造函数初始化(即使用构造函数初始化列表)。不能使用默认初始化。

复合类型包括:指针,引用。

class test_class{
    test_class(int member_):member1(nullptr),member2{member_}{}

private: 
    int* member1; 
    int &member2;
}

2.3 类的数据成员含有const限定符

如果数据成员中含有const限定符,必须提供类内初始值或用户必须自定义构造函数初始化(即使用构造函数初始化列表)。不能使用默认初始化。

class test_class{
    test_class(int member_):member1{member_}{}

private: 
    const int member1; 
}

2.4 类中含有类类型的数据成员

类中如果包括其他类类型的数据成员,且该类类型的数据成员没有默认构造函数,则编译器无法初始化该成员,必须自定义构造函数。初始化类类型的数据成员的规则与上面构造函数初始值列表或类内初始值的方式一致。

示例:

class Membertarget{ 
    Membertarget(int Parameter1, int Parameter2){}
}  //定义一个类Subtarget,在其他类中可以像使用内置类型一样将该类的对象作为成员对象  

class Target{
    Target(int P1, int P2):membertarget{P1,P2}{}  //在构造函数初始化列表中进行初始化,构造函数初始值列表中也可以使用subtarget(4,8)的形式。
    Membertarget membertarget;
}

或者:

class Membertarget{ 
    Membertarget(int Parameter1, int Parameter2){}
}  //定义一个类Subtarget,在其他类中可以像使用内置类型一样将该类的对象作为成员对象  

class Target{
    Target(){} 
    Membertarget membertarget{4,8};//提供类内初始值 !!注意:不能使用()括号方式初始化,如果写成Membertarget membertarget(4,8),会报错:error:expected identifier before numeric constant
}

2.5 类的数据成员为容器

数据成员为容器时,可以执行默认初始化。std中每个容器类型都存在默认构造函数。

C c; //C如果为array,则c中元素都按照默认初始化方式初始化;C如果为string,vector,deque, list,forward_list,则c的初始化为空,例如:vector<T> v1 默认初始化,v1为空的vector,它潜在元素是T类型的;

2.6 派生类的数据成员初始化

派生类含有从基类继承的数据成员,但派生类不能直接初始化这些成员,而是调用基类的构造函数初始化从基类继承而来的数据成员。

class Fathertarget{ 
    Fathertarget(int Parameter1, int Parameter2){};
}  

class Target:public Fathertarget{
    Target(int P1, int P2):Fathertarget{P1,P2}{} 
}

三、对象声明与定义的区别

变量声明规定了变量的类型和名字;

变量定义除了规定了变量的类型和名字外还申请存储空间,实现初始化。

内置类型:

int i;   //变量定义
extern int i; //变量声明

类类型:

class Test; //类的声明

class Test{  //类的定义
    int member; //成员的声明;(注意与变量定义的区别)
};
Test test; //对象的定义

注意区分类的定义和对象的定义,对象的定义是使用类创建对象,在对象定义中实现类的数据成员的初始化。 

猜你喜欢

转载自blog.csdn.net/Cxiazaiyu/article/details/105705004