Presentation is loading. Please wait.

Presentation is loading. Please wait.

西安交通大学 计算机教学实验中心 http://ctec.xjtu.edu.cn 大学C++程序设计教程 西安交通大学 计算机教学实验中心 http://ctec.xjtu.edu.cn.

Similar presentations


Presentation on theme: "西安交通大学 计算机教学实验中心 http://ctec.xjtu.edu.cn 大学C++程序设计教程 西安交通大学 计算机教学实验中心 http://ctec.xjtu.edu.cn."— Presentation transcript:

1 西安交通大学 计算机教学实验中心 http://ctec.xjtu.edu.cn
大学C++程序设计教程 西安交通大学 计算机教学实验中心

2 第11章 多态性 本章目标 1.理解多态性的基本思想 2.掌握运算符重载的方法 3.学习使用虚函数实现动态多态性

3 授 课 内 容 11.1 多态性概述 11.2 派生类对象替换基类对象 11.3 虚函数 11.4 抽象类 11.5 运算符重载
程序设计举例 实例编程

4 11.1 多态性概述 编译时的多态性 在C++中有两种多态性
多态性是面向对象程序设计的关键技术之一。若程序设计语言不支持多态性,不能称为面向对象的语言。利用多态性技术,可以调用同一个函数名的函数,实现完全不同的功能。 在C++中有两种多态性 编译时的多态性 运行时的多态性 运行时的多态性是指在程序执行前,无法根据函数名和参数来确定该调用哪一个函数,必须在程序执行过程中,根据执行的具体情况来动态地确定。它是通过类继承关系和虚函数来实现的。目的也是建立一种通用的程序。通用性是程序追求的主要目标之一。 通过函数的重载和运算符的重载来实现的。

5 编译时多态性——函数重载(兔子逃生) 伪代码描述: class 兔子 { public: …… void 逃生(老鹰a){“兔子蹬鹰”;}
void 逃生(狼 b){“动如脱兔”;} };

6 基类和派生类的同名成员 class 宠物 {public: void speak() { cout<< "zzz"; }; …
{public: void speak() { cout<< "miao!miao!"; } class 狗 : public宠物 {public: void speak() { cout<< "wang!wang!"; }

7 例: cat or dog???(续) void main() { …… 宠物 *p; //p为宠物类指针 猫 cat1; //定义猫类对象
{ …… 宠物 *p; //p为宠物类指针 猫 cat1; //定义猫类对象 狗 dog1; //定义狗类对象 …… 根据用户输入将猫或狗对象地址赋给p指针, 例如用户输入1,则执行p = &cat1; 用户输入2,则执行p = &dog1; …… p->speak(); …… }

8 11.2 派生类对象替换基类对象 原则 形式 凡是基类对象出现的场合都可以用公有派生类对象取代 (1)派生类对象给基类对象赋值
(2)派生类对象可以初始化基类对象的引用 (3)可以令基类对象的指针指向派生类对象,即将派生类对象的地址传递给基类指针

9 例11-1 派生类对象替换基类对象 #include <iostream.h> class Pet //基类 {public:
void Speak() { cout<<"How does a pet speak ?"<<endl; } }; class Cat : public Pet //派生类 void Speak() { cout<<"miao!miao!"<<endl; }

10 例11-1 派生类对象替换基类对象 class Dog : public Pet //派生类 { public:
void Speak() { cout<<"wang!wang!"<<endl; } };

11 int main() { Pet obj,*p1;//基类对象指针p1, 基类对象obj Dog dog1; Cat cat1; obj = dog1;//用Dog类对象给Pet类对象赋值 obj.Speak();

12 11.3 虚函数 定义: 语法: 用途: 在某基类中声明为virtual并在一个或多个派生类中被重新定义的成员函数
{ 函数体 } 用途: 实现多态性,通过指向派生类的基类指针,访问派生类中同名覆盖成员函数,

13 11.3 虚函数 虚函数是一个类的成员函数,定义格式如下: virtual 返回类型 函数名(参数表);
关键字virtual指明该成员函数为虚函数。virtual仅用于类定义中,如虚函数在类外定义,不可加virtual。 当某一个类的一个类成员函数被定义为虚函数,则由该类派生出来的所有派生类中,该函数始终保持虚函数的特征。 当在派生类中重新定义虚函数(overriding a virtual function,亦译作超载或覆盖)时,不必加关键字virtual。但重新定义时不仅要同名,而且它的参数表和返回类型全部与基类中的虚函数一样,否则联编时出错。 虚函数与在11.2节中介绍的派生类的第二步——改造类成员,同名覆盖(override)有关:如未加关键字virtual,则是普通的派生类中的新成员函数覆盖基类同名成员函数(当然参数表必须一样,否则是重载),可称为同名覆盖函数,它不能实现运行时的多态性。

14 例11-2 虚函数实现多态性 #include <iostream.h> class Pet //基类 {public:
例11-2 虚函数实现多态性 #include <iostream.h> class Pet //基类 {public: virtual void Speak() { cout<<"How does a pet speak ?"<<endl; } };

15 class Cat : public Pet //派生类
virtual void Speak() { cout<<"miao!miao!"<<endl; } };

16 虚函数的使用限制 应通过指针或引用调用虚函数,而不要以对象名调用虚函数说明语句
在派生类中重定义的基类虚函数仍为虚函数,同时可以省略virtual关键字 不能定义虚构造函数,可以定义虚析构函数

17 11.4 抽象类 纯虚函数(pure virtual function)是指被标明为不具体实现的虚拟成员函数。它用于这样的情况:定义一个基类时,会遇到无法定义基类中虚函数的具体实现,其实现依赖于不同的派生类。定义纯虚函数的一般格式为: virtual 返回类型 函数名(参数表)=0; 含有纯虚函数的基类是不能用来定义对象的。纯虚函数没有实现部分,不能产生对象,所以含有纯虚函数的类是抽象类。 定义纯虚函数必须注意: 1 定义纯虚函数时,不能定义虚函数的实现部分。即使是函数体为空也不可以,函数体为空就可以执行,只是什么也不做就返回。而纯虚函数不能调用。 2 “=0”表明程序员将不定义该函数,函数声明是为派生类保留一个位置。“=0”本质上是将指向函数体的指针定为NULL。 3 在派生类中必须有重新定义的纯虚函数的函数体,这样的派生类才能用来定义对象。

18 抽象类的使用要求 抽象类不能实例化 抽象类只作为基类被继承 可以定义指向抽象类的指针或引用

19 动态联编 联编是指计算机程序自身彼此关联的过程,是把一个标识符名和一个存储地址联系在一起的过程,也就是把一条消息和一个对象的操作相结合的过程 。 如果使用基类指针或引用指明派生类对象并使用该指针调用虚函数(成员选择符用箭头号“->”),则程序动态地(运行时)选择该派生类的虚函数,称为动态联编。 动态联编(dynamic binding)亦称滞后联编(late binding),对应于静态联编(static binding)。 如果使用对象名和点成员选择运算符“.”引用特定的一个对象来调用虚函数,则被调用的虚函数是在编译时确定的(称为静态联编)

20 虚函数表 多态是由复杂的数据结构实现的,图中列出了基类和各派生类的虚函数表,这些表是由指向函数的指针组成的。
C++编译器编译含有一个或几个虚函数的类及其派生类 时,对该类建立虚函数表(Virtual function table,vtable)。 虚函数表使执行程序正确选择每次执行时应使用的虚函数。 多态是由复杂的数据结构实现的,图中列出了基类和各派生类的虚函数表,这些表是由指向函数的指针组成的。 还有第二层指针,在实例化带虚函数的类(创建对象)时,编译器在对象前加上一个指向该类的虚函数表的指针。 第三层指针是链表结点类对象中指向抽象基类Object的指针(这也可以是引用,但本例是指针)。 虚函数的调用是这样进行的,考虑虚函数Compare(),则看含“cat”的结点。由该结点的info指针找到含“cat”的无名对象,再由对象前的指针找到StringObject虚函数表,移动4个字节(一个指针占4个字节)找到比较函数指针,进入串比较函数。

21 进一步探讨虚函数与实函数的区别 假设基类和派生类都只有一个公有的数据成员,其中类A有vfunc1和vfunc2两个虚函数和func1和func2两个实函数。类A公有派生类B,类B改写vfunc1和func1函数,它又作为类C的基类,公有派生类C。类C也改写vfunc1和func1函数。图给出3个类建立的vptr和vtable之间的关系图解以及实函数与虚函数的区别。 首先给vptr分配地址,它所占字节数决定对象中最长数据成员的长度。因为3个类的数据成员都是整型,所以VC为vptr分配4个字节。如果有double型的数据,则要分配8个字节。

22 实函数和虚函数的图解示意图

23 例11-3 抽象宠物类的实现 int main() { Pet *p1; //基类对象指针p1
例11-3 抽象宠物类的实现 int main() { Pet *p1; //基类对象指针p1 p1 = new Cat("MiKey",1,"Blue"); //动态生成Cat类对象 p1->GetInfo(); p1->Speak(); delete p1; p1 = new Dog("BenBen",2,"Black");//动态生成Dog类对象 delete p1; return 0; }

24 11.5 运算符重载 重载运算符主要用于对类的对象的操作,在本例中重载了运算符“+”、“=”、“+=”和“*”、“/”,以及求模(绝对值)的函数abs(),可以进行复数运算。首先来看“+”的重载。 Complex Complex::operator+(Complex c) { Complex temp; //显式说明局部对象 temp.m_fReal = m_fReal+c.m_fReal; temp.m_fImag = m_fImag+c.m_fImag; //注意:直接写对象c的私有成员,不用调c的公有函数处理 return temp ;}

25 在做 c=c2+c3时,C++编译器把表达式c2+c3解释为:
c2.operator+(c3) ; 这样一个函数调用过程,函数c2.operator创建一个局部的Complex对象Temp,把出现在表达式中的两个Complex类对象c2和c3的实部之和及虚部之和暂存其内,然后把这个局部对象返回,赋给Complex类对象c(注意这里调用了拷贝构造函数生成一个无名临时对象过渡)。

26 Real Image c3.Real c3.Image 显式说明临时对象的“+”运算符执行过程
局部对象Temp 当前对象c2 对象c3 Temp.Real=Real+ c2.Real; Temp.Image=Image+ c3.Image; c=return(Temp); Real Image c3.Real c3.Image = + 显式说明临时对象的“+”运算符执行过程 可以用隐式的临时对象替换显式的对象Temp,如例中重载的Operator+(double),它执行复数与实数的加法。 Complex Complex::operator+(double d){ return Complex(Real+d , Image);}//隐式说明局部对象 在return后面跟的是一个表达式,在这里表达式中调用的是类的构造函数,构造函数无返回说明,而不是无返回值,实际上编译器为表达式中的构造函数创立了一个临时对象,临时对象生命期就在该表达式中,返回值就是该临时对象。

27 例11-4 复数加法运算 class Complex { double real, imag; public:
例11-4 复数加法运算 #include <iostream.h> class Complex { double real, imag; public: Complex(double r = 0, double i = 0): real(r), imag(i){} double Real(){return real;} double Imag(){return imag;} Complex operator +(Complex&); Complex operator +(double); Complex operator =(Complex); };

28 例11-4 复数加法运算(续1) // 重载运算符 + Complex Complex::operator + (Complex &c) { real = real+c.real; imag = imag+c.imag; return *this; } // 重载运算符+ Complex Complex::operator + (double d) { real = real+d;

29 例11-4 复数加法运算(续2) // 重载运算符= Complex Complex::operator = (Complex c) { real = c.real; imag = c.imag; return *this; }

30 例11-4 复数加法运算(续3) int main() { Complex c1(3,4),c2(5,6),c3;
例11-4 复数加法运算(续3) int main() { Complex c1(3,4),c2(5,6),c3; cout << "C1 = " << c1.Real() << "+j" << c1.Imag() << endl; cout << "C2 = " << c2.Real() << "+j" << c2.Imag() << endl; c3 = c1+c2; cout << "C3 = " << c3.Real() << "+j" << c3.Imag() << endl; c3 = c3+6.5; cout << "C = " << c3.Real() << "+j" << c3.Imag() << endl; return 0; }

31 11.5 运算符重载  在缺省的情况下,C++ 编译器为每个类生成一个缺省的赋值操作,用于同类的两个对象之间的相互赋值,缺省的语义是类成员逐个相互赋值。对复数类 complex 如果没有重载赋值运算符 =,复数的赋值语义是: Complex &Complex::operator = (Complex& c){ Real = c.Real; Imag = c.Imag; return *this; }

32 这种缺省的赋值操作格式对所有类是固定的,这种缺省的格式对复数是合适的,但对其他类缺省的赋值可能产生问题,那时需重载。对所有的类对象,赋值运算符“ =”即缺省的按成员拷贝赋值操作符(Copy Assignment Operator),同类对象之间可以用“=”直接拷贝。因为缺省的赋值操作返回一个复数的引用,所以它可以进行连续赋值如: a=b=c=d ;

33 this 指针 实际上编译器是这样实现this指针的 1.改变类成员函数的定义,用附加参数this指针来定义每个成员函数。如:
void Person:: Register(char *name, int age, char sex) { strcpy(m_strName, name); this->Age = age; this->Sex = (sex == 'm'?0:1); }

34 this 指针 在上例中,this指针不必写成显式的,但是有时必须写成显式的,如在以后要学的某些类型的链表管理中,在需要返回当前调用的对象时(对复数类的赋值号重载中 ),等等。但必须指出静态成员函数没有this指针。因为普通成员函数虽然在物理上只有一份拷贝,但在逻辑上都认为一个对象有一份拷贝,所以有this指针,而静态成员函数在逻辑上也只有一份拷贝,不属于具体的对象,当然没有this指针。

35 11.5 运算符重载小结: 1. 运算符重载函数的函数名必须为关键字operator加一个合法的运算符。在调用该函数时,将右操作数作为函数的实参。 2.  当用类的成员函数实现运算符的重载时,运算符重载函数的参数(当为双目运算符时)为一个或(当为单目运算符时)没有。运算符的左操作数一定是对象,因为重载的运算符是该对象的成员函数,而右操作数是该函数的参数,其类型并无严格限制。C++不允许重载三目运算符。

36 3.  单目运算符“++”和“--”存在前置与后置问题。前置“++”格式为:
返回类型 类名::operator++(){……} 而后置“++”格式为: 返回类型 类名::operator++(int){……} 后置“++”中的参数int仅用作区分,并无实际意义,可以给一个变量名,也可以不给变量名。 4. C++中只有极少数的运算符不允许重载。

37 C++中不允许重载的运算符 运算符名称 禁止重载的理由 ? : . 成员操作符 :: 该操作符右操作数不是表达式 sizeof 运算符
三目条件运算符 C++中没有定义三目运算符的语法 . 成员操作符 为保证成员操作符对成员访问的安全性 :: 作用域操作符 该操作符右操作数不是表达式 sizeof 类型字长操作符 该操作符的操作数为类型名,不是表达式

38 注意 在本小节中学习了有关运算符重载的基础知识,必须指出的是这只是初步的,由于所学知识有限,重载中不少问题还不能解决。如: c=c+d;
语句,改为 c=d+c; 因为d不是complex的对象,C++编译器将无法找到合适的重载的“+”运算符对应的函数,最终给出出错信息。

39 调试技术:异常处理机制 被调用函数直接检测到异常条件并使用throw引发一个异常 在上层调用函数中使用try检测函数调用是否引发了异常
被检测到的各种异常由catch语句捕获并作相应处理

40 程序设计举例 例11.5 抽象宠物类的另一种用法 例11.6 从例10-4的Point、Circle类中抽象出基类Shape,研究抽象类和具体类的接口和实现。

41 上机练习题目 1.有一个基类名为“形状”、它有派生类“圆”、“正方形”和“长方形”。利用“多态性”的概念,以虚函数的形式完成计算“圆”、“正方形”和“长方形”的面积。 提示:初始值分别给出:圆的圆心和半径;正方形的中点和一个顶点;长方形的中点和两个顶点。 2.应用C++的多态性,编程求球体和圆柱体的体积和表面积。设球的半径为r,则求得体积为4πr3/3;表面积为 4πr2


Download ppt "西安交通大学 计算机教学实验中心 http://ctec.xjtu.edu.cn 大学C++程序设计教程 西安交通大学 计算机教学实验中心 http://ctec.xjtu.edu.cn."

Similar presentations


Ads by Google