多态——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; }
声明:以上内容整理自中山大学万海讲师上课所授。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- 关于指针的一些事情
- c++ primer 第五版 笔记前言
- share_ptr的几个注意点
- c#中虚函数的相关使用方法
- C#与.net高级编程 C#的多态介绍
- C#中面向对象编程机制之多态学习笔记
- Lua中调用C++函数示例
- Lua教程(一):在C++中嵌入Lua脚本
- Lua教程(二):C++和Lua相互传递数据示例
- C#中的多态深入理解
- C#中多态、重载、重写区别分析
- 设计引导--一个鸭子游戏引发的设计理念(多态,继承,抽象,接口,策略者模式)
- C++联合体转换成C#结构的实现方法
- C++高级程序员成长之路
- C++编写简单的打靶游戏
- C++ 自定义控件的移植问题
- C++变位词问题分析
- C/C++数据对齐详细解析
- C++基于栈实现铁轨问题