多态——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;
}
声明:以上内容整理自中山大学万海讲师上课所授。