多态——polymorphism

来源:互联网 发布:欠淘宝消保 编辑:程序博客网 时间:2024/05/18 13:08

多态——polymorphism

1、定义——什么是多态:

在CPP中,一个符号或者一个保留字或者一个函数名有多种意义的现象,称为多态。

如:符号*:
c = a*b; int* p; *p=1;既可以表示乘号,也可以表示指针符号,也可以表示解引用操作。
一个简单的多态例子:

int abs(int x) // 整数类型数据的绝对值函数{   cout << "Using integer version of abs().\n";    return (x >= 0 ? x : -x);}double abs(double x) // 浮点类型数据的绝对值函数{   cout << "Using floating-point version of abs().\n";    return (x >= 0.0 ? x : -x);}long abs(long x) // 长整数类型数据的绝对值函数{   cout << "Using long integer version of abs().\n";    return (x >= 0 ? x : -x);}int main(){   cout << abs(-5) << "\n"; // 调用abs()的整数版本    cout << abs(-5L) << "\n"; // 调用abs()的长整数版本    cout << abs(3.14) << "\n"; // 调用abs()的浮点版本    return 0;}

2、分类——有哪些多态情况:

  • 编译时多态性——编译时已经完成:
    编译时的多态有以下情况:
    因函数的重载导致的多态
    因运算符的重载导致的多态

  • 运行时多态性——运行时才完成,跟动态绑定有关如虚函数。

1)何为动态绑定?

绑定就是将函数的声明和函数的定义连接在一起的过程。

绑定分为两种,一种是静态绑定:静态绑定在编译时就已经完成,非虚函数采用静态绑定。它基于调用它的类型、在编译时就已经确定,并且无法改变。

另一种是动态绑定:在运行时才进行的绑定,虚函数采用动态绑定。它基于调用函数的基类的子类的特定类型对象,是在运行时确定的,可以改变。

简言之,就是说非虚函数由形参类型决定调用哪个函数(静态绑定),而虚函数由实参类型决定调用哪个函数(动态绑定)。

将成员函数声明为虚函数可以指示编译器生成代码时保证动态绑定。

2)那末,虚函数又是什么?

虚函数是类中的成员函数,它的不同之处在于在函数的前面加上了保留字“virtual”。

1、它具有“一旦为虚,永远为虚”的特性,即一个函数若是虚函数,那末在继承它的派生类中,它仍然是虚函数。

2、用虚函数实现动态绑定的关键:必须用基类指针(或基类引用)来访问虚函数。

3、若一函数是类中的虚函数,则称该函数具有虚特性。

4、在派生类中重定义从基类中继承过来的虚函数(函数原型保持不变) ,该重定义的函数在该派生类中仍是虚函数。

5、函数重载,虚特性丢失。

6、当一个派生类没有重新定义虚函数时,则使用其基类定义的虚函数版本。

下面举4个栗子:

栗子一号:

//time.h:class Time{ public:    void Set ( int hours , int minutes , int seconds ) ;    void Increment ( ) ;    void Write ( ) const ;    Time ( int initHrs, int initMins, int initSecs ) ;    Time ( ) ; private:    int hrs ;    int mins ;    int secs ;} ;
// exttime.h#include “time.h”enum ZoneType {EST, CST, MST, PST, EDT, CDT, MDT, PDT } ;class ExtTime : public Time // Time is the base class{ public:    ExtTime ( int initHrs , int initMins , int initSecs ,ZoneType initZone ) ; // constructor    ExtTime ( ) ; // default constructor    void Set ( int hours, int minutes, int seconds ,ZoneType timeZone ) ;    void Write ( ) const ; private:    ZoneType zone ; // added data member};
//client.cpp:#include "iostream"using namespace std;void Print (Time someTime ){    cout << “Time is “ ;    someTime.Write ( ) ;    cout << endl ;}int main() {    Time startTime ( 8, 30, 0 ) ;    ExtTime endTime (10, 45, 0, CST) ;    Print ( startTime ) ;    Print ( endTime ) ;    return 0;}

输出:

Time is 08:30:00Time is 10:45:00

为什么会出现两个输出都调用基类的函数的情况呢?

因为函数Print已经因为someTime.Write()的调用而在编译的时候静态绑定了Time::Write(),因为someTime是一个Time类型的。

那末,如何实现根据调用函数的对象的不同而选择的函数不同呢?这很简单,把Write函数声明成虚函数即可:

class Time { public:    void Set ( int hours , int minutes , int seconds ) ;    void Increment ( ) ;    virtual void Write ( ) const ;    Time ( int initHrs, int initMins, int initSecs ) ;    Time ( ) ; private:    int hrs ;    int mins ;    int secs ;} ;

输出:

Time is 08:30:00Time is 10:45:00 CST达到目的!

这个栗子说明,非虚函数由形参类型决定调用哪个函数(静态绑定),而虚函数由实参类型决定调用哪个函数(动态绑定)

栗子二号:

使基类指针指向不同的派生类时实现的多态:

class BASE {public:    void who( ) { cout<<"BASE\n";}};class FIRST_D:public BASE {public:    // 继承成员的重定义    void who( ) { cout<<"The First Derivation\n";}};class SECOND_D:public BASE {public:    void who( ) { cout<<"The Second Derivation\n";}};
int main() {    BASE b_obj;    FIRST_D f_obj;    SECOND_D s_obj;    BASE *p; // 定义指向基类的指针    p= &b_obj; p->who();    p= &f_obj; p->who(); // 根据赋值兼容性规则    p= &s_obj; p->who(); // 基类指针可指向派生类对象    return 0;}

输出:

BASEBASEBASE

原因:
不管p指向什么对象,通过p三次调用的都是基类的who函数。
调用普通成员函数采用静态绑定方式。通过指针(或引用)调用普通成员函数,仅仅与指针(或引用)的原始类型有关,而与该指针当前所指向(或引用当前所关联)的对象无关。

更正:将基类BASE修改为:

class BASE {public:    virtual void who() {     cout << “BASE\n”;    }};

输出结果则是:

BASEThe First DerivationThe Second Derivation

解释:函数调用p->who()进行动态绑定:实际调用哪个who函数依赖于运行时p所指向的对象

栗子三号:

动态绑定的另一实现方式:使用引用形参。

class BASE {public:    void who( ) { cout<<"BASE\n";}};class FIRST_D:public BASE {public:    void who( ) { cout<<"The First Derivation\n";}};class SECOND_D:public BASE {public:    void who( ) { cout<<"The Second Derivation\n";}};
void print_identity( BASE& me ){    me.who(); //通过基类引用调用虚函数}void main( ){    BASE b_obj;    FIRST_D f_obj;    SECOND_D s_obj;    print_identity(b_obj);    print_identity(f_obj);    print_identity(s_obj);}

输出结果是:

BASEThe First DerivationThe Second Derivation

栗子四号:说明函数重载和函数重定义对虚函数的影响:

class BASE {public:    virtual void f1( ) { cout<<"BASE::f1()"<<endl; }    virtual void f2( ) { cout<<"BASE::f2()"<<endl; }    virtual void f3( ) { cout<<"BASE::f3()"<<endl; }    void f ( ) { cout<<"BASE::f()"<<endl; }};class DERIVED:public BASE {public:    void f1( ) { cout<<"DERIVED::f1()"<<endl; }    //虚函数的重定义,f1在该类中还是虚函数    void f2( int ) { cout<<"DERIVED::f2()"<<endl; }    //f2是函数重载,虚特性丢失    void f ( ) { cout<<"DERIVED::f()"<<endl; } // 普通函数的重定义};
int main( ){    DERIVED d;    BASE *p = &d ; // 基类指针p指向派生类对象    p->f1( ); //调用DERIVED::f1( ); 动态绑定    p->f2( ); //调用BASE::f2( ); 静态绑定    p->f ( ); //调用BASE::f( ); 静态绑定    ((DERIVED *)p)->f2(100); //调用DERIVED::f2( ); 静态绑定    return 0;}

声明:以上内容整理自中山大学万海讲师上课所授。

1 0
原创粉丝点击