多态性是面向对象编程的三大特性之一。他使我们编写的程序可以使用同一种函数方法处理同一个基类下的同一层次的所有类的对象而产生不同的操作。多态性是利用基类的指针句柄和引用句柄,而不是利用名字句柄。
多态性就是不同的消息发送给各种不同的对象而产生不同的结果。
比如,一个Animal基类派生Bird类,Fish类,People类。而每个类中都含有move成员函数。对于每个派生类所实例化的对象,调用move函数产生的结果却不相同。比如bird是飞,fish是游,people是走。对于同样的函数调用,以来每个对象自己做出恰当的想用,这就是多态性的关键思想。
普通继承调用机制:
由于继承的原理是“si-a”,也就是说,派生类就是基类。所以,基类的指针是可以指向派生类的。然而,当派生类重写了基类的普通成员函数时,一个基类指针指向派生类,此时,当此指针调用成员函数时,调用的是基类的成员函数。
如:
Class Animal
{
Public:
Void print() const
{
Cout<<”I am animal;”<<endl;
}
};
Class People : public Animal
{
Public:
Void print() const
{
Cout<<”I am people;”<<endl;
}
};
Void main()
{
Animal animalone;
People peopleone;
Animal *animalPtr=&animalone;
People *peoplePtr=&peopleone;
animalPtr->print();
peoplePtr->print();
animalPt=&peopleone;
animalPtr->print();
}
显示如下:
I am animal;
I an people;
I am animal; //基类指向派生类调用函数,调用的是基类的成员函数。
如果一个基类指向一个派生类,而希望调用一个只有派生类才有的成员函数,会产生编译错误。解决方法是强制性的显式向下类型转化。
virtual函数(虚函数)
对于virtual函数,不再根据指针类型调用函数,而根据指针所指向的对象,调用函数。调用哪个类的成员函数不再是在编译阶段圈定(静态联编),而是动态的在执行阶段悬在正确的成员函数(动态联编)。
如一个基类Shape,派生类:Circle,Rectangle,Square。他们都重载了基类Shape中的虚函数draw()。当一个基类指针指向它们,指针->draw()时,C++根据指针指向的类调用此派生类中重载的draw()成员函数。
一旦一个函数声明为virtual,那它继承层次中以下所有的派生类都默认为virtual函数(隐式默认)。但我们应该尽量显式的声明,其好处如下:
1. 清晰可见,使程序易懂,以维护。
2. 在一个继承层次中,如果我们要继承一个类,这个类中一个virtual函数是隐式声明的,如果我们以为他是一个普通的成员函数而使用,那么会出现一些逻辑错误。(而且特别不容易被发现!!!!)
对于一个virtual函数,派生类中可以不重载,这样编译器会根据正常的继承规则简单的继承基类的virtual函数。(下面说到的纯虚函数必须显式的进行重载。)
同样是Animal基类的例子:
Class Animal
{
Public:
Virtual Void print() const
{
Cout<<”I am animal;”<<endl;
}
};
Class People : public Animal
{
Public:
Virtual Void print() const
{
Cout<<”I am people;”<<endl;
}
};
Class bird : public Animal
{
Public:
Virtual Void print() const
{
Cout<<”I am bird;”<<endl;
}
};
Void main()
{
Animal animalone;
People peopleone;
Brid birdone;
Animal animalPtr=&animalone;
animalPtr->print();
animalPtr=&peopleone;
animalPtr->print();
animalPtr=&birdone;
animalPtr->print();
}
显示:
I am animal;
I am people;
I am bird;
同一基类指针指向不同的类,调用同样的函数,产生不同的结果。这,就是多态的精髓!
抽象类和纯虚函数
对于一些类,我们不打算去实例化,我们定义它仅仅是为了使用它去派生其他类,从而达到代码重用的效果。这种类就是抽象类。我们通常使用抽象类作为基类(我实在想不到除了这个用处,抽象类还有什么用),在抽象类中定义了它所要派生各种派生类的共有特性。通常我们定义的抽象类太过宽泛,无法在语义上实例化为一个对象(当然,语法上也不能实例化)。比如,我们在上面定义的那一个Animal类,我们通常不把他实例化,因为“一个动物”,确实太抽象了。所以,我们把它定义一个抽象类,是一个更好的选择。
抽象类是不需要声明的,只要类中含有一个或多个纯virtual函数,这个类就是抽象类。对于一个抽象类,对它实例化会出现一个编译错误。
纯虚函数:
Virtual void fuction() = 0 ;
纯虚函数没有函数的具体实现。每个派生类都必须对它进行重载,提供这些函数的具体实现。它与虚函数的区别在于:虚函数提供的函数的具体实现,派生类具有是否重载虚函数的选择权。而派生类必须对纯虚函数进行重载。
语义上来说,当一个基类无法对其派生类设置默认函数具体实现,即基类即使实现函数的具体实现,但对派生类来说没有意义,即每个派生类都具有各自不同的函数具体实现时,这时候就要声明为纯虚函数。
如果一个派生类没有重载基类中的纯虚函数,那么这个派生类仍然是一个抽象类。
多态性的重要应用举例
多态性在软件实现中具有非常重要的意义,它主要依靠虚函数和抽象类实现。
比如,再操作系统中,每个物理设备会根据其特点执行迥然不同的操作。但对于操作系统来说,有些操作是没有区别的。如,读写操作,对于具体的物理设备来说,具体实现可能有所不同。但对于操作系统来说,是一样的。而如果操作系统使用多态性的话,只要操作系统提供一个设备的抽象基类,然后再次抽象基类中定义共同的接口。这样,对于不同的设备,只要在设备的驱动程序中继承基类,并且在驱动程序中实现物理设备的具体实现就可以了,而不需要更改操作系统的任何文件。
再比如,定义一个指向基类的指针数组,他同样可以指向任何由基类派生出来的派生类。这样,就可以对这个数组的所有元素调用基类的虚函数,而对于每个元素,他会更具指针所指向的对象而调用函数。