构造函数和析构函数的语法

1
2
3
4
5
class A{
public:
A(){}
~A(){}
}

无返回值,函数名同类名,构造函数在对象生成时被调用,用于初始化,析构函数在对象销毁时被调用,用于扫尾

构造函数可以缺省, 也有多个(重载)。析构函数只能有一个,也可以缺省。

构造函数分类

1.默认构造函数

编译器自己提供,不用写出来(写出来的构造函数就是用户自定义构造函数了,即使用户写的没有参数,也不叫默认构造,只能说是无参),没有参数,函数体为空,不执行任何操作(包括初始化)

2.带参构造函数

用户自定义的构造函数,可以传入参数对数据成员进行初始化,也可以在函数体中执行其他操作。注意的是,参数可以被赋默认值,比如说A(int n = 0){},在没有被赋默认值的时候,一旦定义了带参构造函数,那么在定义对象时候,必须传入参数初始化这个对象,A a(2); 当然如果赋了默认值,就可以不传参数,此时就为默认值。另外注意的是,在赋默认值的时候,必须从左到右依次赋值,比如下面的例子

1
2
3
4
5
6
7
8
9
10
A(int a = 5,int b = 6,int c = 2){}//这种就是对的
A(int a = 5,int b,int c = 2){}//错
A(int a = 5,int b = 6,int c){}//错
/*
如果没有这个规则,试想如何定义一个对象呢,中间那个变量没有参数,不就会发生歧义吗
定义了默认值的变量,定义对象时参数可以缺省,例子如下
*/
A b(1,2,3);
A b(1,2);//假设第三个变量有默认值
A b;//假设三个变量都有默认值

3.无参构造函数

没有参数,定义对象时,不用传参数去初始化,不属于默认构造函数

4.转换构造函数(后补)

5.复制构造函数(后补)

6.转移构造函数(不确定补不补)

构造函数的特性:初始化列表

语法

1
2
3
A(int a,int b):m_p1(a),m_p2(b){
***函数体***
}

初始化操作的两种方法

1.在函数体内通过赋值语句对数据成员实现初始化

1
2
3
4
A(int a,int b){
m_p1 = a;
m_p2 = b;
}

2.通过参数初始化表来实现对数据成员进行初始化

为什么推荐使用 初始化列表

1.对于基本数据类型而言,两者差异不大,但是对于自定义数据类型而言,比如string类型,在函数体中执行str = name; 首先会调用构造函数,其次调用了重载的赋值运算符。而C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前,因此,使用初始化列表str(name)就只调用了拷贝构造函数,效率可想而知。

2.(基类定义了带参构造函数)由于派生类中构造函数的执行顺序是:基类->派生类。所以,对派生类中的基类数据成员的初始化应先于派生类构造函数的执行,因此不能在派生类构造函数体内写赋值语句来初始化基类成员,而应该在初始化列表的时候就完成。

3.类中有const常量数据成员,必须在初始化列表中初始化,不能使用赋值的方式初始化。这是因为常量是不能被赋值的!

4.子对象初始化顺序优于该类的其他成员,因此也要写到初始化列表

析构和构造的时机

子对象的概念

子对象 :子对象就是该类用其他类定义的数据成员,之所以称之为子对象,因为它不是真正意义上的独立对象,只是该类中的一个数据成员(用其他自定义类定义构造的)。比如说在student类中定义person A; ,这个A就叫做子对象。

推广开来,所谓基类子对象 也很好理解了,由于派生类继承了基类的数据和函数成员,那么基类的数据成员也会存在于派生类中,因为它是由其他类构造的,并且不是真正意义上的对象,所以就叫做子对象,特殊的是,构造它的是基类,所以叫做基类子对象。

构造顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<bits/stdc++.h>
using namespace std;
class A{
public:
A(int n){
cout<<"执行A的构造函数"<<endl;
}
};
class B{
public:
B(int n){
cout<<"执行B的构造函数"<<endl;
}
};
class C:public B{
private:
A a;
public:
C(int n):a(3),B(2){
cout<<"执行C的构造函数"<<endl;
}
};
int main(){
C c(3);
return 0;
}
1
2
3
4
5
6
7
//执行结果
执行B的构造函数
执行A的构造函数
执行C的构造函数
执行C的析构函数
执行A的析构函数
执行B的析构函数

从这个例子中可以总结出来

与派生类构造函数的初始化列表顺序无关,是按如下顺序:

1.基类构造函数(按继承时的顺序)

2.子对象构造函数(按类中声明的顺序)

3.派生类自己的构造函数

析构时:

与构造时相反

注:虽然和初始化列表的顺序无关,但是经常还是按顺序写。2333

动态内存分配的对象

  • 用new分配动态对象时将自动调用构造函数,只有执行delete释放动态对象时才执行析构函数,也就是析构函数的执行顺序与delete运算符的执行顺序相同

    1
    2
    3
    4
    5
    6
    MyClass *p = new MyClass;
    MyClass *q = new MyClass(1);
    MyClass *r = new MyClass(2);
    delete p;
    delete r;
    delete q;
  • 析构顺序p-q-r