多态——polymorphism

多态——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:00
Time 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:00
Time 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;
}

输出:

BASE
BASE
BASE

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

更正:将基类BASE修改为:

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

输出结果则是:

BASE
The First Derivation
The 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);
}

输出结果是:

BASE
The First Derivation
The 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;
}

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

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值