多态和虚涵数
我一直搞不清楚,多态和虚涵数有什么区别,在我看来虚涵数就是为了多态而存在,
是多态的实现手段.
不知道对不对,请大家帮我说明一下,谢谢.最好能说明一下两者有什么不同.
问题点数:80、回复次数:19Top
1 楼llf_hust()回复于 2005-07-14 10:34:39 得分 6
多态靠虚函数来实现Top
2 楼OpenHero(开勇)回复于 2005-07-14 10:43:35 得分 6
多态是一种模式
是一种概念
而虚函数是一种方法
虚函数可以实现多态
虚函数还有其他的功能
Top
3 楼xzgyb(老达摩)回复于 2005-07-14 10:47:32 得分 10
还是贴一段
C++Primer中的:
c++语言通过以下几种方式支持多态性
1.通过一个隐式转换,从"派生类型的指针或引用"转换到"其公有基类类型的指针或引用"
2.通过虚拟函数机制
3.通过dynamic_cast和typeid运算符
另外void *指针也可被描述为多态,但语言本身并没有显示的支持它
必须有程序员自己管理
另外
看<<C++ Templates>>
把多态描述为分为静多态和动多态
静多态是使用模板在编译期间所表现出的多态性
动多态时用虚拟函数并通过基类的指针或引用在运行时刻所表现出来的多态性
Top
4 楼songlaf(疯子)回复于 2005-07-14 10:52:33 得分 0
好,请大家继续讨论,分数不够,我再家.要不一会我再开个贴子.Top
5 楼beyondtkl(大龙驹<*好久没来了,兄弟们好吧。*>)回复于 2005-07-14 10:57:30 得分 2
OpenHero(雨流星)
說的不錯
還有就是 具體多態的實現 還需要看對象的模型以及編譯器的實現。。Top
6 楼foochow(无聊,灌水......)回复于 2005-07-14 11:02:18 得分 5
虚函数是在基类中定义的时候用了virtual关键字修饰的函数。虚函数可以在基类中定义,也可以只声明,不定义,而把定义的任务交给继承类.继承类从基类那里继承来的虚函数必须在返回值,参数类型以及参数个数上和基类中声明的函数完全一样。之所以要用到虚函数,就是为了实现多态性。多态性简单说就是对于使用者来说,他用的都是基类的对象,但是这些对象可以表现处不同的行为。对于OO编程,提倡面向基类的编程,也就是说如果可能的话,要尽可能的使用基类提供的接口而不要明确指定使用哪个具体的子类。这样可以使程序的灵活性,可维护性等大大提高。而要实现这个目的,多态性就要起作用.总的来说,多态就是使用基类对象来获得多种不同的行为,而虚函数是实现多态的方法Top
7 楼songlaf(疯子)回复于 2005-07-14 11:07:13 得分 0
收获很多,爽.
刚刚想到,从今天开始,我每天拿一个问题出来让大大家讨论,肯定会收获很多,因为每个人的理解都不一样.对我们这些刚学C++的帮助会很大,哈.希望我的3000分,每个50分,可以开2个月.
每个贴子晚上结贴,如果到时候人太多,分比较少,希望不要介意,西西.
Top
8 楼llf_hust()回复于 2005-07-14 11:11:57 得分 1
我也学到了很多东西Top
9 楼xlsue(小林)回复于 2005-07-14 12:01:03 得分 6
虚函数和多态性使得设计和实现易于扩展的系统成为可能。多态性是通过下函数机制实现的。当通过基类指针(或引用)请求使用虚函数时,C++会在与对象关联的派生类中正确地选择重定义的函数。这个大家都明白的。
为了理解这种虚函数机制,就有必要理解编译器在底层做了些什么?
凡是有虚函数的类,编译器都为他生成一个vtbl(虚函数表),vtbl由函数指针组成的数组或链表。里面装有该类各个函数实现体的指针。
vtbl只是下函数机制的一部分。如果有一种方法可以指示每个对象相应于哪一个vtbl,vtbl才起作用,这个就是vptr(虚函数表指针)。编译器在每个声明有虚函数的类的对象隐藏含有一个data member,用来指向vtbl.
编译器必须产生代码,完成一下动作:
1。根据对象的vptr找出其vtbl.
2。找出被调用函数在vtbl内的对应指针。编译器为每个虚函数指定一个表格索引。通过偏移求进入vtbl数组。
3。调用2。中所得的指针所指向的函数。
这种情况单继承相对来说简单,如果多继承的情况就变得复杂了,一个对象会有多个vptr.
详情看《More Effective C++》条款24。Top
10 楼viga_cheung(唐人v+)回复于 2005-07-14 13:09:46 得分 4
产生多态的两个前提:1、继承-用基类指针指向派生类对象;2、虚函数-产生虚表。
com就是基于多态的。Top
11 楼whatsouta(某疯子)回复于 2005-07-14 13:19:14 得分 10
[转]
1.简介
虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。假设我们有下面的类层次:
class A
{
public:
virtual void foo() { cout << "A::foo() is called" << endl;}
};
class B: public A
{
public:
virtual void foo() { cout << "B::foo() is called" << endl;}
};
那么,在使用的时候,我们可以:
A * a = new B();
a->foo(); // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B的!
这个例子是虚函数的一个典型应用,通过这个例子,也许你就对虚函数有了一些概念。它虚就虚在所谓“推迟联编”或者“动态联编”上,一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为“虚”函数。
虚函数只能借助于指针或者引用来达到多态的效果,如果是下面这样的代码,则虽然是虚函数,但它不是多态的:
class A
{
public:
virtual void foo();
};
class B: public A
{
virtual void foo();
};
void bar()
{
A a;
a.foo(); // A::foo()被调用
}
1.1 多态
在了解了虚函数的意思之后,再考虑什么是多态就很容易了。仍然针对上面的类层次,但是使用的方法变的复杂了一些:
void bar(A * a)
{
a->foo(); // 被调用的是A::foo() 还是B::foo()?
}
因为foo()是个虚函数,所以在bar这个函数中,只根据这段代码,无从确定这里被调用的是A::foo()还是B::foo(),但是可以肯定的说:如果a指向的是A类的实例,则A::foo()被调用,如果a指向的是B类的实例,则B::foo()被调用。
这种同一代码可以产生不同效果的特点,被称为“多态”。
1.2 多态有什么用?
多态这么神奇,但是能用来做什么呢?这个命题我难以用一两句话概括,一般的C++教程(或者其它面向对象语言的教程)都用一个画图的例子来展示多态的用途,我就不再重复这个例子了,如果你不知道这个例子,随便找本书应该都有介绍。我试图从一个抽象的角度描述一下,回头再结合那个画图的例子,也许你就更容易理解。
在面向对象的编程中,首先会针对数据进行抽象(确定基类)和继承(确定派生类),构成类层次。这个类层次的使用者在使用它们的时候,如果仍然在需要基类的时候写针对基类的代码,在需要派生类的时候写针对派生类的代码,就等于类层次完全暴露在使用者面前。如果这个类层次有任何的改变(增加了新类),都需要使用者“知道”(针对新类写代码)。这样就增加了类层次与其使用者之间的耦合,有人把这种情况列为程序中的“bad smell”之一。
多态可以使程序员脱离这种窘境。再回头看看1.1中的例子,bar()作为A-B这个类层次的使用者,它并不知道这个类层次中有多少个类,每个类都叫什么,但是一样可以很好的工作,当有一个C类从A类派生出来后,bar()也不需要“知道”(修改)。这完全归功于多态--编译器针对虚函数产生了可以在运行时刻确定被调用函数的代码。
1.3 如何“动态联编”
编译器是如何针对虚函数产生可以再运行时刻确定被调用函数的代码呢?也就是说,虚函数实际上是如何被编译器处理的呢?Lippman在深度探索C++对象模型[1]中的不同章节讲到了几种方式,这里把“标准的”方式简单介绍一下。
我所说的“标准”方式,也就是所谓的“VTABLE”机制。编译器发现一个类中有被声明为virtual的函数,就会为其搞一个虚函数表,也就是VTABLE。VTABLE实际上是一个函数指针的数组,每个虚函数占用这个数组的一个slot。一个类只有一个VTABLE,不管它有多少个实例。派生类有自己的VTABLE,但是派生类的VTABLE与基类的VTABLE有相同的函数排列顺序,同名的虚函数被放在两个数组的相同位置上。在创建类实例的时候,编译器还会在每个实例的内存布局中增加一个vptr字段,该字段指向本类的VTABLE。通过这些手段,编译器在看到一个虚函数调用的时候,就会将这个调用改写,针对1.1中的例子:
void bar(A * a)
{
a->foo();
}
会被改写为:
void bar(A * a)
{
(a->vptr[1])();
}
因为派生类和基类的foo()函数具有相同的VTABLE索引,而他们的vptr又指向不同的VTABLE,因此通过这样的方法可以在运行时刻决定调用哪个foo()函数。
虽然实际情况远非这么简单,但是基本原理大致如此。
1.4 overload和override
虚函数总是在派生类中被改写,这种改写被称为“override”。我经常混淆“overload”和“override”这两个单词。但是随着各类C++的书越来越多,后来的程序员也许不会再犯我犯过的错误了。但是我打算澄清一下:
override是指派生类重写基类的虚函数,就象我们前面B类中重写了A类中的foo()函数。重写的函数必须有一致的参数表和返回值(C++标准允许返回值不同的情况,这个我会在“语法”部分简单介绍,但是很少编译器支持这个feature)。这个单词好象一直没有什么合适的中文词汇来对应,有人译为“覆盖”,还贴切一些。
overload约定成俗的被翻译为“重载”。是指编写一个与已有函数同名但是参数表不同的函数。例如一个函数即可以接受整型数作为参数,也可以接受浮点数作为参数。
Top
12 楼whatsouta(某疯子)回复于 2005-07-14 13:19:57 得分 10
2. 虚函数的语法
虚函数的标志是“virtual”关键字。
2.1 使用virtual关键字
考虑下面的类层次:
class A
{
public:
virtual void foo();
};
class B: public A
{
public:
void foo(); // 没有virtual关键字!
};
class C: public B // 从B继承,不是从A继承!
{
public:
void foo(); // 也没有virtual关键字!};
这种情况下,B::foo()是虚函数,C::foo()也同样是虚函数。因此,可以说,基类声明的虚函数,在派生类中也是虚函数,即使不再使用virtual关键字。
2.2 纯虚函数
如下声明表示一个函数为纯虚函数:
class A
{
public:
virtual void foo()=0; // =0标志一个虚函数为纯虚函数
};
一个函数声明为纯虚后,纯虚函数的意思是:我是一个抽象类!不要把我实例化!纯虚函数用来规范派生类的行为,实际上就是所谓的“接口”。它告诉使用者,我的派生类都会有这个函数。
2.3 虚析构函数
析构函数也可以是虚的,甚至是纯虚的。例如:
class A
{
public:
virtual ~A()=0; // 纯虚析构函数
};
当一个类打算被用作其它类的基类时,它的析构函数必须是虚的。考虑下面的例子:
class A
{
public:
A() { ptra_ = new char[10];}
~A() { delete[] ptra_;} // 非虚析构函数
private:
char * ptra_;
};
class B: public A
{
public:
B() { ptrb_ = new char[20];}
~B() { delete[] ptrb_;}
private:
char * ptrb_;
};
void foo()
{ A * a = new B; delete a;}
这个例子中,程序也许不会象你想象的那样运行,在执行delete a的时候,实际上只有A::~A()被调用了,而B类的析构函数并没有被调用!这是否有点儿可怕?
如果将上面A::~A()改为virtual,就可以保证B::~B()也在delete a的时候被调用了。因此基类的析构函数都必须是virtual的。
纯虚的析构函数并没有什么作用,是虚的就够了。通常只有在希望将一个类变成抽象类(不能实例化的类),而这个类又没有合适的函数可以被纯虚化的时候,可以使用纯虚的析构函数来达到目的。
2.4 虚构造函数?
构造函数不能是虚的。
3. 虚函数使用技巧 3.1 private的虚函数
考虑下面的例子:
class A
{
public:
void foo() { bar();}
private:
virtual void bar() { ...}
};
class B: public A
{
private:
virtual void bar() { ...}
};
在这个例子中,虽然bar()在A类中是private的,但是仍然可以出现在派生类中,并仍然可以与public或者protected的虚函数一样产生多态的效果。并不会因为它是private的,就发生A::foo()不能访问B::bar()的情况,也不会发生B::bar()对A::bar()的override不起作用的情况。
这种写法的语意是:A告诉B,你最好override我的bar()函数,但是你不要管它如何使用,也不要自己调用这个函数。
3.2 构造函数和析构函数中的虚函数调用
一个类的虚函数在它自己的构造函数和析构函数中被调用的时候,它们就变成普通函数了,不“虚”了。也就是说不能在构造函数和析构函数中让自己“多态”。例如:
class A
{
public:
A() { foo();} // 在这里,无论如何都是A::foo()被调用!
~A() { foo();} // 同上
virtual void foo();
};
class B: public A
{
public:
virtual void foo();
};
void bar()
{
A * a = new B;
delete a;
}
如果你希望delete a的时候,会导致B::foo()被调用,那么你就错了。同样,在new B的时候,A的构造函数被调用,但是在A的构造函数中,被调用的是A::foo()而不是B::foo()。
3.3 多继承中的虚函数 3.4 什么时候使用虚函数
在你设计一个基类的时候,如果发现一个函数需要在派生类里有不同的表现,那么它就应该是虚的。从设计的角度讲,出现在基类中的虚函数是接口,出现在派生类中的虚函数是接口的具体实现。通过这样的方法,就可以将对象的行为抽象化。
以设计模式[2]中Factory Method模式为例,Creator的factoryMethod()就是虚函数,派生类override这个函数后,产生不同的Product类,被产生的Product类被基类的AnOperation()函数使用。基类的AnOperation()函数针对Product类进行操作,当然Product类一定也有多态(虚函数)。
另外一个例子就是集合操作,假设你有一个以A类为基类的类层次,又用了一个std::vector<A *>来保存这个类层次中不同类的实例指针,那么你一定希望在对这个集合中的类进行操作的时候,不要把每个指针再cast回到它原来的类型(派生类),而是希望对他们进行同样的操作。那么就应该将这个“一样的操作”声明为virtual。
现实中,远不只我举的这两个例子,但是大的原则都是我前面说到的“如果发现一个函数需要在派生类里有不同的表现,那么它就应该是虚的”。这句话也可以反过来说:“如果你发现基类提供了虚函数,那么你最好override它”。
Top
13 楼xlsue(小林)回复于 2005-07-14 13:37:02 得分 4
不错啊!学习!
“虚构造函数?
构造函数不能是虚的。”,话虽如此,但可以绕过去,一个东西只要类似一个东西,它就是一个东西!-->>见BS《The C++Programming Language》15.6.2 虚构造函数
Top
14 楼liyuan02(梨园)回复于 2005-07-14 17:28:17 得分 4
学习。。。Top
15 楼yhbttfile(小兵)回复于 2005-07-14 21:53:07 得分 4
同意“ xzgyb(老达摩) ”的说法。
多态可以存在静态形式的多态和动态形式的多态。
静态形式的多态是使用模板实现的,动态形式的多态是使用继承实现的。
Top
16 楼xjp6688(大平/要做必须最好)回复于 2005-07-14 23:54:25 得分 4
虚函数是实现多态的一种方法,建议看inside the c++ objedt modelTop
17 楼jiangsheng(蒋晟.Net[MVP])回复于 2005-07-15 00:43:30 得分 4
多态性是同样的代码可以实现不同的功能的性质。
C++中操作符重载、虚函数是比较常见的多态实现。一些人认为摸板和参数传递时的自动类型转换也属于多态Top
18 楼solomon_zyl()回复于 2005-07-15 14:20:22 得分 0
我认为分数是小事
能学到东西才是最重要的
所以请高手们能毫无保留的说出你知道的一切
谢谢Top
19 楼pipilupzj(皮皮鲁)回复于 2005-07-15 15:47:26 得分 0
markTop




