请教【多态】、【函数重载】、【虚函数的动态绑定】这三个概念的区别和联系
如题 请高手指教 问题点数:20、回复次数:7Top
1 楼Andy84920(你也不懂)回复于 2004-12-04 22:28:43 得分 0
找书啊..............
这么好的学习机会还要给别人?Top
2 楼Robinhoodgood(Steven)回复于 2004-12-04 22:34:01 得分 0
多态是C++的一个重要特性,主要通过函数重载和虚函数来实现
其中函数重载是静态绑定,而虚函数则是动态绑定(即直到运行时才确定调用哪个函数)
待补充。。。Top
3 楼asimpleman(simple_man)回复于 2004-12-04 23:50:03 得分 0
gTop
4 楼greenteanet(扎扎实实打基础,保持一颗平常心。)回复于 2004-12-05 00:21:18 得分 10
要说得很明白的话,可能要一些时间..
我就简单的说一下吧..
多态可以分为四种,强制多态,重载多态,类型参数多态,包含多态。前两种是特殊多态性,也就是说语义上没有关联,而后面两种是一般多态性,也就是说语义上相互关联。
函数重载是一种多态,它允许使用多个同名函数,即允许同一函数有多种形式。
函数重载的关键是函数的参数列表,如果两个函数的参数和类型相同,同时参数顺序也相同,则它们的参数列表相同,而与变量名无关。如果参数数目和(或)参数类型不同,则参数列表也不同。
动态绑定也叫做动态联编,它是在程序运行过程是进行选择正确的虚拟函数的代码的联编方式。
虚函数表现的就是一种多态性,它是在程序运行过程中根据当前指针来选择调用那个方法。Top
5 楼koalapheonix()回复于 2004-12-05 09:07:08 得分 10
C++批评系列:函数重载
Ian Joyner
cber译
C++允许在参数类型不同的前提下重载函数。重载的函数与具有多态性的函数(即虚函数)不同处在于:调用正确的被重载函数实体是在编译期间就被决定了的;而对于具有多态性的函数来说,是通过运行期间的动态绑定来调用我们想调用的那个函数实体。多态性是通过重定义(或重写)这种方式达成的。请不要被重载(overloading)和重写(overriding)所迷惑。重载是发生在两个或者是更多的函数具有相同的名字的情况下。区分它们的办法是通过检测它们的参数个数或者类型来实现的。重载与CLOS中的多重分发(multiple dispatching)不同,对于参数的多重分发是在运行期间多态完成的。
【Reade 89】中指出了重载与多态之间的不同。重载意味着在相同的上下文中使用相同的名字代替出不同的函数实体(它们之间具有完全不同的定义和参数类型)。多态则只具有一个定义体,并且所有的类型都是由一种最基本的类型派生出的子类型。C. Strachey指出,多态是一种参数化的多态,而重载则是一种特殊的多态。用以判断不同的重载函数的机制就是函数标示(function signature)。
重载在下面的例子中显得很有用:
max( int, int )
max( real, real )
这将确保相对于类型int和real的最佳的max函数实体被调用。但是,面向对象的程序设计为该函数提供了一个变量,对象本身被被当作一个隐藏的参数传递给了函数(在C++中,我们把它称为this)。由于这样,在面向对象的概念中又隐式地包含了一种对等的但却更有更多限制的形式。对于上述讨论的一个简单例子如下:
int i, j;
real r, s;
i.max(j);
r.max(s);
但如果我们这样写:i.max(r),或是r.max(j),编译器将会告诉我们在这其中存在着类型不匹配的错误。当然,通过重载运算符的操作,这样的行为是可以被更好地表达如下:
i max j 或者
r max s
il:COMPARABLE_LIST[INTEGER]
rl:COMPARABLE_LIST[REAL]
i := il.max
r := rl.max
Top
6 楼koalapheonix()回复于 2004-12-05 09:07:30 得分 0
上面的例子显示,面向对象的编程典范(paradigm),特别是和泛型化(genericity)结合在一起时,也可以达到函数重载的效果而不需要C++中的函数重载那样的声明形式。然而是C++使得这种概念更加一般化。C++这样作的好处在于,我们可以通过不止一个的参数来达到重载的目的,而不是仅使用一个隐藏的当前对象作为参数这样的形式。
另外一个我们需要考虑的因素是,决定(resolved)哪个重载函数被调用是在编译阶段完成的事情,但对于重写来说则推后到了运行期间。这样看起来好像重载能够使我们获得更多性能上的好处。然而,在全局分析的过程中编译器可以检测函数min和max是否处在继承的最末端,然后就可以直接的调用它们(如果是的话)。这也就是说,编译器检查到了对象i和r,然后分析对应于它们的max函数,发现在这种情况下没有任何多态性被包含在内,于是就为上面的语句产生了直接调用max的目标代码。与此相反的是,如果对象n被定义为一个NUMBER,NUMBER又提供一个抽象的max函数声明(我们所用的REAL.max和INTERGER.max都是从它继承来的),那么编译器将会为此产生动态绑定的代码。这是因为n既可能是INTEGER,也有可能是REAL。
现在你是不是觉得C++的这种方法(即通过提供不同的参数来实现函数的重载)很有用?不过你还必须明白,面向对象的程序设计对此有着种种的限制,存在着许多的规则。C++是通过指定参数必须与基类相符合的方式实现它的。传入函数中的参数只能是基类,或是基类的派生类。例如:
A.f( B someB ) {...}
class B ...;
class D : public B ...;
A a;
D d;
a.f( d );
其中d必须与类'B'相符,编译器会检测这些。
通过不同的函数签名(signature)来实现函数重载的另一种可行的方法是,给不同的函数以不同的名字,以此来使得它们的签名不同。我们应该使用名字来作为区分不同实体(entities)的基础。编译器可以交叉检测我们提供的实参是否符合于指定的函数需要的形参。这同时也导致了软件更好的自记录(self-document)。从相似的名字选择出一个给指定的实体通常都不会很容易,但它的好处确实值得我们这样去做。
[Wiener95]中提供了一个例子用以展示重载虚拟函数可能出现的问题:
class Parent
{
public:
virutal int doIt( int v )
{
return v * v;
}
};
class Child: public Parent
{
public:
int doIt( int v, int av = 20 )
{
return v * av;
}
};
int main()
{
int i;
Parent *p = new Child();
i = p->doIt(3);
return 0;
}
当程序执行完后i会等于多少呢?有人可能会认为是60,然而结果却是9。这是因为在Child中doIt的签名与在Parent中的不一致,它并没有重写Parent中的doIt,而仅仅是重载了它,在这种情况下,缺省值没有任何作用。
Java也提供了方法重载,不同的方法可以拥有同样的名字及不同的签名。
在Eiffel中没有引入新的技术,而是使用泛型化、继承及重定义等。Eiffel提供了协变式的签名方式,这意味着在子类的函数中不需要完全符合父类中的签名,但是通过Eiffel的强类型检测技术可以使得它们彼此相匹配。
Top
7 楼zebulon(zebulon)回复于 2004-12-05 17:25:40 得分 0
首先说说重载:
重载就是指有多个函数,其名字相同,但是参数不同(参数个数不同或者参数类型不同),这样的几个函数就称为函数重载。。所以说,重载就是指函数重载。
在看看多态:
多态就是同一个行为(函数或者成员函数)在不同的情况下表现出不同的状态。一般有两种情况的多态:继承关系的多态和操作符的多态。
对于前者,举例如下: 有一个基类Shape,
它有两个派生类:Circle,Square. 由于这两个派生类都覆盖了基类的方法draw(), 所以在运行时:它们调用各自的draw()方法,表现的状态不同:一个画圆,一个画的是正方形。
对于操作符的多态,很好理解,比如+,3+4, 4.5+5.4 ;分别是两个整数相加,浮点数相加。对于同一个操作符,表现的状态也不同。
最后,看看虚函数的动态绑定:
其实,应该说是采用了虚函数才能够实现动态绑定,下面的代码是一个关于多态的例子,同时也说明了什么是动态绑定:
#include<iostream.h>
class Shape{
public:
Shape(){}
virtual void draw(){
cout << "draw a shape \n";
}
};
class Circle:public Shape{
public:
Circle(){}
void draw(){
cout << "draw a circle \n";
}
};
class Square:public Shape{
public:
Square(){
}
void draw(){
cout << "draw a square \n";
}
};
void main(){
Shape *s1,*s2;
s1 = new Circle();
s2 = new Square();
s1->draw();
s2->draw();
}
Top




