析构函数和虚析构函数

本篇文章主要是介绍在C++中虚函数定义以及为什么析构函数是虚函数?


1首先思考什么是虚函数以及我们什么时候要用到虚函数?

     简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离;用形象的语言来解释就是通过基类访问派生类定义的函数。

举个简单的例子:

class A
{
public:
	void print(){ std::cout<<"This is A"<<endl;}
};

class B:public A
{
public:
	void print(){ std::cout<<"This is B"<<endl;}
};

int main()
{  
	A a;
	B b;
	a.print();
	b.print();
}

     这个程序的输出很简单分别是:“this is A”和“this is B”;但是他没有做到多态性,我们在此基础之上再来解释多态性:就是指:一切用指向基类的指针或引用来操作对象,这里我们的基类是A。于是我们将上述代码做修改如下:

class A
{
public:
	virtual void print(){ std::cout<<"This is A"<<endl;}
};

class B:public A
{
public:
	void print(){ std::cout<<"This is B"<<endl;}
};

int main()
{  
	A a;
	B b;
	A* p1=&a;
	A* p2=&b;
	p1->print();
	p2->print();
}
    这个程序的输出和上面的程序是一样的,不同之处就在于p2是指向A的指针,却输出的是B中的print函数(注意这里B里面的print()也是虚函数,由于B是A的派生类,当基类里面的print为虚函数时,派生类里面的print也会自动变为虚函数)。假如上述代码改成下面这样:
class A
{
public:
	virtual void print(){ std::cout<<"This is A"<<endl;}
};

class B:public A
{
public:
	void print(){ std::cout<<"This is B"<<endl;}
};

void main(A* p1)
{  
	A a;
	B b;
	p1->print();//这个输出的到底是A里面的还是B里面的就不能确定
}

    从这里我们可以看出虚函数只能借助于指针或应用来达到多态性。于是在这里我们就可以再次总结一下虚函数:一个类函数的调用并不是在编译时刻被决定的,而是在运行时刻被决定的,由于编写代码的时候并不能确定被调用的是基类的还是哪个派生类的函数所以被称为“虚函数”。简单来说指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用相应的函数,这个函数就是虚函数。


还有一个纯虚构函数:

class A
{
public:
	virtual void print()=0;//=0,标志一个函数为纯虚函数
};
     一个函数被声明为纯虚函数后,其表明:我是一个抽象类,不要将我实例化,纯虚函数用来规范派生类的行为,实际上就是所为的接口,它告诉使用者,我的派生类都会有这个函数。


2 接着我们再来思考为什么析构函数是虚函数,是不是所有的析构函数都是虚函数?

 

    一个函数声明为纯虚后,纯虚函数的意思是:我是一个抽象类!不要把我实例化!纯虚函数用来规范派生类的行为,实际上就是所谓的“接口”。它告诉使用者,我的派生类都会有这个函数。

    在实现多态时,当用基类操作派生类,在析构时防止只析构基类而不析构派生类的状况发生,我们一般将析构函数定义为虚函数。 当一个类打算被用作其它类的基类时,它的析构函数必须是虚函数。

下面我们来举例说明:

#include<iostream>
using namespace std;
class A
{
public:
	A(){}
	~A(){cout<<"Output from the destruct of A"<<endl;}
	virtual void print(){ cout<<"This is A"<<endl;}
};

class B:public A
{
public:
	B(){}
	~B(){cout<<"Output from the destruct of B"<<endl;}
	void print(){ cout<<"This is B"<<endl;}
};

int main()
{  
	A* p1=new B;
	p1->print();
	delete p1;
	return 0;
}

    其运行结果如下:

    我们可以看到在执行delete p1的时候,只有A中的析构函数被调用,而B中的析构函数没被调用。而如果我们将A中的析构函数改为虚函数,代码如下

#include<iostream>
using namespace std;
class A
{
public:
	A(){}
	virtual ~A(){cout<<"Output from the destruct of A"<<endl;}
	virtual void print(){ cout<<"This is A"<<endl;}
};

class B:public A
{
public:
	B(){}
	~B(){cout<<"Output from the destruct of B"<<endl;}
	void print(){ cout<<"This is B"<<endl;}
};

int main()
{  
	A* p1=new B;
	p1->print();
	delete p1;
	return 0;
}

其运行结构如下:

    我们可以看到这段代码中基类的析构函数被定义为虚函数,在main函数中用基类的指针去操作继承类的成员,释放指针P的过程是:只是释放了继承类的资源,再调用基类的析构函数.调用print()函数执行的也是继承类定义的函数. 

还有一种情况:当我们的基类没有对派生类对象操作时:

#include<iostream>
using namespace std;
class A
{
public:
	A(){}
	~A(){cout<<"Output from the destruct of A"<<endl;}
	void print(){ cout<<"This is A"<<endl;}
};

class B:public A
{
public:
	B(){}
	~B(){cout<<"Output from the destruct of B"<<endl;}
	void print(){ cout<<"This is B"<<endl;}
};

int main()
{  
	B* p1=new B;
	p1->print();
	delete p1;
	return 0;
}

其运行结果如下:

    这段代码里面没有虚函数,在main函数中用继承类的指针去操作继承类的成员,释放指针P的过程是:先释放继承类的资源,再释放基类资源. 于是我们得出下面的结论

   如果不需要基类对派生类及对象进行操作,则不能定义虚函数,因为这样会增加内存开销.当类里面有定义虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间.所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数.

   纯虚的析构函数没有什么作用,是虚的就够了,根据纯虚函数的定义,通常是希望将一个类变成抽象类,而这个类又没有合适的函数作为纯虚函数时,我们可以选择析构函数。

    在你设计一个基类的时候,如果发现一个函数需要在派生类里有不同的表现,那么它就应该是虚的。从设计的角度来说,出现在基类的虚函数时接口,出现在派生类中的虚函数是接口的具体实现。通过这种方法,我们可以实现将对象的行为抽象化。






  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值