第九章 多态性 丘志杰 电子科技大学 计算机学院 软件学院.

Slides:



Advertisements
Similar presentations
第9章 重载.
Advertisements

第10章 虚函数与多态性.
7.2 访问控制 —— 公有继承 公有继承练习 //Point.h #ifndef _POINT_H #define _POINT_H class Point { //基类Point类的定义 public: //公有函数成员 void initPoint(float x = 0, float.
内容提要 代码重用 类的继承 多态 抽象类 多重继承 虚拟继承. 常宝宝 北京大学计算机科学与技术系
预备知识——C++、类与对象 C++要素 类和对象 模版 类的继承.
第12章 组合与继承 欢迎辞 第14次见面!.
第14章 c++中的代码重用.
第11章 类的继承和派生 继承是面向对象程序设计方法的四个基本特征之一,是程序代码可重用性的具体体现。
C++中的声音处理 在传统Turbo C环境中,如果想用C语言控制电脑发声,可以用Sound函数。在VC6.6环境中如果想控制电脑发声则采用Beep函数。原型为: Beep(频率,持续时间) , 单位毫秒 暂停程序执行使用Sleep函数 Sleep(持续时间), 单位毫秒 引用这两个函数时,必须包含头文件
第6章 多态性与虚函数.
第10讲 Java面向对象编程基础(4) 教学目标 主要内容.
Using C++ The Weird Way Something about c++11 & OOP tricks
Object-Oriented Programming in C++ 第四章 运算符重载
Derived Class 前言 衍生類別的定義 單一繼承 public, protected, 和 privated 基底類別
EBNF 请用扩展的 BNF 描述 C语言里语句的结构; 请用扩展的 BNF 描述 C++语言里类声明的结构;
刘胥影 东南大学计算机学院 面向对象程序设计1 2011~2012第3学期 刘胥影 东南大学计算机学院.
第11章 运算符重载 什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符 运算符重载实例.
第七章 类与对象 丘志杰 电子科技大学 计算机学院 软件学院.
第3章 继承和派生.
西安交通大学 计算机教学实验中心 大学C++程序设计教程 西安交通大学 计算机教学实验中心
授课老师:龚涛 信息科学与技术学院 2018年3月 教材: 《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
第11讲 类的继承 1. 类的继承的概念 2. 类的单继承机制 3. 单继承中的构造函数和析构函数.
第12讲 多继承与虚基类 多继承 虚基类.
辅导课程六.
第一单元 初识C程序与C程序开发平台搭建 ---观其大略
第五讲 四则运算计算器(一) 精品教程《C#程序设计与应用(第2版)清华大学出版社 谭恒松 主编
第二章 Java语言基础.
第八章 继承与派生 丘志杰 电子科技大学 计算机学院 软件学院.
第十一讲 运算符重载 与类型转换.
用event class 从input的root文件中,由DmpDataBuffer::ReadObject读取数据的问题
第七章 操作符重载 胡昊 南京大学计算机系软件所.
10 多載函數 10.1 多載概論 多載一般函數 多載成員函數 10-3
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
C语言程序设计 主讲教师:陆幼利.
EBNF与操作语义 请用扩展的 BNF 描述 javascript语言里语句的结构;并用操作语义的方法描述对应的语义规则
简单介绍 用C++实现简单的模板数据结构 ArrayList(数组, 类似std::vector)
第11讲 类的继承 1. 类的继承的概念 2. 类的单继承机制 3. 单继承中的构造函数和析构函数.
$9 泛型基础.
C++复习3 ----类的继承与派生.
第14讲 运算符重载 运算符重载的概念、方法和规则 运算符重载作为类的成员函数 运算符重载作为类的友元函数 特殊运算符的重载 类型转换函数.
C#面向对象程序设计 $6 深入理解类.
Classes (1) Lecture 6.
第13讲 多态 友员函数 多态性与虚函数 纯虚函数和抽象类.
补课.
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
第4章 Excel电子表格制作软件 4.4 函数(一).
C++语言程序设计 C++语言程序设计 第十章 多态 第十一组 C++语言程序设计.
第九节 赋值运算符和赋值表达式.
本节内容 结构体 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
C++程序设计基础 主讲人:谢昕 华东交通大学信息工程学院 第十~十二讲 多态性和虚函数 2005年春季学期.
Chapter 18 使用GRASP的对象设计示例.
多层循环 Private Sub Command1_Click() Dim i As Integer, j As Integer
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第八章 继承 C++语言程序设计.
辅导课程十五.
授课老师:龚涛 信息科学与技术学院 2016年3月 教材:《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
第7章 模板 陈哲 副教授 南京航空航天大学 计算机科学与技术学院.
本节内容 C语言的汇编表示 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
C++语言程序设计 C++语言程序设计 第十一章 异常处理 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第八章 继承 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第一章 C++语言概述 第十一组 C++语言程序设计.
本节内容 动态链接库 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
本课程学习目标 培养编程技能 开启智慧之门.
C++语言程序设计 C++语言程序设计 第十章 多态 第十一组 C++语言程序设计.
使用Fragment 本讲大纲: 1、创建Fragment 2、在Activity中添加Fragment
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
本节内容 this指针 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
§2 自由代数 定义19.7:设X是集合,G是一个T-代数,为X到G的函数,若对每个T-代数A和X到A的函数,都存在唯一的G到A的同态映射,使得=,则称G(更严格的说是(G,))是生成集X上的自由T-代数。X中的元素称为生成元。 A变, 变 变, 也变 对给定的 和A,是唯一的.
Presentation transcript:

第九章 多态性 丘志杰 电子科技大学 计算机学院 软件学院

什么是多态性 多态是指同样的消息被不同类型的对象接收时导致完全不同的行为。 多态可以分为: 编译时的多态,如函数重载、运算符重载 运行时的多态,虚函数 2019/1/17

运算符重载 当在使用一种程序设计语言编写程序时,我们不仅要设计新的数据类型,同时还要为新类型设计运算。一般地,用户定义类型的运算都是用函数的方式实现的。而在一般情况下,基本类型的运算都是用运算符表达的,这很直观,语义也简单。 如果直接将运算符作用在用户定义类型之上,那么编译器将不能识别运算符的语义。因此,在这种情况下,我们需要一种特别的机制来重新定义作用在用户定义类型上的普通运算符的含义。这就是运算符重载的简单概念。 2019/1/17

在C编译器里早就存在简单的运算符重载的概念。考虑整型和浮点型两种加法运算: int a,b,c; c=a+b; mov eax,dword ptr [ebp-4] add eax,dword ptr [ebp-8] mov dword ptr [ebp-0Ch],eax float a,b,c; c=a+b; fld dword ptr [ebp-4] fadd dword ptr [ebp-8] fstp dword ptr [ebp-0Ch] 2019/1/17

什么是运算符重载 在原来预定义的运算符含义的基础上,再定义对于某个用户定义类型的对象进行操作的新的含义。这就是运算符重载。 运算符重载后,其优先级和结合性不变,所需的操作数也不能变。 2019/1/17

重载运算符 大多数系统预定义的运算符可以重载,但少数的C++运算符不能重载: :: 、#、 ?:、 .、.*、* 另外,不是运算符的符号,如“;”等也不能重载。C++还不允许重载不存在的运算符,如“$”、“**”等。 2019/1/17

例:复数的加操作 c3 = c1.add(c2);这种使用方式不太直观。 我们更希望是如下方式: c3=c1+c2; class Complex{ double re, im; public: Complex(double r=0.0, double i=0.0): re(r), im(i){ } Complex add(Complex c){ Complex t; t.re = re + c.re; t.im = im + c.im; return t; } }; void main( ) { Complex c1(1, 2), c2(3, 4); Complex c3 = c1.add(c2); } c3 = c1.add(c2);这种使用方式不太直观。 我们更希望是如下方式: c3=c1+c2; 2019/1/17

例:重载复数类的加运算符 class Complex{ …… public: Complex operator+(Complex c){ Complex t; t.re = re + c.re; t.im = im + c.im; return t; } }; void main( ) { Complex c1(1, 2), c2(3, 4); Complex c3 = c1 + c2; //相当于c3 = c1.operator+(c2); } operator+(…)称为运算符重载函数 c3 = c1 + c2称为operator+(…)函数的隐式调用 c3 = c1.operator+(c2)称为operator+(…)函数的显示调用 2019/1/17

例:计数器重载运算符 class counter{ unsigned int value; public: counter( ){value=0;} void operator++( ); void operator--( ); unsigned int operator( )( ); }; void counter:: operator++( ){if (value<1000) value++;} void counter:: operator--( ){if (value>0) value--;} unsigned int counter:: operator( )( ){return value;} 2019/1/17

for(int i=0;i<10;i++){ ++my_counter; void main( ) { counter my_counter; for(int i=0;i<10;i++){ ++my_counter; cout<<“my_counter=“<<my_counter( )<<endl; } --my_counter; 2019/1/17

运算符重载的语法形式 运算符是通过运算符重载函数来完成重载的。 运算符重载函数是成员函数时,语法形式为: type className::operator@(parameter list); type是返回类型 className是重载该运算符的类的类名 重载运算符时使用operator关键字, @表示被重载的运算符,”operator@”称为运算符函数 当运算符函数不属于某个类时,一般将此函数声明为类的友元函数,语法形式为: type operator@(parameter list); 2019/1/17

几点注意 当运算符被重载时,它是被绑定在一个特定的类型之上的。当此运算符不作用在特定类型上时,它将保持原有的含义。 当重载运算符时,不能创造新的运算符符号,例如不能用“**”来表示求幂运算符。 另外,我们应当尽可能保持重载运算符原有的语义。试想,如果在某个程序中用“+”表示减,“*”表示除,那么这个程序读起来将会非常别扭。 2019/1/17

用友元函数重载运算符 用成员函数重载运算符会碰到麻烦: class Complex{ …… public: Complex operator+(Complex c){ Complex t; t.re = re + c.re; t.im = im + c.im; return t; } }; void main( ) { Complex c1(1, 2); c1 = c1 + 27; //正确,相当于c1=c1.operator+(Complex(27)) c1 =27+c1;//错误,被解释为无意义的c1=27.operator+(c1) } 2019/1/17

可以用友元函数实现运算符重载,此时能够很容易地将27隐式转换为Complex类型 class Complex{ …… public: friend Complex operator+(Complex c1, Complex c2){ Complex t; t.re = c1.re + c2.re; t.im = c1.im + c2.im; return t; } }; 请思考,能不能同时存在以下两个函数: Complex operator+(Complex c){…} friend Complex operator+(Complex c1, Complex c2){…} void main( ) { Complex c1(1, 2); c1 = c1 + 27; //正确,相当于c1=operator+(c1,Complex(27)) c1 =27+c1;//正确,相当于c1=operator+(Complex(27), c1) } 2019/1/17

用友元或成员函数重载运算符的一般原则 没有强制要求程序员必须用那种方式来重载运算符。 如果运算符的操作需要修改类对象的状态,则应该把它实现为成员函数。 对于诸如=、+=、++等需要左值操作数的运算符最好重载为成员函数。 如果运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,那么应该实现为友元函数。 =、()、[]、->不能重载为友元函数。 2019/1/17

例子:用友元重载运算符 class counter{ int value; public: counter operator=(int num);//表示 对象=整数 friend counter operator+(counter obj,int num); //表示 对象+整数 friend counter operator+(int num,counter obj); //表示 整数+对象 }; counter counter::operator=(int num){ value=num; return *this; } counter operator+(counter obj,int num){ counter tmp; tmp.value=obj.value+num; return tmp; counter operator +(int num,counter obj){ counter tmp; tmp.value=obj.value+num; return tmp; 2019/1/17

counter obj2=obj1+5;//使用重载运算符函数operator+(counter,int) void main( ) { counter obj1; obj1=10;//使用重载的=运算符 counter obj2=obj1+5;//使用重载运算符函数operator+(counter,int) counter obj3=3+obj2; //使用重载运算符函数operator+(int, counter) } 2019/1/17

例子:用友元重载++运算符 class Point{ int x, y; public: Point(int a=0,int b=0):x(a),y(b){ } Point operator++( ){ x++; y++; return *this; } };//用成员函数重载 class Point{ int x, y; public: Point(int a=0,int b=0):x(a),y(b){ } friend Point operator++( Point obj); }; Point operator++(Point obj){ obj.x++; obj.y++; return obj; } //用友元函数和重载 void main( ) { Point point1(1,3); Point point2=++point1; } 2019/1/17

Point(int a=0,int b=0):x(a),y(b){ } class Point{ int x, y; public: Point(int a=0,int b=0):x(a),y(b){ } friend Point operator++( Point &obj); }; Point operator++(Point &obj){ obj.x++; obj.y++; return obj; } void main( ) { Point point1(1,3); Point point2=++point1; } //通过传递引用,可以改变对象的内部状态 2019/1/17

一元和二元运算符 一元运算符: 对任意一元运算符@,将其作为成员函数重载时的定义及调用方式为: type className::operator@(); className obj; @obj 或 obj@ 此时运算符重载函数operator@的参数表为空,因为所需的一个操作数通过this指针隐含传递。 2019/1/17

如果将一元运算符@作为友元函数重载时的定义及调用方式为: type operator@(one_parameter) className obj; @obj 或 obj@ 此时运算符重载函数operator@所需的一个操作数在参数表中。 2019/1/17

二元运算符 对任意二元运算符@,将其作为成员函数重载时的定义及调用方式为: type className::operator@(className param); className obj1,obj2; obj1@obj2; 此时运算符重载函数operator@所需的第一个操作数通过this指针隐含传递,第二个操作数通过参数提供。 2019/1/17

如果将二元运算符@作为友元函数重载时的定义及调用方式为: type operator@(param1,param2); className obj1,obj2; obj1 @ obj2; 此时运算符重载函数operator@所需的两个操作数均通过参数提供。 2019/1/17

重载++和-- 运算符++和--有两种使用方式:前缀和后缀。因此在重载这两个运算符时,应该考虑到这两种方式的差别。 二者的差别主要在于函数的参数不同。 为了方便起见,我们仅以++为例。 2019/1/17

这里的int类型参数叫做占位参数,用来区别前缀和后缀方式,一般在函数体里不会引用它。 对于前缀方式++obj 成员函数方式 type className::operator++(); 友元函数方式 type operator++(className&); 对于后缀方式obj++ type className::operator++( int ); type operator++(className&, int); 这里的int类型参数叫做占位参数,用来区别前缀和后缀方式,一般在函数体里不会引用它。 2019/1/17

Point(int a=0,int b=0):x(a),y(b){ } class Point{ int x, y; public: Point(int a=0,int b=0):x(a),y(b){ } friend Point operator++( Point &obj); friend Point operator++( Point &obj,int); }; Point operator++(Point &obj){ obj.x++; obj.y++; return obj; } Point operator++(Point &obj , int){ Point tmp=obj;//请注意该语句 return tmp; //请注意该语句 void main( ) { Point point1(3,4); Point point3=++point1; Point point2=point1++; } 2019/1/17

对于占位参数,缺省为0,但也可以设置非0值,例如: obj.operator++(3)//成员函数形式 operator++(obj,3)//友元函数形式 相当于:obj++=3或obj++(3) class Point{ int x, y; public: Point(int a=0,int b=0):x(a),y(b){ } friend Point operator++( Point &obj, int v); }; Point operator++(Point &obj,int v){ Point tmp=obj; obj.x+=v; obj.y+=v; return tmp; } 2019/1/17

重载运算符 [ ] [ ]不能用友元函数重载,只能用成员函数重载。 重载下标运算符[ ] operator[](参数表) obj[index] => obj.operator[](index) 2019/1/17

例子:整型数组类IntArray class IntArray{ int *p; int size; void expand(int offset); public: IntArray(int num){ size=(num>6)?num:6; p=new int[size]; } ~ IntArray( ){delete [ ]p;} int &operator[ ](int index); }; void IntArray::expand(int offset){ int *pi=new int[size+offset]; for(int i=0;i<size;i++) pi[i]=p[i]; delete [ ]p; p=pi; size+=offset; } int &IntArray:: operator[ ](int index){ if(index<size) return p[index]; else{ expand(index-size+1); return p[index]; 2019/1/17

for(int i=0;i<8;i++) array[i]=i; } void main( ) { IntArray array(5); for(int i=0;i<8;i++) array[i]=i; } 2019/1/17

重载赋值运算符 当用户自定义类要重载赋值运算符时,必须重载为成员函数,格式一般如下: X X::operator=(X &from) { //复制X的成员 } 如果用户没有在类中重载赋值运算符,那么编译器会生成一个缺省的赋值运算符,该缺省的赋值运算符的作用就是通过位拷贝的方式将源对象复制到目的对象。 2019/1/17

举例(一) class Complex{ double re, im; public: Complex(double r=0.0, double i=0.0): re(r), im(i){ } Complex(Complex &obj){ re=obj.re; im=obj.im; } Complex operator=(Complex &obj){ re=obj.re; im=obj.im; return *this; …… }; void main( ){ Complex c1(1, 2), c2(3, 4),c3; Complex c4=c1+c2;//调用的是拷贝构造函数 c3= c1 + c2;//调用的是重载的赋值运算符 2019/1/17

赋值运算符与拷贝构造函数的异同 相同点:它们的目的都是为了将一个对象的数据成员复制到另一个对象中。 不同点:拷贝构造函数是要创建一个新对象,而赋值运算符则是要改变一个已经存在的对象的值。 2019/1/17

举例(二) class A{ private: int len; char *buf; A::A(const A&obj){ public: A(int n); A(const A&obj); ~A( ); void copy(char *src); }; A::A( int n){ len=n; buf=new char[n]; } void A::copy(char *src){ strcpy(buf,src); A::~A( ){ delete [ ]buf; A::A(const A&obj){ len = obj.len; buf=new char[ len ]; strcpy( buf , obj.buf ); } void func(){ A obj1( 64 ), obj2(32); obj1.copy(“helloworld”); A obj3=obj1; obj2=obj3;//将会出错 void main(){ func( ); obj2中buf成员指向的32字节的内存区将会丢失 2019/1/17

改进办法:重载赋值运算符 class A{ …… public: A(int n); A operator=(const A&from){ if(this==&from) return *this;//避免自己给自己赋值 delete [ ]buf; len = from.len; buf=new char[ len ]; strcpy( buf , from.buf ); return *this; } }; 2019/1/17

重载输入输出运算符 在iostream.h中,有两个标准的类类型istream和ostream。istream将“>>”重载为输入运算符。ostream将“<<”重载为输出运算符。 用户可以很方便地使用“>>”和 “<<”对系统预定义类型进行输入和输出。 对于类类型,用户也可以将“>>”和 “<<”两个运算符进行重载后以满足自身的需要。 2019/1/17

>>运算符第一个操作数是cin,它是istream的对象的引用;<<运算符第一个操作数是cout,它是ostream的对象的引用。 void main( ) { int num; istream &my_cin=cin; ostream &my_cout=cout; my_cin>>num; my_cout<<num<<endl; } 2019/1/17

重载<<运算符 当用户自定义的className类要重载<<运算符时,重载函数为operator<<,并且只能使用友元函数进行重载。原因在于: 隐式调用的方式为“cout<<obj”。 显示调用方式为“cout.operator<<(obj),而cout本身不是className类的对象。 为了保证”<<“的连用性,重载函数的返回应该为ostream &。 重载函数有两个参数,类型为ostream和className,分别对应于实参cout和obj。 2019/1/17

重载>>运算符 当用户自定义的className类要重载>>运算符时,重载函数为operator>> ,并且只能使用友元函数进行重载。 为了保证“>>”的连用性,重载函数的返回应该为istream &。 注意:重载函数的第一个参数的类型为istream,而第二个参数必须是className的对象的引用。 2019/1/17

举例 class Complex{ double re, im; public: Complex(double r=0.0, double i=0.0): re(r), im(i){ } friend istream &operator>>(istream &scin, Complex &obj); friend ostream &operator<<(ostream &scout, Complex obj); …… }; istream &operator>>(istream &scin, Complex &obj) { scin>>obj.re; scin>>obj.im; return scin; } ostream &operator<<(ostream &scout, Complex obj) scout<<obj.re<<“+”<<obj.im<<“i”; return scout; 2019/1/17

cin>>c1>>c2; Complex c3=c1+c2; cout<<c3; } void main( ) { Complex c1, c2; cin>>c1>>c2; Complex c3=c1+c2; cout<<c3; } 2019/1/17

new和delete的特殊用途 C++多用new和delete来动态申请和释放内存。 因此需要很好地设计类的复制构造函数、析构函数,以及赋值运算符的重载。 2019/1/17

重载new和delete C++将new和delete都看作运算符。重载这两个运算符的格式如下: void *operator new (size_t size){ ……//完成存储分配工作 return pToMemory; } void operator delete(void *p){ ……//释放p所指向的存储空间 2019/1/17

类型size_t由C++定义为能容纳可分配的单一的内存块的最大值。 参数size给出待创建的对象的大小。 重载new函数返回的是指向新分配空间的指针,如果分配不成功,返回NULL。 注意:虽然new函数有参数要求,但是调用该函数时不必给出实参,系统会自动计算出要分配空间的大小。 有两种重载方式:局部重载和全局重载。 2019/1/17

局部重载new和delete 将new和delete重载为与类相关的成员函数,即局部重载。 使用new分配某个重载了new的类的对象时,先调用new的重载函数,再调用构造函数。当构造函数有参数时,还必须给出相应的实参。 使用delete释放某个重载了delete的类的对象空间时,先调用类的析构函数,再调用delete的重载函数。 2019/1/17

举例 class Complex{ double re, im; public: Complex(double r=0.0, double i=0.0): re(r), im(i){ cout<<“Complex Constructing\n”; } ~Complex( ){cout<<“Complex Destructing\n”;} void *operator new(size_t size){ cout<< “new Complex obj\n”; return malloc(size); void operator delete( void *p){ cout<<“delte Complex obj\n”; free(p); }; 2019/1/17

if((p1==NULL)||(p2==NULL)){ cout<<“Allocation Failsure\n”; void main( ){ Complex *p1, *p2; p1=new Complex(3,4); p2=new Complex(5,6); if((p1==NULL)||(p2==NULL)){ cout<<“Allocation Failsure\n”; return; } int *pnum=new int(78); delete p1; delete p2; delete pnum; //输出: new Complex obj Complex Constructing Complex Destructing delte Complex obj 2019/1/17

全局重载new和delete 在任何类说明之外重载new和delete时,称为全局重载new和delete。 全局重载时,C++原来的new和delete被忽略,而重载后的new和delete运算符则用于所有的分配和释放请求。 全局重载有时候会产生一些意想不到的问题,建议尽量少使用全局重载。 2019/1/17

举例 class Complex{ double re, im; public: Complex(double r=0.0, double i=0.0): re(r), im(i){ cout<<“Complex Constructing\n”; } ~Complex( ){cout<<“Complex Destructing\n”;} }; void *operator new(size_t size){ cout<<“begin new obj\n”; return malloc(size); void operator delete( void *p){ cout<<“begin delte obj\n”; free(p); 2019/1/17

if((p1==NULL)||(p2==NULL)){ cout<<“Allocation Failsure\n”; void main( ){ Complex *p1, *p2; p1=new Complex(3,4); p2=new Complex(5,6); if((p1==NULL)||(p2==NULL)){ cout<<“Allocation Failsure\n”; return; } int *pnum=new int(78); delete p1; delete p2; delete pnum; //输出如下,但是当程序快运行完时,会报告错误 begin new obj Complex Constructing Complex Destructing begin delte obj 2019/1/17

运算符重载函数返回值类型的选择 当进行运算符重载的时候,需要根据该运算符的语义来确定运算符重载函数的返回值类型。合适的返回类型将会使得: 重载的“=”运算符可以级联:a=b=c。 能够进行诸如a+b+c+d这样复杂的运算。 能够进行scout<<a<<b或scin>>a>>b这样的级联。 2019/1/17

类型转换 C++允许的类型转换有四种: 标准类型-〉标准类型 标准类型-〉类类型 类类型-〉标准类型 类类型-〉类类型 2019/1/17

标准类型转换到类类型 要实现标准类型到类类型的转换,可以通过重载“=“运算符和定义构造函数来完成。 class counter{ int value; public: counter(int v=0){ value=v; } counter operator=(int num){ value=num; return *this; }; void main( ) { counter obj1=6;//使用构造函数 counter obj2; //使用构造函数 obj2=10;//使用重载的=运算符 } 2019/1/17

类型转换函数 将类类型转换到简单类型的时候,需要一个特殊的成员函数,即类型转换函数来完成。语法如下: className::operator type() { … return type类型的实例; } 该函数的功能是将className类型的对象转换为type类型的实例。type可以是基本类型,也可以是用户自定义类型。 这个转换函数没有参数,没有返回类型,但函数体内必须有一条返回type类型实例的返回语句。 只能将其定义为类的成员函数。 2019/1/17

举例:为counter添加类型转换函数 class counter{ void main( ) int value; { public: counter(int v=0){ value=v; } counter operator=(int num){ value=num; return *this; operator int( ){ return value; }; void main( ) { counter obj1=6; counter obj2; obj2=10; int v1=obj1; int v2=(int)obj2; int v3=int(obj2); } //对于最后3条赋值语句,都调用了类型转换函数。 2019/1/17

请思考 依据编译器而定! 对于“counter objx=obj1+v1”语句,编译器该按怎样的方式进行解释? class counter{ int value; public: counter(int v=0){ value=v; } counter operator=(int num){ value=num; return *this; operator int( ){ return value; }; void main( ) { counter obj1=6; int v1=obj1+1; counter objx=obj1+v1; } 依据编译器而定! 2019/1/17

虚函数和多态性 重载普通的成员函数,存在着两种方式: 以上两种重载的匹配都是在编译的时候静态完成的。 在同一个类中重载 重载函数是以参数特征区分的。 派生类重载基类的成员函数 由于重载函数处在不同的类当中,因此它们的原型可以完全相同。在调用时,使用类名::函数名的方式加以区分。 以上两种重载的匹配都是在编译的时候静态完成的。 2019/1/17

重载是一种简单形式的多态。C++提供另一种更加灵活的多态机制:虚函数。虚函数允许函数调用与函数体的匹配在运行时才确定。 虚函数提供的是一种动态绑定的机制。 2019/1/17

基类对象的指针指向派生类对象 我们已经学习过,在单继承或多继承的公有派生方式下,基类对象的指针可以指向派生类对象。设B是基类,D是派生类: B *p; D Dobj; p = &Dobj; 利用p可以访问从基类B中继承的成员,但是不能用p访问派生类自己定义的成员。 2019/1/17

简单的例子 class Base{ public: void Print(){ cout<<"I am Base\n"; }; class Derived:public Base{ cout<<"I am Derived\n"; void main() { Derived derived; Base base, *p; Base &ref1 = base; Base &ref2 = derived; //调用的是基类的Print p=&base; ref1.Print(); p->Print(); p=&derived; ref2.Print(); } 2019/1/17

我们的目的是试图通过基类引用或指针来访问派生类的成员。但这是行不通的,因为通过基类引用或指针所能看到的是一个基类对象,派生类中的成员对于基类引用或指针来说是“不可见的”。 Other members &Base::Print &Derived::Print p 对于p是不可见的 2019/1/17

为了解决上面的问题,我们可以利用C++的虚函数机制,将基类的Print说明为虚函数形式。这样就可以通过基类引用或指针来访问派生类中的Print。 2019/1/17

简单的例子(续):使用虚函数 class Base{ public: virtual void Print(){ cout<<"I am Base\n"; }; class Derived:public Base{ void Print(){ cout<<"I am Derived\n"; void main() { Derived derived; Base base, *p; Base &ref1 = base; Base &ref2 = derived; //调用的是基类的Print p=&base; ref1.Print(); p->Print(); //调用的是派生类的Print p=&derived; ref2.Print(); } 2019/1/17

虚函数 虚函数的概念 在基类中用virtual关键字声明的成员函数即为虚函数。 虚函数可以在一个或多个派生类中被重新定义,但要求在重定义时虚函数的原型(包括返回值类型、函数名、参数列表)必须完全相同。 2019/1/17

例子:虚函数 class Base{ public: virtual void Print(){ cout<<"I am Base\n"; }; class Derived1:public Base{ void Print(){ cout<<"I am Derived1\n"; class Derived2:public Base{ cout<<"I am Derived2\n"; void main() { Base *p, obj; Derived1 obj1; Derived2 obj2; //调用的是Base的Print p=&obj; p->Print(); //调用的是Derived1的Print p=&obj1; //调用的是Derived2的Print p=&obj2; } 2019/1/17

基类中的函数具有虚特性的条件 在基类中用virtual将函数说明为虚函数。 在公有派生类中原型一致地重载该虚函数。 定义基类引用或指针,使其引用或指向派生类对象。当通过该引用或指针调用虚函数时,该函数将体现出虚特性来。 2019/1/17

注意 在派生类中重载虚函数时必须与基类中的函数原型相同,否则该函数将丢失虚特性。 仅返回类型不同,其他相同。C++认为这种情况是不允许的。 2019/1/17

虚函数的实现机制 函数指针回顾 int add(int a, int b) { return a+b; } int sub(int a, int b) return a-b; void main() { int (*pfunction)(int ,int); int ret; pfunction=add; ret=pfunction(5,3); pfunction=sub; ret=pfunction(6,3); } 2019/1/17

虚函数表和虚指针 在编译时,为每个有虚函数的类建立一张虚函数表VTABLE(即函数指针数组),表中存放的是每一个虚函数的指针;同时用一个虚指针VPTR指向这张表的入口。 VPTR &fun1 &fun2 … &funN VTABLE 访问某个虚函数时,不是直接找到那个函数的地址,而是通过VPTR间接查到它的地址。 2019/1/17

因此,每个对象的内存空间除了保存数据成员外,还保存了VPTR。而VPTR也是由构造函数初始化的。 &Print VTABLE Other members 基类Base Derived d; Base* bp = &d; VPTR &Print VTABLE Other members 子类Derived bp 2019/1/17

对虚函数的要求 虚函数必须是类的成员函数。 不能将虚函数说明为全局函数,也不能说明为静态成员函数。 不能将友元说明为虚函数。 2019/1/17

在成员函数中调用虚函数 在一个基类或派生类的成员函数中,可以直接调用类等级中的虚函数。此时需要根据成员函数中this指针所指向的对象来判断调用的是哪一个函数。 2019/1/17

析构函数可以定义为虚函数 构造函数不能定义为虚函数(为什么?) 而析构函数可以定义为虚函数。 若析构函数为虚函数,那么当使用delete释放基类指针所指向的派生类对象时,先调用派生类的析构函数,再调用基类的析构函数。 2019/1/17

例子 class Base{ public: virtual ~Base(){ cout<<“Base destroy\n”; } }; class Derived:public Base{ ~Derived (){ cout<<“Derived destroy\n”; void main() { Base *p1, *p2; p1=new Base(); p2=new Derived(); delete p1; delete p2; } 输出: Base destroy Derived destroy 2019/1/17

提供虚函数的意义 虚函数的特性使得它成为许多C++程序设计的关键,因为基类可以使用虚函数提供一个接口,这使该类的所有公有派生类都具有相同的接口,但派生类可以定义自己的实现版本,而且虚函数调用的解释依赖于它的对象类型。这实现了“一个接口,多种实现/语义”的概念,为软件的充分可重用提供了坚实的基础。 2019/1/17

纯虚函数及抽象类 基类往往表示一种抽象的概念。在很多时候,基类仅仅提供一些公共的接口,表示这类对象拥有的共同操作。而这些操作又是依赖于不同的派生类对象的,因此,在基类的定义中,这些公共接口只需要有说明而不需要有实现。这就是纯虚函数的概念。 纯虚函数刻画了一系列派生类应该遵循的协议,这些协议的具体实现由派生类来决定。 2019/1/17

例子 class Shape{ virtual float Circumference()=0; Virtual float Area()=0; }; class Circle:public Shape{ float r; …… public: float Circumference(){ return 2*3.14*r; } float Area(){ return 3.14*r*r; class Squre:public Shape{ float l, h; …… public: float Circumference(){ return 2*l*h; } float Area(){ return l*h; }; 2019/1/17

将一个函数说明为纯虚函数,就要求任何派生类都定义自己的实现。 定义纯虚函数的语法形式: virtual type functionName(parameters)=0; 将一个函数说明为纯虚函数,就要求任何派生类都定义自己的实现。 拥有纯虚函数的类被称为抽象类。抽象类不能被实例化,只能作为其它类的基类。抽象类的派生类需要实现纯虚函数,否则该派生类也是一个抽象类。 特别地,当抽象类的所有函数成员都是纯虚函数时,这个类被称为接口类。 2019/1/17