您的位置:首页 > 编程语言 > C语言/C++

多态——polymorphism

2016-06-05 17:28 441 查看

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


声明:以上内容整理自中山大学万海讲师上课所授。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  多态 cpp C++ 虚函数