C++ 和 Delphi 的函数覆盖(Override)与重载(overload)
Spacesoft【暗夜狂沙】
在面向对象编程中,当子类继承了来自基类的函数后,子类有可能需要对其中的一些函数作出与基类不同处理,比如:
class CHuman
{
public:
void SayMyName()//打印出对象的姓名
{
cout << "Hi, I am a human" << endl;
}
};
那么很明显,假如他的子类有一个同名、同参数和返回值(一句话,一摸一样)的函数SayMyName,它会调用哪个函数呢?比如现在有一个class CMark
class CMark: public CHuman
{
public:
void SayMyName()
{
cout << "Hi, I am mark" << endl;
}
};
那么我们要问,下面的程序段:
CHuman *pH = new CMark;
if (pH)
pH->SayMyName();
else
cout << "cast error! " << endl;
delete pH;
pH = NULL;
要打印出来的,真的是我们想要的Hi, I am mark 吗?
不是。它输出了Hi, I am a human。这很糟糕,当我们指着一个人要他说出自己的名字的时候,他却告诉我们他“是一个人”,而不是说出自己的名字。出现这样的问题原因在于,用基类的指针指向公有派生类,可以访问派生类从基类中继承的成员函数。但如果派生类中也有同名的函数,则结果仍然是访问基类的同名函数,而不是派生类本身的函数。而事实上,我们希望的是由一个对象的真实类型来决定到底该调用这些同名函数中的哪一个,就是说,这样的决议是动态(Dynamic)的。或者我们可以说,我们希望当一个对象是子类型时,它的同名函数在子类中的实现覆盖(override)掉基类的实现。
我们先从C++对这个问题的处理说起。
这是C++中比较典型的多态的例子,C++用虚函数来实现这样的多态。具体点说,就是使用virtual 关键字来将函数说明成虚函数,在上一个例子中就是应该声明成:
class CHuman
{
public:
virtual void SayMyName()//打印出对象的姓名
{
cout << "Hi, I am a human" << endl;
}
};
这样,其他的代码还是那个老样子,但是我们的CMark 已经知道怎么说自己的名字了。CMark 的SayMyName()函数是否加了virtual 关键字的说明并没有关系,因为根据C++语法的规定,因为它覆盖了CHuman 的同名函数,它自己也就成为virtual 的了。至于为什么一个virtual 关键字有那么神奇的效果呢?C++ FAQ Lite 对此是这样说明的: 在C++中,“虚成员函数是动态确定的(在运行时)。也就是说,成员函数(在运行时)被动态地选择,该选择基于对象的类型,而不是指向该对象的指针/引用的类型”。于是我们的pH就发现自己其实指向的是一个CMark类型的对象,而不是自己的类型所声明的CHuman,所以它聪明的调用了CMark的SayMyName。