【转】什么是多态,怎样实现多态
C++中多态是怎样实现的?
多态是⼀种不同的对象以单独的⽅式作⽤于相同消息的能⼒,这个概念是从⾃然语⾔中引进的。例如,动
词“关闭”应⽤到不同的事务上其意思是不同的。关门,关闭银⾏账号或关闭⼀个程序的窗⼝都是不同的⾏
为;其实际的意义取决于该动作所作⽤的对象。
⼤多数⾯向对象语⾔的多态特性都仅以虚拟函数的形式来实现,但C++除了⼀般的虚拟函数形式之外,还多
了两种静态的(即编译时的)多态机制:
1、操作符重载:例如,对整型和串对象应⽤ += 操作符时,每个对象都是以单独的⽅式各⾃进⾏解释。显
然,潜在的 += 实现在每种类型中是不同的。但是从直观上看,我们可以预期结果是什么。
2、模板:例如,当接受到相同的消息时,整型vector对象和串vector对象对消息反映是不同的,我们以关闭
⾏为为例:
vector < int > vi; vector < string > names;
string name("VC知识库");
vi.push_back( 5 ); // 在 vector 尾部添加整型
names.push_back (name); // 添加串和添加整型体现差别的潜在的操作
静态的多态机制不会导致与虚拟函数相关的运⾏时开。此外,操作符重载和模板两者是通⽤算法最基本的东
西,在STL中体现得尤为突出。
那么接下来我们说说以虚函数形式多态:
通常都有以重载、覆盖、隐藏来三中⽅式,三种⽅式的区别⼤家应该要很深⼊的了解,这⾥就不多说
了。
许多开发⼈员往往将这种情况和C++的多态性搞混淆,下⾯我从两⽅⾯为⼤家解说:
1、编译的⾓度
C++编译器在编译的时候,要确定每个对象调⽤的函数的地址,这称为早期绑定(early binding)。
2、内存模型的⾓度
为了确定对象调⽤的函数的地址,就要使⽤迟绑定(late binding)技术。当编译器使⽤迟绑定时,就会在运
⾏时再去确定对象的类型以及正确的调⽤函数。⽽要让编译器采⽤迟绑定,就要在基类中声明函数时使⽤
virtual关键字(注意,这是必须的,很多开发⼈员就是因为没有使⽤虚函数⽽写出很多错误的例⼦),这样
的函数我们称为虚函数。⼀旦某个函数在基类中声明为virtual,那么在所有的派⽣类中该函数都是virtual,⽽
不需要再显式地声明为virtual。
那么如何定位虚表呢?编译器另外还为每个类的对象提供了⼀个虚表指针(即vptr),这个指针指向了对象
所属类的虚表。在程序运⾏时,根据对象的类型去初始化vptr,从⽽让vptr正确的指向所属类的虚表,从⽽在
调⽤虚函数时,就能够到正确的函数。
正是由于每个对象调⽤的虚函数都是通过虚表指针来索引的,也就决定了虚表指针的正确初始化是⾮常重要
的。换句话说,在虚表指针没有正确初始化之前,我们不能够去调⽤虚函数。那么虚表指针在什么时候,或
者说在什么地⽅初始化呢?
答案是在构造函数中进⾏虚表的创建和虚表指针的初始化。还记得构造函数的调⽤顺序吗,在构造⼦类对象
时,要先调⽤⽗类的构造函数,此时编译器只“看到了”⽗类,并不知道后⾯是否后还有继承者,它初始化⽗
类对象的虚表指针,该虚表指针指向⽗类的虚表。当执⾏⼦类的构造函数时,⼦类对象的虚表指针被初始
化,指向⾃⾝的虚表。
要注意:对于虚函数调⽤来说,每⼀个对象内部都有⼀个虚表指针,该虚表指针被初始化为本类的虚表。所
以在程序中,不管你的对象类型如何转换,但该对象内部的虚表指针是固定的,所以呢,才能实现动态的对
象函数调⽤,这就是C++多态性实现的原理。
总结(基类有虚函数):
1、每⼀个类都有虚表。
2、虚表可以继承,如果⼦类没有重写虚函数,那么⼦类虚表中仍然会有该函数的地址,只不过这个地址指
向的是基类的虚函数实现。如果基类3个虚函数,那么基类的虚表中就有三项(虚函数地址),派⽣类也会
有虚表,⾄少有三项,如果重写了相应的虚函数,那么虚表中的地址就会改变,指向⾃⾝的虚函数实现。如
果派⽣类有⾃⼰的虚函数,那么虚表中就会添加该项。
3、派⽣类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。
封装、继承、多态,⾯向对象的三⼤特性,前两项理解相对容易,但要理解多态,特别是深⼊的了解,对于初学者⽽⾔可能就会有⼀定困难
把它跟“多态”这个词对应起来。在此抛砖引⽟,⼤家讨论,个⼈能⼒有限,不⾜之处还请指正。
之前看到过类似的问题:如果⾯试时主考官要求你⽤⼀句话来描述多态,尽可能的精炼,你会怎么回答?当然答案有很多,每个⼈的理解和表达不尽相同,但我⽐较趋向这样描述:通过继承实现的不同对象调⽤相同的⽅法,表现出不同的⾏为,称之为多态。
例1:
代码
public class Animal
{
public virtual void Eat()
{
Console.WriteLine("Animal eat");
}
}
public class Cat : Animal
{
public override void Eat()
{
Console.WriteLine("Cat eat");
}
}
public class Dog : Animal
{
public override void Eat()
{
Console.WriteLine("Dog eat");
}
}
class Tester
{
static void Main(string[] args)
{
Animal[] animals = new Animal[3];
animals[0] = new Animal();
animals[1] = new Cat();
animals[2] = new Dog();
for (int i = 0; i <</span> 3; i++)
{
animals[i].Eat();
}
}
}
输出如下:
在上⾯的例⼦中,通过继承,使得Animal对象数组中的不同的对象,在调⽤Eat()⽅法时,表现出了不同的⾏为。
writeline方法属于类多态的实现看起来很简单,要完全理解及灵活的运⽤c#的多态机制,也不是⼀件容易的事,有很多需要注意的地⽅。
1. new的⽤法
先看下⾯的例⼦。
例2:
代码
public class Animal
{
public virtual void Eat()
{
Console.WriteLine("Animal eat");
}
}
public class Cat : Animal
{
public new void Eat()
{
Console.WriteLine("Cat eat");
}
}
class Tester
{
static void Main(string[] args)
{
Animal a = new Animal();
a.Eat();
Animal ac = new Cat();
ac.Eat();
Cat c = new Cat();
c.Eat();
}
}
运⾏结果为:
可以看出,当派⽣类Cat的Eat()⽅法使⽤new修饰时,Cat的对象转换为Animal对象后,调⽤的是Animal类中的Eat()⽅法。其实可以理解为,使⽤new关键字后,使得Cat中的Eat()⽅法和Animal中的Eat()⽅法成为毫不相关的两个⽅法,只是它们的名字碰巧相同⽽已。所以,Animal类中的Eat()⽅法不管⽤还是不⽤virtual修饰,也不管访问权限如何,或者是没有,都不会对Cat的Eat()⽅法产⽣什么影响(只是因为使⽤了new关键字,如果Cat类没⽤从Animal类继承Eat()⽅法,编译器会输出警告)。
我想这是设计者有意这么设计的,因为有时候我们就是要达到这种效果。严格的说,不能说通过使⽤new来实现多态,只能说在某些特定的时候碰巧实现了多态的效果。
2.override实现多态
真正的多态使⽤override来实现的。回过去看前⾯的例1,在基类Animal中将⽅法Eat()⽤virtual标记为虚拟⽅法,再在派⽣类Cat和Dog中
⽤override对Eat()修饰,进⾏重写,很简单就实现了多态。需要注意的是,要对⼀个类中⼀个⽅法⽤override修饰,该类必须从⽗类中继承了⼀个对应的⽤virtual修饰的虚拟⽅法,否则编译器将报错。
好像讲得差不多了,还有⼀个问题,不知道你想没有。就是多层继承中⼜是怎样实现多态的。⽐如类A是基类,有⼀个虚拟⽅
法method()(virtual修饰),类B继承⾃类A,并对method()进⾏重写(override修饰),现在类C⼜继承⾃类B,是不是可以继续对method()进⾏重写,并实现多态呢?看下⾯的例⼦。
例3:
代码
public class Animal
{
public virtual void Eat()
{
Console.WriteLine("Animal eat");
}
}
public class Dog : Animal
{
public override void Eat()
{
Console.WriteLine("Dog eat");
}
}
public class WolfDog : Dog
{
public override void Eat()
{
Console.WriteLine("WolfDog eat");
}
}
class Tester
{
static void Main(string[] args)
{
Animal[] animals = new Animal[3];
animals[0] = new Animal();
animals[1] = new Dog();
animals[2] = new WolfDog();
for (int i = 0; i <</span> 3; i++)
{
animals[i].Eat();
}
}
}
运⾏结果为:
在上⾯的例⼦中类Dog继承⾃类Animal,对⽅法Eat()进⾏了重写,类WolfDog⼜继承⾃Dog,再⼀次对Eat()⽅法进⾏了重写,并很好地实现了多态。不管继承了多少层,都可以在⼦类中对⽗类中已经重写的⽅法继续进⾏重写,即如果⽗类⽅法⽤override修饰,如果⼦类继承了该⽅法,也可以
⽤override修饰,多层继承中的多态就是这样实现的。要想终⽌这种重写,只需重写⽅法时⽤sealed关键字进⾏修饰即可。
3. abstract-override实现多态
先在我们在来讨论⼀下⽤abstract修饰的抽象⽅法。抽象⽅法只是对⽅法进⾏了定义,⽽没有实现,如果⼀个类包含了抽象⽅法,那么该类
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论