今天研究了一下虚函数,解决了一直困扰的几个问题,尤其是在前段时间找工作面试的时候说不清的几个问题,早知如此,何必当初哎!
Questions:
(1)为什么要用虚函数?
(2)为什么要定义virtual析构函数?
(3)什么时候该定义virtual析构函数和什么时候不该定义virtual析构函数?
Answers:
(1)为什么要用虚函数?
C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类类型的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。Java中的多态是通过interface和abstract来实现的,java没有virtual一说!
定义一个函数为虚函数,不代表函数为不被实现的函数。定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。而在Java中,只要子类实现了interface中定义的函数,那么接口声明的引用一定会调用子类的这个函数,如果子类没有定义,则调用接口中的函数!
定义一个函数为纯虚函数,才代表函数没有被实现。定义他是为了实现一个接口,起到一个规范的作用,规范继承这个。类的程序员必须实现这个函数。
先看一个不使用virtual的例子:
该程序先定义了一个基类:TimeKeeper,基类中有一个实现了的函数print,然后定义一个类AtomicClock来继承这个TimeKeeper,TimeKeeper中也定义了一个函数print,在main函数中先定义一个基类指针,让其指向一个通过AtomicKeeper类的成员函数getTimeKeeper返回的实例,然后用这个基类指针调用print,那么到底会调用基类和子类中的哪个print呢?
// VirtualConstructor.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include<iostream>
using namespace std;
class TimeKeeper {
public:
TimeKeeper() {}
void print()
{
cout<<"call print() of TimeKeeper......"<<endl;
}
virtual ~TimeKeeper() {}
virtual TimeKeeper* getTimeKeeper() = 0;
};
class AtomicClock: public TimeKeeper {
public:
AtomicClock(){}
~AtomicClock(){}
TimeKeeper* getTimeKeeper()
{
return new AtomicClock();
}
void print()
{
cout<<"call print() of AtomicClock......"<<endl;
}
};
int main(int argc, _TCHAR* argv[])
{
AtomicClock ato;
TimeKeeper* ptk = ato.getTimeKeeper();
ptk->print();
delete ptk;
return 0;
}
执行结果可能出乎各位看官的意料,调用的基类TimeKeeper中的print方法!
下面将这个程序修改一下,将基类中的print函数改为virtual函数:
// VirtualConstructor.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include<iostream>
using namespace std;
class TimeKeeper {
public:
TimeKeeper() {}
virtual void print() // virtual函数
{
cout<<"call print() of TimeKeeper......"<<endl;
}
virtual ~TimeKeeper() {}
virtual TimeKeeper* getTimeKeeper() = 0;
};
class AtomicClock: public TimeKeeper {
public:
AtomicClock(){}
~AtomicClock(){}
TimeKeeper* getTimeKeeper()
{
return new AtomicClock();
}
void print()
{
cout<<"call print() of AtomicClock......"<<endl;
}
};
int main(int argc, _TCHAR* argv[])
{
AtomicClock ato;
TimeKeeper* ptk = ato.getTimeKeeper();
ptk->print();
delete ptk;
return 0;
}
这次的执行结果就是调用了子类的print函数!
这就是虚函数的神奇功能,也是跟Java的多态机制的不同之处,至于为什么会出现这种效果,那就是涉及到虚函数表的知识了,每个类的实例存放有本类实现了的虚函数列表,当用基类指针指向的时候,就会调用对象列表中的函数!这里不做赘述!
(2)为什么要定义virtual析构函数?
如果你有一个带有虚函数功能的类,则它需要一个虚析构函数,原因如下:
1.如果一个类有虚函数功能,它经常作为一个基类使用,为了能够被子类继承。
2.如果它是一个基类,它的派生类经常使用new来分配。
3.如果一个派生类对象使用new来分配,并且通过一个指向它的基类的指针来控制,那么它经常通过一个指向它的基类的指针来删除它。
C++明白指出,当derived class对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,其结果将是不确定的,通常会调用基类的析构函数而导致派生类定义的成员没有被析构,产生内存泄露等问题。
基类有虚析构函数的话,最底层的派生类的析构函数最先被调用,然后各个基类的析构函数被调用。
看如下的例子:
不适用virtual虚构函数:
// VirtualConstructor.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include<iostream>
using namespace std;
class TimeKeeper {
public:
TimeKeeper()
{
cout<<"constructor of TimeKeeper......"<<endl;
}
~TimeKeeper()
{
cout<<"deconstructor of TimeKeeper......"<<endl;
}
virtual TimeKeeper* getTimeKeeper() = 0;
};
class AtomicClock: public TimeKeeper {
public:
AtomicClock()
{
cout<<"constructor of AtomicClock......"<<endl;
}
~AtomicClock()
{
cout<<"deconstructor of AtomicClock......"<<endl;
}
TimeKeeper* getTimeKeeper()
{
return new AtomicClock();
}
};
int main(int argc, _TCHAR* argv[])
{
AtomicClock ato;
TimeKeeper* ptk = ato.getTimeKeeper();
delete ptk;
return 0;
}
执行的结果是,ptk释放的时候没有只调用了基类TimeKeeper中的析构函数,没有调用子类的析构函数,这样的话子类的“成分”就没有得到释放,就很容易造成内存泄露问题!其实原因跟上面一样,析构函数也是一个成员函数!
使用virtual析构函数的例子:
// VirtualConstructor.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include<iostream>
using namespace std;
class TimeKeeper {
public:
TimeKeeper()
{
cout<<"constructor of TimeKeeper......"<<endl;
}
virtual ~TimeKeeper()
{
cout<<"deconstructor of TimeKeeper......"<<endl;
}
virtual TimeKeeper* getTimeKeeper() = 0;
};
class AtomicClock: public TimeKeeper {
public:
AtomicClock()
{
cout<<"constructor of AtomicClock......"<<endl;
}
~AtomicClock()
{
cout<<"deconstructor of AtomicClock......"<<endl;
}
TimeKeeper* getTimeKeeper()
{
return new AtomicClock();
}
};
int main(int argc, _TCHAR* argv[])
{
AtomicClock ato;
TimeKeeper* ptk = ato.getTimeKeeper();
delete ptk;
return 0;
}
这次运行结果会先调用子类AtomicKeeper的析构函数,然后调用基类TimeKeeper的析构函数,实现完成的内存释放操作!这也是利用多态来实现的,要想使用多态,就必须使用virtual析构函数!
实际上,优秀的程序员常常把基类的析构函数定义为虚函数。因为,将基类的析构函数定义为虚函数后,当利用delete删除一个指向派生类定义的对象指针时,系统会调用相应的类的析构函数(包括派生类的和基类的)。而不将析构函数定义为虚函数时,只调用基类的析构函数。
构造函数的调用顺序总是如下:
1.基类构造函数。如果有多个基类,则构造函数的调用顺序是某类在类派生表中出现的顺序,而不是它们在成员初始化表中的顺序。
2.成员类对象构造函数。如果有多个成员类对象则构造函数的调用顺序是对象在类中被声明的顺序,而不是它们出现在成员初始化表中的顺序。
3.派生类构造函数。
析构函数的调用顺序总是如下:
析构函数的调用顺序与构造函数的调用顺序正好相反,将上面3个点反过来用就可以了,首先调用派生类的析构函数;其次再调用成员类对象的析构函数;最后调用基类的析构函数。
析构函数在下边3种情况时被调用:
1.对象生命周期结束,被销毁时(一般类成员的指针变量与引用都i不自动调用析构函数);
2.delete指向对象的指针时,或delete指向对象的基类类型指针,而其基类虚构函数是虚函数时;
3.对象i是对象o的成员,o的析构函数被调用时,对象i的析构函数也被调用。
(3)什么时候该定义virtual析构函数和什么时候不该定义virtual析构函数?
1.多态性质的base class应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。因为只有在“多态”的情况下才能通过父类的指针调用实际子类的成员函数。
2.class的设计目的如果不是作为base class使用,或不是为了具备多态性,就不应该声明virtual析构函数。
分享到:
相关推荐
在实现多态时, 当用基类指针操作派生类, 在析构时候防止只析构基类而不析构派生类。 2、例子: (1)、 #include using namespace std; class Base{ public: Base() {}; ~Base() {cout <&...
条款07:为多态基类声明Virtual析构函数 条款08:别让异常逃离析构函数 条款09:绝不在构造和析构过程中调用Virtual函数 条款10:令Operator=返回一个referenceto this 条款11:在Operator=中处理“自我赋值” ...
C++教学课件:继承和多态.ppt
C++的多态是通过虚表指针来实现的。但是在构造和析构函数中调用虚函数,情况如何呢?是否还能实现多态呢?本文档从内存角度揭示了这其中的多态实现过程。
delphi析构函数的使用,以及窗体的继承,多态。
条款07:为多态基类声明virtual析构函数 declare destructors virtual in polymorphic base classes. 条款08:别让异常逃离析构函数 prevent exceptions from leaving destructors. 条款09:绝不在构造和析构过程中...
1、增加重复员工编号的判定 2、增加清除文件时输入错误的错误提示 3、解决员工岗位输入错误导致的程序崩溃
简单例子展示虚函数展现的多态特性,更改一处注释就能对比基类是否是虚函数带来的变化
C++重点复习题(多态和继承).doc
面向对象程序设计C++:第5章 多态.ppt
C++ 上课/复习ppt多态.pptx
在任何编程语言中,字符串都是重中之重。通过使用XMind总结C++ XMind中的字符串,了解并熟练掌握string对象的构造和初始化,插入删除替换,查找和比较,把所有相关的操作整合在一个页面上,提高工作效率
利用C++计算正方形,矩形,三角形圆形的面积,包含了虚函数,继承,多态相关的基础知识,这是一个示例程序力求简单易懂。代码采用VC6.0建立工程。
后端 / C++ 类 封装 继承 多态 stl容器 虚函数 纯虚函数 友元函数 模板
多态基类声明virtual析构函数 别让异常逃离析构构函数 不在构造析构过程中调用virtual函数 operator =返回对* this的引用 在operator =中处理自我赋值 复制对象时勿忘其每一个部份 以对象管理资源 在资源管理类中...
异质链表: 是指可以链表指针所指向的数据类型并不一致,比如一个链表中可以既存储整形数据,又可以存储浮点性的数据。在面向对象的语言中甚至就是可以指向不同的对象。...C++多态:通过虚函数表实现多态。
C++程序设计课件:4 继承与多态.ppt
基于C++抽象封装集成多态开发的物品销售系统V1.0,体现了类的封装继承多态,数据处理采用文本方式
要求:1、虚函数 多态 多态表现为 基类 基类指针和继承间的关系 2、带有多对象成员。定义 3、体现继承 虚拟继承(要通过至少三层 父类父类子类) 虚函数 (3层 纵向关系) 水平方向上:体现出继承顺序 先虚拟继承 ...