Posted on 2008-07-16 21:15
softgamer 阅读(394)
评论(0) 编辑 收藏 引用
我不想在这里讲整个关于C++中的多态性机制,因为我自己描述完后,90%会误人子弟,其实那些资料你可以在
网上搜索到一大堆。 我只想着重讲一下我对于多态性中的动态多态性的一些使用总结。
先简单的说说多态性:不是说千手观音。
所谓多态性,简单地说就是一个名称,但是具有多种语义。多态性的发明有什么意义呢?考虑一下,现在你想 玩球。但是有很多种
球,什么足球,网球,篮球,具体有多少种球类,请你登录奥运网站查阅。程序必须去根 据不同的球的类别给
你选择。于是你用 switch
来根据 不同的情况来选择不同的球。但是这样做已经过时了,意 识到没有,它一点扩展性都没有,一但需要 加入新的球的类别,你就
不得不去修 改代码----而不是扩展代码。
采用多态性就不同了,利用多态性,你可以把一大堆的case语句替换为一个Ball函数。这就是它的神奇功能。
多态性分为静态多态性和动态多态性。
函数重载可以实现静态多态性,而要实现动态多态性就可以用----虚函数(virtual function),这也牵扯到继
承。
1.如果以一个基础类别的指针指向一个衍生类别的物件,那么经由此指针,你就只能呼叫基础类别(而不是衍
生类别)所定义的函数。
2.如果你以一个衍生类别的指针,指向一个基础类别的物件,你必须先做明显的转型动作(explicit cast),
这种作法很危险,不符合真实生活经验,在程序设计上也会带给程序员困惑。
3.如果基础类别和衍生类别都定义了相同名称的成员函数,那么透过物件指针呼叫成员函数时,到底呼叫哪一
个函数,必须视该指针的原始类型而定,而不是视指针之实际所指之物件的类型而定。
摘自《深入浅出MFC》
好了,这些死板的遣词造句就到这里了,下面着重看一下虚函数在多态性中的是怎么表现的。
以下继承论述的都是public继承。
#include <iostream>
using namespace std;
class CBallBase
{
public:
void Ball()
{
cout << "CBallBase::Ball was selected" << endl;
}
};
class CBallDerive:public CBallBase
{
public:
void Ball()
{
cout << "CBallDerive::Ball was selected"<< endl;
}
void Ball2()
{
cout << "CBallDerive::Ball2 was selected" << endl;
}
};
int main()
{
CBallDerive bd;
CBall b;
CBall *p=&bd;
p->Ball(); //result:CBall::Ball was selected if no virtual function affix
//p->Ball2(); // error C2039: 'Ball2' : is not a member of 'CBall'
//CBallDerive *p2=&b; // error C2440: 'initializing' :.....不能自动转换
CBallDerive*p2=(CBallDerive*)&b;
p2->Ball(); //result:CBallDerive ::Ball was selected
p2->Ball2(); //correct
b=bd; //correct 足球是球
//b.Ball2(); //error C2039: 'Ball2' : is not a member of 'CBallBase'
//bd=b; //error 球不是足球
return 0;
}
以上代码执行结果:
CBall::Ball was selected
CBallDerive::Ball was selected
CBallDerive::Ball2 was selected
代码基本上反映了上面三点,那么为什么基类指针能够名正言顺地指向其派生类对象呢(甚至可以把一个
派生类对象赋值给一个基类对象)?原因:派生类以public继承基类时,由于包含了基类所有的元素(private
除外),所有的派生类对象同时也是基类对象。另一方面,基类对象就不能赋值给派生类对象,但是如果是指 针的话,我们可以强制 转换。那为什么说“不符合真实生活经验,在程式设计上也会带给程式员困惑。”呢?
正如侯先生论述的,物件导向观念是描绘现实世界
用的。水果类经过添加各种特征(属性或方法)派生出苹果类。我们可以说苹果是水果,但是却不能说水果就
是苹果的。
看这一句://b.Ball2(); //error C2039: Ball2' : is not a member of 'CBallBase'
这里就涉及到切割(object slicing),
看这段话你就会明白为什么它会出错:“衍生物件通常都比基类物件大(记忆体空间),因为衍生物件不但继承
其基础类别的成员,又有自己的成员。那么所谓的upcasting(向上强制转型)将会造成物件的内容被切割(object
slicing)。”
在这里我要提取出第三点,先放在这里:如果基础类别和衍生类别都定义了相同名称之成员函数,那么透过物
件指标呼叫成员函数时,到底呼叫哪一个函数,必须视该指标的原始型别而定,而不是视指标之实际所指之物
件的型别而定。
//////////////////////////////////////////////////////////////////////////////////////////////////////
接下来让我们看,当虚拟函数参合进来时,那些指针啊对象啊之类会出现什么情况。
再在Cbase类的void Ball()前加上virtual, 然后结果就变成了
CBall *p=&bd;
p->Ball(); //result:CBall::Ball was selected if without virtual function affix
=====》
CBallDerive::Ball was called with virtual affix
原来的结果是
CBall::Ball was selected without virtual affix
CBallDerive*p2=(CBallDerive*)&b;
p2->Ball(); //result:CBallDerive ::Ball was selected
=====》
CBase::Ball was called with virtual affix
原来的结果是
CBallDerive::Ball was selected without virtual affix
p2->Ball2(); //correct
=====》
CBallDerive::Ball2 was called
原来的结果是
CBallDerive::Ball2 was selected
当在基类中不使用virtual声明会在其派生类中出现同名的Ball()函数时,如果基础类别和衍生类别都定义了相同
名称Ball(),那么透过物件指针呼叫成员函数时,到底呼叫哪一个函数,必须视该指针的原始类
型而定,这里的原始类型指的是CBallDerive*p2,而不是视指针之实际所指之物件(CBallDerive*)&b 的类型而定;
当在基类中使用virtual声明会在其派生类中出现同名的Ball()函数时,如果基础类别和衍生类别都定义了相
同名称Ball()成员函数(且基类中的该函数被定义为virtual),那么透过物件指针呼叫成员函数时,到底呼
叫哪一个函数,必须视该指针之实际所指之物件CBallDerive &bd的类型而定,而不是视指标的原始型CBall
*p别而定!