• 1. 第6章 多态性与虚函数多态性的概念 虚函数 纯虚函数与抽象类
  • 2. 6.1 多态性的概念多态引入举例:迷宫游戏,有门、墙、房间等对象。每一个对象都可以接收到同样的消息Enter,但是不同的对象对它的响应是不同的。
  • 3. 理想的解决方案:当不同的对象接受同样的命令时, 不同的对象自动有不同的反应。不需要在主控程序中 去编写大量的判断语句。 C++语言的多态性(Polymorphism)。 解决方案一:游戏主控程序中使用switch语句或if语句,来判断对象的类型然后决定采取相应的操作。
  • 4. 所谓多态性就是不同对象收到相同的消息时,产生不同的动作。 直观地说,多态性是指用一个名字定义不同的函数,这些函数执行不同但又类似的操作,从而可以使用相同的调用方式来调用这些具有不同功能的同名函数。
  • 5. C++语言中的多态性通过绑定(Binding,也称联编)来实现。绑定分为两种: (1)静态绑定:绑定过程发生在编译链接阶段。函数重载和运算符重载就是静态绑定的典型实例。 (2)动态绑定:绑定过程发生在程序运行阶段,即在程序运行遇到了函数调用时再决定调用的是哪一个函数版本。
  • 6. 静态绑定举例class 兔子 { public: … void 逃生(老鹰 a) {“兔子蹬鹰”;} void 逃生 (狼 b) { “动如脱兔”;} };关于兔子的一个例子:
  • 7. 静态绑定实例class Date { private: int year; int month; int day; public: Date(); Date(int y,int m,int d); Date(Date &obj); void print(); };【例】函数重载的例子。
  • 8. Date::Date() { year=0;month=1;day=1; } Date::Date(int y, int m, int d) { year=y; month=m; day=d; } Date::Date(Date &obj) { year=obj.year; month=obj.month; day=obj.day; } void Date::print() {cout<
  • 9. main() { Date d1; Date d2(2007,5,30); Date d3(d2); d1.print(); d2.print(); d3.print(); return 0; } 函数重载的意义在于可以用相同 的名字访问一组相互关联的函数。 在编译时就可以根据实参的类型 和个数确定函数调用语句对应的 函数体代码。
  • 10. 函数重定义:C++程序中允许存在若干函数,有完全相同的函数原型却可以有多种多样的相异的函数体,这种现象称为函数的重定义(overriding)。 虚函数允许函数调用与函数体的绑定在运行时才进行,即采用动态绑定机制。由虚函数实现的多态性也称为运行时多态性。
  • 11. 6.2 虚函数 6.2.1 虚函数的引入 关于宠物的一个例子。class 宠物 { public: void speak() {cout<<"How does a pet speak? " }; … };
  • 12. class 狗:public宠物{ public: void speak(){cout<<"wang!wang!";} … }; class 猫:public宠物 { public: void speak(){cout<<"miao!miao!";} … };
  • 13. main() { pet *p; cat cat1; dog dog1; int n; cin>>n; //根据用户输入,模拟实际情况 if(n==1) p=&cat1; else p=&dog1; p->speak(); return 0; }
  • 14. 1.虚函数的作用 普通成员函数的调用采用的是静态关联。即通过指针(或引用)引起的普通成员函数调用,仅仅与指针(或引用)的类型有关,而与此刻该指针(或引用)正在指向的对象类型无关。6.2.2虚函数的作用和定义
  • 15. 在pet类的speak()方法前面加上一个virtual关键字:class pet { public: void speak(){ cout<<"How does a pet speak? " <
  • 16. virtual关键字修饰的函数就叫做虚函数。 当C++编译器在编译此段程序的时候,发现pet类的speak()函数是虚函数,这时C++就会采用动态绑定技术,即编译时并不确定具体调用的函数,而在运行时依据对象的类型来确认调用的具体是哪一个函数。这种能力就叫做C++的多态性。
  • 17. #include class Base { public: Base(int x,int y) { a=x; b=y; } virtual void show() //定义虚函数show() { cout<<"Base----------\n"; cout<
  • 18. class Derived : public Base { public: Derived(int x,int y,int z):Base(x,y) {c=z; } void show() //重新定义虚函数show() { cout<< "Derived---------\n"; cout<
  • 19. void main() { Base mb(60,60),*pc; Derived mc(10,20,30); pc=&mb; pc->show(); //调用基类Base的show()版本 pc=&mc; pc->show();//调用派生类Derived的show()版本 }
  • 20. 虚函数就是在基类中被关键字virtual说明,并在派生类中重新定义的成员函数。 在派生类中重定义时,其函数原型,包括返回类型、函数名、参数个数与参数类型的顺序,都必须与基类中的原型完全相同。2.虚函数的定义
  • 21. virtual 函数类型 函数名(形参表) { // 函数体 } 其中,关键字virtual表明当前所定义的函数为虚函数。 虚函数必须是类的成员函数,而且是非静态成员函数。定义虚函数的方法如下:
  • 22. class Grandam { public: virtual void introduce_self() // 定义虚函数introduce_self() { cout<<"I am grandam."<
  • 23. class Daughter:public Mother { public: void introduce_self() // 重新定义虚函数introduce_self() { cout<<"I am daughter."<
  • 24. int main() { Grandam *ptr; Grandam g; Mother m; Daughter d; ptr=&g; ptr->introduce_self(); ptr=&m; ptr->introduce_self(); ptr=&d; ptr->introduce_self(); }
  • 25. (1)在基类中说明某一函数成员f()为虚函数,方法是在说明前加关键字“virtual”; (2)在基类的各个派生类中定义与f()的原型完全相同(但函数体可以各异)的函数成员f(),无论是否用关键字“virtual”来说明,它们都自动地被定义为虚函数。虚函数定义的几点说明:
  • 26. 【例】引用和虚函数体现的多态性。class Rectangle : public Point { double w, h; public: Rectangle (double i, double j, double k, double l):Point (i,j) { w=k; h=l; } double Area( )const { return w*h; } }; class Point { double x, y; public: Point (double i, double j) { x=i; y=j; } virtual double Area( ) const { return 0; } };
  • 27. void fun(Point &s) { cout<
  • 28. 虚函数与函数重载既有相似之处,又相互区别。 共同点:它们都是在程序中设置一组同名函数,都反映了面向对象程序中的多态性特征。 6.2.3 虚函数与重载函数的关系
  • 29. 不同点: (1)虚函数不仅同名,而且同原型。 (2)虚函数仅用于基类和派生类之中,必须是类的一个成员函数,不能是友元函数,也不能是静态的成员函数。而函数重载,可以是类内或者是类外函数。 (3)虚函数需要在程序运行时动态绑定以确定具体函数代码,而重载函数在编译时即可确定。
  • 30. class Shape { double x, y; public: Shape(){ } Shape(double i, double j) { x=i; y=j; } virtual double area(){return 0.0; } };【练习】 分析下面程序的运行结果。
  • 31. class Circle:public Shape { double radius; public: Circle(double r) { radius=r; } double area() { return pi*radius*radius; } };const double pi=3.1415926;
  • 32. double func(Shape & p) { return p.area(); } double func2(Shape p) { return p.area(); } double func(Shape * p) { return p->area(); } int main() { Circle c(2); cout<
  • 33. 6.3 纯虚函数与抽象类6.3.1 纯虚函数 纯虚函数是一种特殊的虚函数。 声明一个成员函数为纯虚函数的语法为: virtual <类型> <函数名>(<参数列表>)=0; 纯虚函数多用在一些方法行为的设计上。在设计基类时,不太好确定或将来的行为多种多样,而此行为又是必需的,就可以在基类的设计中,以纯虚函数来声明此种行为,而不具体实现它。(举例:Figure)
  • 34. class Figure { double x, y; public: Figure(double i, double j) { x=i; y=j; } virtual void show_area( )=0; };
  • 35. class Triangle:public Figure { Triangle(double a,double b):Figure(a,b){} void show_area() { cout<<“Triangle with height”<
  • 36. void show(Figure * p) { p->show_area(); }
  • 37. 注意: (1)纯虚函数可以没有函数体。 (2)纯虚函数声明中最后的“=0”用于表明当前的函数为一个纯虚函数,不是函数的返回值。 (3)在派生类中没有重新定义纯虚函数之前,不能调用虚函数。 (4)当在基类中声明一个纯虚函数后,派生类应该提供自己的实现版本,如果不提供,则该函数在派生类中仍然为纯虚函数。 (5)纯虚函数也是虚函数,有关虚函数使用的所有有关事项也适合纯虚函数。
  • 38. 6.3.2 抽象类 一个类可以包含一个或多个纯虚函数,包含纯虚函数的类称为抽象类。抽象类不能创建任何实例。 抽象类存在的意义在于作为基类被其他类继承。抽象类有时候也成为“抽象基类”。相对抽象类而言,可以实例化对象的类就是具体类。
  • 39. 只能用作其他类的基类 不能用于直接创建对象实例 可声明抽象类的指针和引用 抽象类的特性:
  • 40. #include class Circle { public: void setr(int x) { r=x; } virtual void show()=0; // 纯虚函数 protected: int r; }; 例6.9纯虚函数的使用。
  • 41. class Area:public Circle{ public: void show() { cout<<"Area is "<<3.14*r*r<
  • 42. int main() { Circle *ptr; Area ob1; Perimeter ob2; ob1.setr(10); ob2.setr(10); ptr=&ob1; ptr->show(); ptr=&ob2; ptr->show(); return 0; }
  • 43. 总结: 抽象类的唯一用途是为派生类提供基类,纯虚函数的作用是作为派生类的成员函数的基础,提供统一的接口函数,并实现动态多态性。
  • 44. 补充:虚析构函数 类的析构函数可以是虚函数,而且很多时候需要将类的析构函数声明为虚函数。 【例】 分析下面程序的运行结果。 #include #include class A{ public: ~A( ) { cout<<" ~A of A is called"; } };
  • 45. class D: public A{ public: D (char *s) { str = new char[strlen(s)+1]; strcpy(str,s); } ~D ( ) { delete [] str; cout << " ~D of D is called.\n"; } private: char *str; };
  • 46. void main( ) { A *ptr;//声明基类指针 ptr=new D ("test string"); delete ptr; cout<
  • 47. 将基类的析构函数声明为虚析构函数,该基类的所有派生类的析构函数自动成为虚函数。 析构函数声明为虚函数后,就会引发动态绑定。即程序不根据指针的类型来选择执行哪个类的析构函数,而是根据指针所指具体对象的实际类型来确定所要执行的析构函数。
  • 48. 虚析构函数是一种特殊的虚函数,重定义时不要同名。 普通虚函数只是调用相应类中的函数,而虚析构函数是先调用相应类中的析构函数,然后逐层向上调用基类的析构函数。
  • 49. 思考: 构造函数能否为虚函数???
  • 50. 练习: 一个简化的学校基本信息管理的例子。class School{ public: School(char *); virtual ~School(); virtual void show()=0; protected: char *name; };School::School(char *str) { name=new char[strlen(str)+1]; strcpy(name,str); } School::~School() { delete []name; }
  • 51. 管理小学信息:从School类派生出小学类ElementarySchool class ElementarySchool: public School { public: ElementarySchool(char *loc, char *schname); ~ElementarySchool(); void show(); private: char *location; };
  • 52. ElementarySchool::ElementarySchool(char*loc,char*schname) :School(schname) { location=new char[strlen(loc)+1]; strcpy( location, loc); } ElementarySchool:: ~ElementarySchool( ) { delete []location; } void ElementarySchool :: show() { cout<<"A Elementary School: " <
  • 53. 编写主控制程序,实现对类ElementarySchool对象的管理: void showSchool(School &sch) { sch.show(); } main() { ElementarySchool es("changsha hunan","heishidu ElementarySchool"); showSchool(es); return 0; }
  • 54. 管理中学信息:从School类派生出新类HighSchool class HighSchool:public School{ public: HighSchool(int num,char *schname); void show(); private: int stuNum; }; HighSchool::HighSchool(int num,char *schname): School(schname) { stuNum=num; } void HighSchool::show() { cout<<"A High School: "<
  • 55. void showSchool(School &sch) { sch.show(); } main() { ElementarySchool es("changsha hunan","heishidu ElementarySchool"); showSchool(es); HighSchool hs(3000,"zhoukou High School"); showSchool(hs); return 0; }
  • 56. 管理大学信息:从School类派生出类University class University:public School{ public: University(int num,char *schname); void show(); private: int collegeNum; }; University::University(int num,char *schname):School(schname) { collegeNum=num; } void University::show( ) { cout<<"A University: "<
  • 57. 测试程序: void showSchool(School &school) { school.show(); } main() { ElementarySchool es("changsha hunan","heishidu ElementarySchool"); showSchool(es); HighSchool hs(3000,"zhoukou High School"); showSchool(hs); University us(12,"Hunan University"); showSchool(us); return 0; }
  • 58. 总结: 利用多态性进行软件的渐增式开发,一般只需要在基类中定义接口(公共的虚函数),根据基类的接口编写相关类的对象的处理程序,而不用关心每个派生类的具体实现。实现了“接口”与“实现”分离,从而实现了“一个接口,多种方法”。
  • 59. 多态性经常需要使用基类指针操作不同的对象。通常的方法是用链表或数组组织所有对象,然后用基类指针去遍历它们。 【例】用指针数组管理不同类型的对象。
  • 60. main() { School *sch[9]; sch[0]=new ElementarySchool("hunan","heishidu ") ; sch[1]=new ElementarySchool("zhoukou ","shiyizhong") ; sch[2]=new ElementarySchool("wuhan","shizhizhong") ; sch[3]=new HighSchool(3000,"zhoukougaozhong"); sch[4]=new HighSchool(6000,"zhoukou sangao"); sch[5]=new HighSchool(5000,"zhoukou ergao"); sch[6]=new University(12,"wuhan university"); sch[7]=new University(16,"changsha university"); sch[8]=new University(15,"zhoukou college"); for(int i=0;i<9;i++) sch[i]->show(); return 0; }
  • 61. 第10次课作业: 课后作业6.11、6.12、6.13 思考实验6:2,3,4,5
  • 62. 第11次课作业编写一个简单的工资管理系统。具体要求:设计三个类Employee类,Manager类和HourlyWorker类。Manager类和HourlyWorker类是Employee类的子类。Emloyee类记录职工的姓名和编号,Manager类增加了经理的工资属性;Hwourlyworker记录钟点工每小时的工资数和一个月的工作小时数。每一个类中必须包含构造函数,析构函数,修改和获取所有数据成员的函数,计算职工工资,显示职工姓名和编号的虚函数。主函数中设计一个数组存放所有职工对象。 提示用户选择:[1] 增加一个职工;[2] 显示数组中所有的职工以及他们的工资,[3] 退出
  • 63. 谢 谢!