200 likes | 296 Views
第六章 运行时的多态性和抽象类. 运行时多态性概念 滞后联编和虚函数 利用虚函数设计具有运行多态性程序的方法. 多态性. 定义 - 相同对象收到相同消息,不同对象收到相同消息时,产生不同的处理结果。 分类 重载多态性 :同一个类中成员函数重载 继承多态性 :派生类对基类的覆盖和重载实现 运行时多态性 :对一个类层次来说,动态确定的类层次中的对象不同,则对象调用成员函数不同。 参数多态性 :用参数方法决定一个类的数据类型,模板实现。. 滞后联编和运行时多态性. 联编 就是将一个标识符名和一个存储地址联系在一起。在传统的程序设计语言中
E N D
第六章 运行时的多态性和抽象类 运行时多态性概念 滞后联编和虚函数 利用虚函数设计具有运行多态性程序的方法
多态性 • 定义 - 相同对象收到相同消息,不同对象收到相同消息时,产生不同的处理结果。 • 分类 • 重载多态性:同一个类中成员函数重载 • 继承多态性:派生类对基类的覆盖和重载实现 • 运行时多态性:对一个类层次来说,动态确定的类层次中的对象不同,则对象调用成员函数不同。 • 参数多态性:用参数方法决定一个类的数据类型,模板实现。
滞后联编和运行时多态性 • 联编就是将一个标识符名和一个存储地址联系在一起。在传统的程序设计语言中 • 早期联编是在源程序编译时进行联编。 • 滞后联编就是在程序运行时进行联编。在运行时才根据当时具体的取值确定对象所属类层次中的某个类的编译方法。 • 运行时的多态性是指系统在运行时才具体确定对象所属类层次中的某个类,从而确定外部程序所发消息的匹配对象。
滞后联编和运行时多态性 • C++语言既兼容早期联编又支持滞后联编的编译方法。 • C++语言是在C语言基础上的扩充和发展。 • 滞后联编的语言的缺点是程序运行速度慢。 C++语言采用折中方法: • 凡是虚函数(前边标有关键字virtual的成员函数),采用滞后联编的方式编译; • 其它成员函数,采用早期联编的方式编译 • 一个成员函数是否采用滞后联编由程序设计人员决定
虚函数-定义 • 声明虚函数的语句格式为: virtual <成员函数原型>; • 只有在类层次中才存在虚函数。 • 一般在基类中按如上格式声明某成员函数为虚函数,在派生类中名字、参数个数、参数类型和返回类型都相同的成员函数自动视作虚函数。 • 派生类中声明的虚函数,其成员函数名、参数个数、参数类型以及返回值类型要求和基类中声明的虚函数完全一样 • 要利用赋值兼容,要求派生类必须从基类公有派生。
虚函数-运行时多态性 • 虚函数实现运行时多态性的设计方法: • 设计一个类层次,并在类层次中定义一个或一个以上的虚函数。 • 在外部程序中定义基类的指针对象。 • 程序中既可以把基类对象的地址(或指针值)赋给基类的指针对象,也可以把派生类对象的地址(或指针值)赋给基类的指针对象。 • 当外部程序发送的消息是虚函数时,若此时基类指针对象指向基类对象时,系统就调用基类中的成员函数;若此时基类指针对象指向派生类对象时,系统就调用派生类中的成员函数。 • 如果没有定义虚函数,即使指针指向派生类,调用的仍然是基类中的成员函数。
例6.1 虚函数和派生类 #include <iostream.h> class A { public: A(void){} virtual void vf1(void)const // 虚函数 {cout << "基类的vf1()函数" << endl;} void f2(void)const {cout << "基类的f2()函数" << endl;} }; class AA: public A { public: AA(void){}
void vf1(void)const //虚函数 {cout << "派生类的vf1()函数" << endl;} void f2(void)const //成员函数重载 {cout << "派生类的f2()函数" << endl;} }; void main(void) { A *pa, myA; AA myAA; pa = &myA; //pa当前指向基类对象myA pa->vf1(); pa->f2(); pa = &myAA; //pa当前指向派生类对象myAA pa->vf1(); pa->f2(); myA.f2(); //调用基类的f2()函数 myAA.f2(); //调用派生类的f2()函数 }
虚析构函数 • 引入原因 如果外部程序定义了动态对象,当外部程序结束时,要使用delete运算符删除动态对象。如果外部抽象利用赋值兼容规则,将动态申请的派生类对象地址赋给了基类对象指针,则系统自动调用的是基类的析构函数。有可能引起内存泄漏(只释放了派生类对象的基类子对象动态内存空间,而派生类对象的其它动态内存空间没有被释放)问题。
例6.2 虚析构函数 #include <iostream.h> #include <string.h> class A { public: A(void){} ~A(void) {cout << "基类析构函数" << endl;} }; class AA: public A {private: char *aa; int length; public:
AA(char *message); ~AA(void); }; AA::AA(char *message): A() { length = strlen(message) + 1; aa = new char[length]; //动态申请数据成员的内存空间 strcpy(aa, message); } AA::~AA(void) { delete aa; //动态释放数据成员的内存空间 cout << "派生类析构函数" << endl; } void main(void) { A *pa = new AA("Hello"); delete pa; //释放指针pa所指对象的内存空间 }
纯虚函数和抽象类 • 抽象类 • 定义-没有具体对象存在的类。 • 设计-用纯虚函数方法。 • 特定-抽象类中含有一个或一个以上的纯虚函数。 • 设计原因 • 从各个派生类中抽取共同成员构成的基类只对应一个抽象对象概念。 • 一组对象类中成员变量定义基本没有相同之处,成员函数实现的定义不同,但概念类同。定义抽象类作为类层次的根,并为这组对象提供公共接口(纯虚函数)。 • 实现运行时多态性。
纯虚函数和抽象类 • 纯虚函数 • 在类中声明为虚函数、并且在声明它的类中没有函数体定义的成员函数。其定义必须在派生类中给出。 • 声明格式 virtual <函数原型> = 0; • 当一个类层次的基类中定义有纯虚函数时,如果它的某个 派生类中不覆盖该纯虚函数,则派生类仍被认为是抽象类。 • 不能在一个以非抽象类产生的派生类中定义纯虚函数。 • 派生类覆盖基类中定义的纯虚函数时,前面加或不加virtual均可以。
例6.3 纯虚函数实现 #include <iostream.h> class Shape { public: virtual void draw() = 0; }; class Rectangle: public Shape { public: void draw(){ cout<<“绘制矩形”<<endl; } }; class Circle : public Shape { public: void draw(){ cout<<“绘制圆”<<endl; } };
Void main(void) { Shape *p; p = new Rectangle(); p->draw(); p=new Circle(); p->draw(); }
设计举例 • 设计要求 • 设计能同时存储学校教学人员和非教学人员的异质单链表类。
例5-4 有一个整数类型顺序表对象,一个浮点数类型顺序表对象,要求把整数类型顺序表对象中存放的数据元素,转换为浮点类型后,存放到浮点数类型顺序表对象中。要求设计一个验证主函数,并分析转换存储函数的运行效率。 设计:设计一个整数类型顺序表类IntSeqList和一个浮点数类型顺序表类FloatSeqList,然后设计一个把整数类型顺序表对象转换为浮点类型顺序表对象的函数。因为这个转换函数需要使用整数类型顺序表类的参数和浮点类型顺序表类的参数,所以定义这个转换函数为这两个类的友元函数。 #include <iostream.h> #include <stdlib.h> const int MaxSize = 10; class FloatSeqList;
class IntSeqList { friend void IntToFloat(IntSeqList &ist, FloatSeqList &fst); //定义友元函数 private: int data[MaxSize]; //int类型的有MaxSize个元素的数组 int size; public: IntSeqList(void):size(0){} ~IntSeqList(void){} int ListSize(void) const {return size;} void Insert(const int &item, const int pos); int Delete(const int pos); int GetData(const int pos) const; };
class FloatSeqList { friend void IntToFloat(IntSeqList &ist, FloatSeqList &fst); //定义为友元函数 private: float data[MaxSize]; //int类型的有MaxSize个元素的数组 int size; public: FloatSeqList(void){size = 0;} ~FloatSeqList(void){} int ListSize(void) const {return size;} void Insert(const float &item, const int pos); float Delete(const int pos); float GetData(const int pos) const; };
void IntToFloat(IntSeqList &ist, FloatSeqList &fst) { fst.size = ist.size; //当前数据元素个数赋值 for(int i = 0; i < ist.size; i++) fst.data[i] = float(ist.data[i]); //数据元素类型转换后赋值 } void main(void) //测试主函数 { IntSeqList MyIntList; FloatSeqList MyFloatList; for(int i = 0; i < MaxSize; i++) //为顺序表堆栈式插入数据元素 MyIntList.Insert(i, 0); //9 8 7 6 5 4 3 2 1 0 IntToFloat(MyIntList, MyFloatList); //转换 for(i = 0; i < MyFloatList.ListSize(); i++) //输出 cout << MyFloatList.GetData(i) << endl; }