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

Slides:



Advertisements
Similar presentations
第三讲 面向对象(上).
Advertisements

项目7 面向对象高级.
移动应用软件开发技术 第二讲:C++编程基础
四資二甲 第三週作業 物件導向程式設計.
面向对象的程序设计(一).
第4章 数组 数组是由一定数目的同类元素顺序排列而成的结构类型数据 一个数组在内存占有一片连续的存储区域 数组名是存储空间的首地址
類別與物件 Class & Object.
第八章 类和对象.
走向C++之路 WindyWinter WindyWinter感谢诸位前来捧场。
内容提要 对象的生命周期 构造函数 析构函数 拷贝构造函数. 常宝宝 北京大学计算机科学与技术系
4.1 概述 4.2 类与对象的实现 4.3 对象的初始化和析构 4.4 类的包含 4.5 类模板
Derived Class 前言 衍生類別的定義 單一繼承 public, protected, 和 privated 基底類別
刘胥影 东南大学计算机学院 面向对象程序设计1 2011~2012第3学期 刘胥影 东南大学计算机学院.
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
第11章 运算符重载 什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符 运算符重载实例.
西南科技大学网络教育系列课程 高级语程序设计(Java) 第五章 继承、接口与范型.
授课老师:龚涛 信息科学与技术学院 2018年3月 教材: 《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
Object-Oriented Programming in C++ 第一章 C++的初步知识
程序设计期末复习 黎金宁
第三章 C++中的C 面向对象程序设计(C++).
第12章 從C到C++語言 12-1 C++語言的基礎 12-2 C++語言的輸出與輸入 12-3 C++語言的動態記憶體配置
2 C++ 的基本語法和使用環境 親自撰寫和執行程式是學好程式語言的不二法門。本章藉由兩個簡單的程式,介紹C++ 程式的基本結構和開發環境,讓初學者能逐漸建立使用C++ 的信心。
多态性和虚函数 封装性是基础,继承性是关键,多态性是补充 多态性是指发出同样的消息被不同类型的对象接收时导致完全不同的行为:
C#面向对象程序设计 $7 继承和多态性.
并发机制 结果应该为: 线程 1: 1 线程 1: 2 线程 1: 3 线程 1: 4 线程 1: 5 线程 2: 6 线程 2: 7
第四章 小技巧.
类类型 C++支持的内置类型和操作,如 int i=10; i=i%6; i=i+4;
软件学院 张 慧 清华大学软件学院.
C/C++/Java 哪些值不是头等程序对象
$10 可空类型.
第七章 操作符重载 胡昊 南京大学计算机系软件所.
第16章 虛擬與多形 16-1 虛擬函數 16-2 純虛擬函數與抽象類別 16-3 多形 16-4 虛擬繼承與虛擬解構子.
10 多載函數 10.1 多載概論 多載一般函數 多載成員函數 10-3
C++大学基础教程 第11章 多态性 北京科技大学 信息基础科学系 2019/4/8 北京科技大学.
第五章 递归与广义表 递归的概念 递归过程与递归工作栈 递归与回溯 广义表.
潘爱民 C++ Overview 潘爱民
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
Oop8 function函式.
第11章 從C到C++語言 11-1 C++語言的基礎 11-2 C++語言的資料型態與運算子 11-3 C++語言的輸出與輸入
第三章 数据抽象.
字符串 (String) 字符串是 n (  0 ) 个字符的有限序列, 记作 S = “c1c2c3…cn” 其中,S 是串名字
C++语言程序设计教程 第2章 数据类型与表达式 第2章 数据类型与表达式 制作人:杨进才 沈显君.
C++大学基础教程 第10章 运算符重载 北京科技大学 2019/5/7 北京科技大学.
第二章 Java语法基础.
C++语言程序设计 C++语言程序设计 第十章 多态 第十一组 C++语言程序设计.
C++程序设计 吉林大学计算机科学与技术(软件)学院.
第二章 类型、对象、运算符和表达式.
C++程序设计基础 主讲人:谢昕 华东交通大学信息工程学院 第十~十二讲 多态性和虚函数 2005年春季学期.
Review 1~3.
第九章 物件導向-進階.
C/C++基礎程式設計班 C++: 物件的使用、參考、重載函式 講師:林業峻 CSIE, NTU 3/28, 2015.
第 9 章 建構函式與解構函式.
授课老师:龚涛 信息科学与技术学院 2016年3月 教材:《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
挑戰C++程式語言 ──第9章 函數.
#include <iostream.h>
第二章 Java基本语法 讲师:复凡.
第 4 章 类的高级部分 陈哲 副教授 南京航空航天大学 计算机科学与技术学院.
C++语言程序设计 C++语言程序设计 第十章 多态 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
本章主題 C++的程式結構 資料型態與宣告 算術運算 簡易的輸入輸出指令 程式編譯(Compile)的過程與原理.
《数据结构与算法设计》第一部分 面向对象的C++程序设计基础.
JAVA 程式設計與資料結構 第三章 物件的設計.
C++语言程序设计 C++语言程序设计 第十一章 异常处理 C++语言程序设计.
本节内容 在堆中创建对象 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
第9章 C++程序设计初步 9.1 C++的特点 9.2 最简单的C++程序 9.3 C++的输入输出 9.4 函数的重载
C++程序语言设计 Chapter 14: Templates.
第六章 复合数据类型 指针的声明与使用 数组的声明与使用 指针与数组的相互引用 字符串及相关库函数 new与delete
第十二章 C与C C转入C++时不需改变的内容 12.2 C转入C++的一些与类无关的 新特性
Presentation transcript:

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

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

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

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

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

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

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

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

例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; }

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

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

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

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

例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; } };

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

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

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

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

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

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

进一步探讨虚函数与实函数的区别 假设基类和派生类都只有一个公有的数据成员,其中类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个字节。

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

例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; }

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 ;}

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

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后面跟的是一个表达式,在这里表达式中调用的是类的构造函数,构造函数无返回说明,而不是无返回值,实际上编译器为表达式中的构造函数创立了一个临时对象,临时对象生命期就在该表达式中,返回值就是该临时对象。

例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); };

例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;

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

例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 << "C3 + 6.5 = " << c3.Real() << "+j" << c3.Imag() << endl; return 0; }

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

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

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); }

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

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

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

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

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

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

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

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