1、定义
C++语言有多态特性,而虚函数正是其表现之一。关于其定义,有以下三个要点:
(1)必须是基类的非静态成员函数。
(2)访问权限可以是保护型或公有型。
(3)函数前需加上关键字virtual。
2、作用
下面举一例子说明。
#include <iostream>
using namespace std;
class A
{
public:
void Show()
{
cout << "A:Show()" << endl;
}
};
class B:public A
{
public:
void Show()
{
cout << "B:Show()" << endl;
}
};
void main()
{
A a;
B b;
A *p = &a;
p->Show();
p = &b;
p->Show();
}
上述程序的目的是为了调用类A、类B的Show()函数,但运行结果却是:
A:Show()
A:Show()
造成这一结果的原因是C++的静态编联机制。该机制将指针p与基类A的成员函数Show()连接在一起,无论p指向那个对象,p->Show()调用的总是基类A的成员函数Show()。
为达到目的,将基类A的成员函数Show()声明为虚函数(派生类B的成员函数Show()虽然没有关键字virtual,但系统仍会判断出派生类B的成员函数Show()是虚函数。关于判断规则,这里不详讲)。这里,关键字virtual提示C++编译器:函数调用p->Show()要在运行时才能确定所要调用的函数(即动态编联)。则运行结果为:
A:Show()
B:Show()
这样,通过定义虚函数实现了C++的多态性机制。
3、使用虚函数时需注意一下几点:
(1)必须首先在基类中定义虚函数。
(2)实施多态性是通过将基类的指针指向派生类的对象实现的。只有通过基类指针访问虚函数时才能获得运行时的多态性。
(3)构造函数不能是虚函数。
(4)析构函数可以是虚函数。基类中的析构函数通常定义为虚函数。
4、应用
//CThread.h
#ifndef _CTHREAD_H_
#define _CTHREAD_H_
class CThread
{
public:
CThread();
virtual ~CThread();
public:
void StartThread();
void StopThread(DWORD);
virtual void Run ();
private:
static unsigned int __stdcall ThreadFunction( void *p );
private:
HANDLE m_hThread;
};
#endif
//CThread.cpp
#include "CThread.h"
CThread::CThread():m_hThread(NULL)
{
}
CThread::~CThread()
{
StopThread(0);
}
void CThread::StartThread()
{
if ( m_hThread <= 0 )
{
m_hThread = (HANDLE)::_beginthreadex( NULL, 0, ThreadFunction,
(void *)this, 0, NULL );
if ( m_hThread <=0 )
{
throw ::GetLastError();
}
}
}
void CThread::StopThread( DWORD dwExitCode )
{
if ( m_hThread > 0)
{
if ( 0 == ::TerminateThread(m_hThread, dwExitCode) )
{
throw ::GetLastError();
}
m_hThread = NULL;
}
}
unsigned int __stdcall CThread::ThreadFunction( void *p )
{
CThread *pThis = (CThread *)p;
if ( pThis != NULL)
{
while( 1 )
{
pThis ->Run();
}
}
return 0;
}
void CThread::Run()
{
cout << "Run CThread::Run()!" << endl;
}
上述例子中,线程基类的成员函数Run()定义为虚函数,这样在派生类中Run()有不同的实现,实现不同线程做不同事情的目的。
5、虚析构函数
基类的析构函数是虚函数,则派生类的析构函数也是虚函数,即使没有加关键字virtual。基类的析构函数通常要定义为虚函数,这是很有必要的,通过下面的例子进行说明。
#include <iostream>
using namespace std;
class A
{
public:
~A()
{
cout << "~A()" << endl;
};
};
class B:public A
{
public:
~B()
{
cout << "~B()" << endl;
};
};
void main()
{
A *p = new B;
delete p;
}
上述代码中,基类A的析构函数不是虚函数。由于静态编联机制,删除指针p时,只会调用基类的析构函数。输出结果是:
~A()
这样的设计思路显然不行。若将基类A的析构函数定义为虚函数(此时派生类B的析构函数也是虚函数),由动态编联得知 delete p 时,程序调用指针p指向的派生类的析构函数,而派生类的析构函数又会自动调用基类的析构函数,这样整个派生类的对象就被完全释放了。运行结果为:
~B()
~A()
6、纯虚函数
纯虚函数是一个在基类中说明的虚函数,它在基类中没有定义,但是要求在它的派生类中必须定义自己的版本。即纯虚函数的实现部分在基类中不给出,而在派生类中给出。