C++中构造函数和析构函数与虚函数之间的关系要点

首先对构造函数、析构函数、虚函数的概念作出定义。

构造函数是类的同名成员函数,当创建类对象时它会自动执行,一般负责对类进行初始化、分配资源。
public:
类名(参数)
{
p = new int;
}
注意:
构造函数的访问属性必须是public否则无法创建对象;
构造函数可以重载,可以有多个版本的构造函数;
调用带参数的构造函数:Test t(实参);
默认情况下编译器自动生成一个没有参数的,什么都不做的构造函数,如果一旦显式实现带参数的构造函数,则无参构造不生成,Text t调用无参构造时,如果没有无参构造,语句报错;
可以通过设置默认形参来达到无参构造的效果;
构造函数没有返回值;
不使用malloc为类对象分配内存,因为malloc不会调用类的构造函数。

析构函数是负责对类对象进行收尾,比如释放类中的资源,保存数据等,当类对象销毁时自动调用。
public:
类名(参数)
{
p = new int;
}
~类名()
{
delete p;
}
注意:
析构函数也必须是public;
析构函数没有参数,没有返回值,不能重载;
类对象的生命周期完结时,并且调用delete销毁对象时才会自动调用析构函数;
构造函数肯定会执行,但析构不一定;
不要使用free来销毁对象,因为free不会调用析构函数;
如果没有显式地实现析构函数,则编译器也会自动生成一个什么都不做的析构函数。

当成员函数前加virtual关键词后,这样的函数变成了虚函数,这个类就会像虚继承(当使用virtual修饰继承时,子类会多一个虚指针用于指向父类的内容,当子类被继承时,孙子类会通过父类中的虚指针比较是不是有相同的多份祖先类,如果有多份则只保留一份)一样多了一个虚指针。
虚指针记录一个表格的首地址,而表格中记录的是类中所有虚函数的地址。
如果子类中有父类的虚函数的同名成员函数,此时编译器会比较这两个同名函数的格式,如果完全相同,则把子类中的同名函数的地址覆盖父类中虚函数表中的位置,这叫做函数覆盖(重写),如果不相同则构成隐藏。

弄清楚定义之后要考虑的就是构造函数和析构函数能否是虚函数?
如果构造函数定义为虚函数,那么子类的构造函数会自动覆盖父类的构造函数,当创建子类对象时,子类构造执行前先要调用父类的构造函数,就像造楼先得从地基开始,但是此时父类的构造函数已经被子类的构造函数覆盖了,此时形成了死循环,因此编译器禁止把构造函数定义为虚函数。
析构函数则可以定义为虚函数,当使用类多态时,通过父类的指针或引用指向子类时,如果释放父类对象,默认情况下不会调用子类的析构函数。只有把父类的析构函数定义为虚函数,此时子类的析构函数覆盖了它,当通过父类指针或引用释放子类对象时,会先调用子类的析构函数(多态),子类的析构函数执行完毕后,会自动调用父类的析构函数,完成析构,就像拆楼从楼顶开始往地基拆。这意味着,当使用多态时,而且当子类的析构函数需要释放资源时,一定要把父类的析构函数设置为虚析构。

在执行时,构造函数不可以是虚函数,析构函数可以是虚函数,并且在特定情况下必须设置为虚函数,那在调用时,构造函数和析构函数可以调用虚函数么?
先说结论:不要在构造函数和析构函数中调用虚函数。
如果父类有虚函数,那么编译器会为其创建虚函数表vtbl,并在对象的内存空间创建虚函数指针vptr,虚函数表的原理是每个类会在里面有自己所有能调用到的虚函数地址。
对象的内存空间一般只有虚函数指针vptr和数据成员(包括直接父类和间接父类的),在对象初始化过程中,先构造父类对象再构造子类对象,也就是说当前正在执行父类的构造函数时,执行完初始化列表之后,执行构造函数体之前,编译器插入了初始化vptr的逻辑,令对象的vptr指向父类的vtbl,所以此时无论如何也访问不到子类的vtbl,也就无法调用子类中覆盖了的虚函数,只有执行到子类的构造函数时,才能更新vptr指向子类的vtbl,此后调用的虚函数才是子类中覆盖了的虚函数。
析构是构造的逆,所以在子类的析构函数中的vptr和父类析构中的vptr不同,同样是执行析构函数体之前就被改为对应当前类的vptr。

你可能感兴趣的:(C++,c++)