构造函数和析构函数
构造函数和析构函数的语法
1 | class A{ |
无返回值,函数名同类名,构造函数在对象生成时被调用,用于初始化,析构函数在对象销毁时被调用,用于扫尾
构造函数可以缺省, 也有多个(重载)。析构函数只能有一个,也可以缺省。
构造函数分类
1.默认构造函数
编译器自己提供,不用写出来(写出来的构造函数就是用户自定义构造函数了,即使用户写的没有参数,也不叫默认构造,只能说是无参),没有参数,函数体为空,不执行任何操作(包括初始化)
2.带参构造函数
用户自定义的构造函数,可以传入参数对数据成员进行初始化,也可以在函数体中执行其他操作。注意的是,参数可以被赋默认值,比如说A(int n = 0){},在没有被赋默认值的时候,一旦定义了带参构造函数,那么在定义对象时候,必须传入参数初始化这个对象,A a(2); 当然如果赋了默认值,就可以不传参数,此时就为默认值。另外注意的是,在赋默认值的时候,必须从左到右依次赋值,比如下面的例子
1 | A(int a = 5,int b = 6,int c = 2){}//这种就是对的 |
3.无参构造函数
没有参数,定义对象时,不用传参数去初始化,不属于默认构造函数
4.转换构造函数(后补)
5.复制构造函数(后补)
6.转移构造函数(不确定补不补)
构造函数的特性:初始化列表
语法
1 | A(int a,int b):m_p1(a),m_p2(b){ |
初始化操作的两种方法
1.在函数体内通过赋值语句对数据成员实现初始化
1 | A(int a,int b){ |
2.通过参数初始化表来实现对数据成员进行初始化
为什么推荐使用 初始化列表
1.对于基本数据类型而言,两者差异不大,但是对于自定义数据类型而言,比如string类型,在函数体中执行str = name; 首先会调用构造函数,其次调用了重载的赋值运算符。而C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前,因此,使用初始化列表str(name)就只调用了拷贝构造函数,效率可想而知。
2.(基类定义了带参构造函数)由于派生类中构造函数的执行顺序是:基类->派生类。所以,对派生类中的基类数据成员的初始化应先于派生类构造函数的执行,因此不能在派生类构造函数体内写赋值语句来初始化基类成员,而应该在初始化列表的时候就完成。
3.类中有const常量数据成员,必须在初始化列表中初始化,不能使用赋值的方式初始化。这是因为常量是不能被赋值的!
4.子对象初始化顺序优于该类的其他成员,因此也要写到初始化列表
析构和构造的时机
子对象的概念
子对象 :子对象就是该类用其他类定义的数据成员,之所以称之为子对象,因为它不是真正意义上的独立对象,只是该类中的一个数据成员(用其他自定义类定义构造的)。比如说在student类中定义person A;
,这个A就叫做子对象。
推广开来,所谓基类子对象 也很好理解了,由于派生类继承了基类的数据和函数成员,那么基类的数据成员也会存在于派生类中,因为它是由其他类构造的,并且不是真正意义上的对象,所以就叫做子对象,特殊的是,构造它的是基类,所以叫做基类子对象。
构造顺序
1 |
|
1 | //执行结果 |
从这个例子中可以总结出来
与派生类构造函数的初始化列表顺序无关,是按如下顺序:
1.基类构造函数(按继承时的顺序)
2.子对象构造函数(按类中声明的顺序)
3.派生类自己的构造函数
析构时:
与构造时相反
注:虽然和初始化列表的顺序无关,但是经常还是按顺序写。2333
动态内存分配的对象
用new分配动态对象时将自动调用构造函数,只有执行delete释放动态对象时才执行析构函数,也就是析构函数的执行顺序与delete运算符的执行顺序相同
1
2
3
4
5
6MyClass *p = new MyClass;
MyClass *q = new MyClass(1);
MyClass *r = new MyClass(2);
delete p;
delete r;
delete q;析构顺序p-q-r