C++继承时,关于构造函数以及为什么父类析构函数要定义为为虚析构函数的讲解

这个问题主要是由于子类继承父类时,构造函数和析构函数的调用问题产生的结果:

第一点我们需要知道,子类在继承时构造函数是不能被继承的。但是,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造方法。因此必定会调用父类的构造函数。

构造原则:

  1. 如果子类没有定义构造方法,则调用父类的无参数的构造方法。
  2. 如果子类定义了构造方法,不论是无参数还是带参数,在创建子类的对象的时候,首先执行父类无参数的构造方法,然后执行自己的构造方法。
  3. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数,则会调用父类的默认无参构造函数。
  4. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类自己提供了无参构造函数,则会调用父类自己的无参构造函数。
  5. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类只定义了自己的有参构造函数,则会出错(如果父类只有有参数的构造方法,则子类必须显示调用此带参构造方法)。
  6. 如果子类调用父类带参数的构造方法,需要用初始化父类成员对象的方式
     

总结一点就是,只要使用子类来定义一个对象,就必定会调用父类的构造函数或者默认构造函数。

就是有蛋必有鸡,可能这个会有异议;换句话说就是想有儿子必有爹,想要有儿子,就必须要先把父亲的东西安置好。这个没办法反驳吧。hh


可能会有人纠结上面六种方法,那么我们先看下上面的六种方法:

不用看子类如何,父类可以分为两类,仅仅就是子类写构造函数的区别

  1. 父类没有自定义构造函数,就是默认构造函数
  2. 父类自定义了构造函数

这两种也没有太大区别,只不过父类有自定义构造函数的时候要麻烦一点。

eg:

第一种:父类无自定义

父类定义:A() {}

那么子类可以直接写为:B() {}      (这里默认调用了父类的默认构造函数)

第二种:父类自定义构造函数

父类定义:A(int a) {}

那么子类则麻烦一点,定义时需要把父类带上即:B():A(1) {}     或者子类也有参数  B(int a):A(a) {}     (这个地方随便传进去一个值就行)




 

上面大概讲了一下关于构造函数的问题,接下来讲解为何一定要使用虚析构函数:

一,正常的定义方式就是某个类定义出对应类的方法即:
    父类定义父类对象,子类定义子类对象。这个时候调用函数以及析构都是正常的:

eg(代码在最后):

父类初始化父类对象和子类初始化子类对象运行截图:

因为子类会调用父类的构造函数,所以B对象b时会有父类的构造函数和析构函数出现;

二,当父类用子类初始化自己对象时(即C++中多态的实现)

此时析构函数和虚析构函数就会有区别,看运行截图

可以看出第一种使用非虚析构函数时,调用了子类的构造函数,但是却没有析构,因此就会造成内存泄漏。

重点:

为什么会产生这种原因呢,是因为使用virtual时,会生成虚函数表,即对象调用参数时会参考这个虚函数表。

比如父类A中有show()函数,子类中也有show()函数:那么如果父类定义时使用了virtual,那么虚函数表中就会保存一个指向子类中show()函数的指针。

假如父类为School,子类为Class。类中都有函数show();

那么School *test = new Class后。会执行test->show;和delete test;的操作。

一,如果test和析构两个都不是虚函数
那么执行的时候,首先查看虚函数表里,但是此时虚函数表里什么也没有,继续往下找,也就找到了父类的show和析构函数,然后执行。自然子类的析构就不会被调用,且show也是执行父类中。

二,如果两个都是虚函数
那么执行的时候,虚函数表里两个都有:

  • 当执行show函数时,会从虚函数表里会找到指向子类show函数的指针,因此就会执行子类的函数;
  • 然后执行析构函数,先从虚函数表里找到了子类的析构函数,执行完发现没有释放掉对象test,继续找虚函数表,但是找完也没有释放对象test,然后从父类中找,找到析构函数后执行,此时就释放了对象test,程序也就结束了。

因此也就明白了为什么会如果析构函数不定义为虚析构,那么可能就不会执行子类的析构函数,也就会产生图一中的情况。

代码:

#include<iostream>
using namespace std;

class A{
public:
	A(){ cout << "调用A的默认构造函数" << endl; }
	virtual ~A(){ cout << "调用A的析构函数" << endl; }
};

class B:public A{
public:
	B(){ cout << "调用B的默认构造函数" << endl; }
	~B(){ cout << "调用B的析构函数" << endl; }
};

int main()
{
/*
	//实验一
	cout << "A对象a::" << endl;
	A *a = new A();
	delete a;

	cout << endl << endl;
	cout << "B对象b::" << endl;
	B *b = new B();
	delete b;
*/
	//实验二
	A *a1 = new B();
	delete a1;
	system("pause");
	return 0;
}

个人的浅薄理解,如果有其他意见欢迎评论指正。

 

  • 39
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大学生毕设

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值