• 1. 《C++面向对象程序设计》教学内容 第1章 C++概述 第2章 类和对象 第3章 面向对象程序设计概述 第4章 进一步学习类和对象 第5章 堆与复制构造函数 第6章 继承性:派生类 第7章 运算符重载 第8章 虚函数和多态性 第9章 模板 第10章 类库和C++的标准模板库STL 第11章 输入输出流 第12章 异常处理
  • 2. 第8章 多态性与虚函数8.1 多态性概述 8.2 静态联编和动态联编 8.3 虚函数 8.4 纯虚函数和抽象类 8.5 应用举例
  • 3. 面向对象程序设计的特征抽象性(Abstraction) 封装性(Encapsulation) 继承性(Inheritance) 多态性(Polymorphism)
  • 4. 8.1 多态性概述polymorphism,“many forms”:即多种形态 在自然语言中,多态性即是“一词多义”; 更准确地说,多态性是指相同的动词作用到不同类型的对象上, 例如: 驾驶摩托车 驾驶汽车 驾驶飞机 驾驶轮船 驾驶宇宙飞船
  • 5. 什么是多态性?(OOP)当不同对象接受到相同的消息产生不同的动作,这种性质称为多态性。 通俗地说,多态性是指用一个名字定义不同的函数,这些函数执行不同但又类似的操作, 即用同样的接口访问功能不同的函数,从而实现“一个接口,多种方法”。
  • 6. 多态性的例子在C语言中,由于不支持多态,求绝对值的动作要求三个不同的函数名字: abs(), labs(),fabs() 分别用来求整数,长整数、浮点数的绝对值。 在C++语言中,由于支持多态,求绝对值的动作可以只用一个函数名: abs()
  • 7. 面向对象程序设计的精华通过一个简单的接口对不同的实现进行概念上的简化。 class walkman{ virtual void on( )=0; virtual void off( )=0; virtual void play( )=0; virtual void record( )=0; virtual void stop( )=0; }; Cassette player, CD player, MD player, MP3 player;提供给用户相同的接口
  • 8. 应用多态性的好处多态应用于OOP的目的是允许用一个名字来指定动作的一般类(即逻辑上相似的动作)。 从而带来以下的好处: 提高了处理问题的抽象级别; 降低了程序设计时的复杂性; (程序员只需记住一个接口,而不是好几个。)
  • 9. C++实现的多态性编译时多态性: 函数重载 运算符重载 模板 运行时多态性: 借助虚函数来获得在C++中,多态性的实现与联编(Binding)这一概念有关。
  • 10. 8.2 静态联编和动态联编什么叫联编(Binding)? 一个源程序需要经过编译、连接,才能成为可执行代码。 上述过程中需要将一个函数调用链接上相应的函数代码,这一过程称为联编。 联编的目的是要建立函数调用与函数体之间的联系, 即将一个函数调用连接到一函数的入口地址。
  • 11. C++支持两种联编静态联编:在程序被编译时进行联编;(早联编) 程序执行快,但灵活性较小。 动态联编:在程序运行时联编。(晚联编,滞后联编) 灵活性高,程序执行慢。 动态联编是C++实现运行时多态性的关键因素。
  • 12. 8.3 虚函数virtual float area( ){ return -1;} virtual 关键字的作用:指示C++编译器对该函数的调用进行动态联编。
  • 13. #include class shape{ public: float area( ){ return -1;} }; class circle:public shape{ float radius; public: void circle(float r){ radius = r; } float area( ){ return 3.14159*radius*radius; } };为什么需要虚函数
  • 14. void main() { shape obj, *ptr; circle c(3.6); ptr=&obj; cout<area( )<area( )<
  • 15. 输出的结果为: -1 -1 为什么会是这样? 原因在于这里只是用了静态联编。 前后两次ptr->area( )调用都链接到下面的函数实现: float area( ){ return -1;}输出的结果分析
  • 16. #include class shape{ public: virtual float area( ){ return -1;} }; class circle:public shape{ float radius; public: void circle(float r){ radius = r; } float area( ){ return 3.14159*radius*radius; } };虚函数被关键字virtual说明的函数称为虚函数
  • 17. void main() { shape obj, *ptr; circle c(3.6); ptr=&obj; cout<area( )<area( )<
  • 18. 虚函数的定义冠以关键字virtual的成员函数称为虚拟函数,简称虚函数。 说明虚函数的方法如下: virtual <类型说明符> <函数名> ( <参数表> ) 虚函数是成员函数,而且是非static的成员函数。 包含虚函数的类被称为多态类。
  • 19. class shape{ public: virtual float area( ) { }; }; class triangle:public shape{ float H,W; public: triangle(float h,float w){H=h;W=w;} float area( ) {return H*W*0.5;} };在派生类中,虚函数被重新定义以实现不同的操作。 这种方式称为函数超越(overriding),又称为函数覆盖。虚函数的使用分析
  • 20. class rectangle:public shape{ float H,W; public: rectangle(float h,float w){ H=h; W=w; } float area( ){return H*W;} };
  • 21. void main( ) { shape *s; triangle tri(3,4); rectangle rect(3,4); s=&tri; cout<<“The area of triangle: ”<area()<area()<
  • 22. 虚函数是动态联编的基础virtual 关键字的作用:指示C++编译器对该函数的调用进行动态联编。 尽管可以用对象名和点算符的方式调用虚函数,即向对象发送消息: tri.area( ) 或者 rect.area( ) 这时是静态联编方式。 只有当访问虚函数是通过基类指针s时才可获得运行时的多态性。
  • 23. 虚函数与重载函数的比较1.从形式上说,重载函数要求函数有相同的函数名称,并有不同的参数序列; 2.重载函数可以是成员函数和非成员函数;而虚函数要求这三项(函数名称、返回值和参数序列)完全相同,即具有完全相同的函数原型。 而虚函数只能是成员函数。
  • 24. 虚函数与重载函数的比较(续)3.对重载函数的调用是以所传递参数序列的差别(参数个数或类型的不同)作为调用不同函数的依据;虚拟函数是根据对象的不同去调用不同类的虚拟函数。虚函数在运行时表现出多态功能,正是C++的精髓之一;而重载函数不具备这一功能。
  • 25. 虚函数小结 如果某类中的一个成员函数被说明为虚函数,这就意味着该成员函数在派生类中可能有不同的实现。 为了实现运行时的多态性,调用虚函数应该通过第一次定义该虚函数的基类对象指针 只有通过指针或引用标识对象来操作虚函数时,才对虚函数采取动态联编方式。 如果采用一般类型的标识对象来操作虚函数,则将采用静态联编方式调用虚函数。
  • 26. 虚函数小结(续)构造函数不能是虚拟的,但析构函数可以是虚拟的。 设计C++程序时,一般在基类中定义处理某一问题的接口和数据元素,而在派生类中定义具体的处理方法。 通常都将基类中处理问题的接口设计成虚函数,然后利用基类对象指针调用虚函数,从而达到单一接口,多种功能的目的。
  • 27. 8.4 纯虚函数和抽象类 纯虚函数是指在基类中声明但是没有定义的虚函数,而且设置函数值等于零。 virtual type func_name(parameter list) =0; 通过将虚函数声明为纯虚函数可以强制在派生类中重新定义虚函数。 如果没有在派生类中重新定义,编译器将会报告错误。
  • 28. class shape{ public: virtual float area( )=0; //A PURE VIRTUAL FUNCTION }; class triangle:public shape{ float H,W; public: triangle(float h,float w){H=h;W=w;} float area( ){return H*W*0.5;} };纯虚函数的例子纯虚函数
  • 29. 抽象类抽象类:包含有纯虚函数的类称为抽象类。 不能说明抽象类的对象,但能说明指向抽象类的指针,一个抽象类只能作为基类来派生其他的类。 抽象类的指针用于指向该抽象类的派生类的对象。
  • 30. void main( ) { shape *s; triangle tri(3,4); rectangle rect(3,4); s=&tri; cout<<“The area of triangle: ”<area()<area()<
  • 31. 8.5 应用举例人员管理系统: 有三类人员:经理、技术人员、推销人员,还有销售经理。 月薪:经理:8000元/月;技术人员:100元/小时;推销人员:4%提成;销售经理:5000元月+5%提成。
  • 32. 问题分析在前面一章中,这个程序有两点不足: ①基类的成员函数pay( )和displayStatus( )的函数体均为空,在实现部分仍要写出函数体,显得冗余。 ②在main()函数中,建立了四个不同类的对象,对它们进行了类似的操作,但是却重复写了四遍类似的语句,程序不够简洁。
  • 33. (本页无文本内容)
  • 34. class employee { protected: char *name; //姓名 int individualEmpNo; //个人编号 int grade; //级别 float accumPay; //月薪总额 static int employeeNo; //本公司职员 编号目前最大值 public: employee(); //构造函数 ~employee(); //析构函数 virtual void pay( )=0; //计算月薪函数 virtual void promote(int); //升级函数 virtual void displayStatus( )=0; //显示人员信息 };
  • 35. class technician : public employee //技术人员类 { private: float hourlyRate; //每小时酬金 int workHours; //当月工作时数 public: technician(); //构造函数 void promote(int); void pay(); //计算月薪函数 void displayStatus(); //显示人员信息 };
  • 36. class salesman:virtual public employee //兼职推销员类 { protected: float CommRate; //按销售额提取酬金的百分比 float sales; //当月销售额 public: salesman(); //构造函数 void promote(int); void pay(); //计算月薪函数 void displayStatus(); //显示人员信息 };
  • 37. class manager:virtual public employee //经理类 { protected: float monthlyPay; //固定月薪数 public: manager(); //构造函数 void promote(int); void pay(); //计算月薪函数 void displayStatus(); //显示人员信息 };
  • 38. class salesmanager : public manager,public salesman //销售经理类 { public: salesmanager (); //构造函数 void promote(int); void pay(); //计算月薪函数 void displayStatus(); //显示人员信息 };
  • 39. employee::employee() { char namestr[50]; //输人雇员姓名时首先临时存放在namestr中 cout<<"请输入下一个雇员的姓名:"; cin>>namestr; name=new char[strlen(namestr)+1]; //动态申请用于存放姓名的内存空间 strcpy(name, namestr); //将临时存放的姓名复制到 name individualEmpNo= employeeNo++; //新输人的员工,其编号为目前最大编号加1 grade=1; //级别初值为1 accumPay=0.0; //月薪总额初值为0 }
  • 40. employee :: ~employee( ) { delete name ; //在析构函数中删除为存放姓名动态分配的内存空间 } /* void employee :: pay() //计算月薪,空函数 { } */ void employee ::promote(int increment) { grade += increment ; //升级,提升的级数由 increment指定 } /* void employee :: displayStatus( ) //显示人员信息,空函数 { } */
  • 41. technician :: technician() { hourlyRate=100; //每小时酬金100元 } void technician::pay() { cout<<"请输入"<>workHours; accumPay=hourlyRate* workHours; //计算月薪,按小时计酬 cout<<"兼职技术人员"<
  • 42. salesman::salesman() { CommRate=0.04; //销售提成比例4% } void salesman::pay() { cout<<"请输入"<>sales; accumPay=sales*CommRate; //月薪=销售提成 cout<<"推销员"<
  • 43. manager::manager() { monthlyPay=8000; //固定月薪8000元 } void manager :: pay() { accumPay=monthlyPay; //月薪总额即固定月薪数 cout<<"经理"<
  • 44. salesmanager ::salesmanager() { monthlyPay=5000; CommRate= 0.005; } void salesmanager ::pay() { cout<<"请输入"<>sales; accumPay=monthlyPay+CommRate*sales; //月薪=固定月薪十销售提成 cout<<"销售经理"<
  • 45. void technician::promote(int) { employee::promote(2); } void salesman::promote(int) { employee::promote(1); } void manager::promote(int) { employee::promote(3); } void salesmanager::promote(int) { employee::promote(2); }
  • 46. int main() { manager m1; technician t1; salesmanager sm1; salesman s1; employee *emp[4]={&m1,&t1,&sm1,&s1}; //用指针数组存放各个对象的地址 int i; for (i=0; i<4;i++) { emp[i]->promote(); //显示提升信息 emp[i]-> pay(); //显示月薪 emp[i]-> displayStatus(); //显示人员信息 } return 0; }