第 5 章 继承、多态和虚函数 陈哲 副教授 南京航空航天大学 计算机科学与技术学院.

Slides:



Advertisements
Similar presentations
第 2 章 初探 C++.
Advertisements

程序设计实习 3月份练习解答
Chapter 6 字串與時間處理.
第九讲 类与对象 (I)面向对象基础.
第6章 多态性与虚函数.
第4章 数组 数组是由一定数目的同类元素顺序排列而成的结构类型数据 一个数组在内存占有一片连续的存储区域 数组名是存储空间的首地址
第八章 类和对象.
C++程序设计 王希 图书馆三楼办公室.
C++语言程序设计教程 第7章 类与对象 第7章 类与对象.
刘胥影 东南大学计算机学院 面向对象程序设计1 2010~2011第3学期 刘胥影 东南大学计算机学院.
第5章 面向对象程序设计 本章要点 5.1 面向对象程序设计概述 5.2 Java语言的面向对象程序设计 5.3 方法的使用和对象数组
内容提要 对象的生命周期 构造函数 析构函数 拷贝构造函数. 常宝宝 北京大学计算机科学与技术系
C++语言程序设计 第四章 类与对象 成都信息工程学院计算机系.
函數(一) 自訂函數、遞迴函數 綠園.
Chap 18 類別與物件 夫有土者,有大物也。有大物者,不可以物。 物而不物,故能物物。 明乎物物者之非物也,豈獨治天下百姓而已哉!
4.1 概述 4.2 类与对象的实现 4.3 对象的初始化和析构 4.4 类的包含 4.5 类模板
Derived Class 前言 衍生類別的定義 單一繼承 public, protected, 和 privated 基底類別
教材 《C++程序设计》.谭浩强. 清华大学出版社 王雪晶
刘胥影 东南大学计算机学院 面向对象程序设计1 2010~2011第3学期 刘胥影 东南大学计算机学院.
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
刘胥影 东南大学计算机学院 面向对象程序设计1 2011~2012第3学期 刘胥影 东南大学计算机学院.
Ch10 類別與物件-方法 Java程式設計(2).
Classes: A Deeper Look, Part 1
第六章 继承性和派生类 胡昊 南京大学计算机系软件所.
刘胥影 东南大学计算机学院 面向对象程序设计1 2010~2011第3学期 刘胥影 东南大学计算机学院.
類別樣板 Class Template 類似函式樣板 由類別樣板產生的類別稱為類別樣版的實體(instance)
第3章 继承和派生.
C++语言程序设计 C++语言程序设计 第四章 数组及自定义数据类型 C++语言程序设计.
授课老师:龚涛 信息科学与技术学院 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++語言的動態記憶體配置
前處理指令可以要求前處理器 (preprocessor) 在程式編譯之前,先進行加入其它檔案的內容、文字取代以及選擇性編譯等工作。
2 C++ 的基本語法和使用環境 親自撰寫和執行程式是學好程式語言的不二法門。本章藉由兩個簡單的程式,介紹C++ 程式的基本結構和開發環境,讓初學者能逐漸建立使用C++ 的信心。
C++程序设计 string(字符串类) vector(容器类).
面向对象程序设计 QQ群: Object-Oriented Programming 汽车学院.
C++语言程序设计 第二章 C++简单程序设计.
程序的三种基本结构 if条件分支语句 switch多路开关语句 循环语句 循环嵌套 break,continue和goto语句
谭浩强 编著 中国高等院校计算机基础教育课程体系规划教材 C++程序设计.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
切換Dev c++顯示語言 工具->環境選項(V)->介面->language (Chinese TW)
10 多載函數 10.1 多載概論 多載一般函數 多載成員函數 10-3
C++大学基础教程 第11章 多态性 北京科技大学 信息基础科学系 2019/4/8 北京科技大学.
OOP6 結構Struct 黃兆武.
Name1..hour //加班時數 name2..hour //請假時數
第二章 基本数据类型及运算 C数据类型概述 基本数据类型 运算符和表达式 混合运算与类型转换 数据的输入输出 顺序程序设计举例.
Chapter 2 & Chapter 3.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
物件導向程式設計 CH2.
第11章 從C到C++語言 11-1 C++語言的基礎 11-2 C++語言的資料型態與運算子 11-3 C++語言的輸出與輸入
Inheritance -II.
第三章 数据抽象.
Object-Oriented Programming in C++ 第二章 类和对象
C++语言程序设计 C++语言程序设计 第八章 继承 C++语言程序设计.
C++程式設計入門 變數與運算子 作者:黃建庭.
C/C++基礎程式設計班 C++: 物件的使用、參考、重載函式 講師:林業峻 CSIE, NTU 3/28, 2015.
第 9 章 建構函式與解構函式.
第 3 章 类的基础部分 陈哲 副教授 南京航空航天大学 计算机科学与技术学院.
#include <iostream.h>
C++语言程序设计 C++语言程序设计 第八章 继承 C++语言程序设计.
第 4 章 类的高级部分 陈哲 副教授 南京航空航天大学 计算机科学与技术学院.
C++语言程序设计 C++语言程序设计 第十章 多态 第十一组 C++语言程序设计.
《数据结构与算法设计》第一部分 面向对象的C++程序设计基础.
C++语言程序设计 C++语言程序设计 第十一章 异常处理 C++语言程序设计.
C++语言程序设计(第4版) 第七章 继承与派生 数学与统计科学学院 胡凤珠.
變數與資料型態  綠園.
資料結構與C++程式設計進階 C++與資料結構 講師:林業峻 CSIE, NTU 7/ 5, 2010.
Presentation transcript:

第 5 章 继承、多态和虚函数 陈哲 副教授 南京航空航天大学 计算机科学与技术学院

5.1 继承 继承是OOP程序设计中很重要的一个方面。继承易于扩充现有类以满足新的应用。将已有的类称之为父类,也称基类,将新产生的类称为子类,也称为导出类或派生类。 导出类不做任何改变地继承了基类中的所有变量和函数(构造函数和析构函数除外),并且还可以增加新的数据成员和函数,从而使导出类比基类更为特殊化。 例5-1: Test类继承Grade类。

class Grade { char letter; float score; void calcGrade( ); public: void setScore(float s) { score = s; calcGrade( ); } float getScore( ) { return score; } char getLetter( ) { return letter; } };

// Definition of member function Grade::calcGrade void Grade::calcGrade( ) { if (score > 89) letter = 'A'; else if (score > 79) letter = 'B'; else if (score > 69) letter = 'C'; else if (score > 59) letter = 'D'; else letter = 'F'; }

class Test : public Grade { int numQuestions; float pointsEach; int numMissed; public: Test( int, int ); };

Test::Test(int q, int m) { float numericGrade; numQuestions = q; //参数q 代表问题的个数,m代表答错的题数 . Test::Test(int q, int m) { float numericGrade; numQuestions = q; numMissed = m; pointsEach = 100.0f / numQuestions; numericGrade = 100.0f - numMissed * pointsEach ; setScore(numericGrade); }

{ int questions, missed; cout << "How many questions ? "; void main( ) { int questions, missed; cout << "How many questions ? "; cin >> questions; cout << "How many questions missed? "; cin >> missed; Test exam(questions, missed); cout.precision(2); cout << "\n The score is " << exam.getScore( ); cout << "\n The grade is " << exam.getLetter( ); } 5-1.cpp

BadBase( ) { x = getVal( ); } // Error }; 上例中,父类中的公有成员在子类中仍是公有的,它们可以和子类中的公有成员一样被访问。但反过来是错误的,基类对象或基类中的某个函数不能调用子类中的函数。 class BadBase { int x; public: BadBase( ) { x = getVal( ); } // Error }; class Derived : public BadBase { int y; Derived( int z ) { y = z; } int getVal( ) { return y; }

5.2 保护成员和类的访问 基类中的保护成员和私有成员比较类似,唯一的区 别是:子类不可访问基类中的私有成员,但可访问 基类中的保护成员。 5.2 保护成员和类的访问 基类中的保护成员和私有成员比较类似,唯一的区 别是:子类不可访问基类中的私有成员,但可访问 基类中的保护成员。 在公有继承或保护继承的情况下,子类能访问基类 的protected成员。 Example: 例 5-2

class Grade { protected: char letter; float score; void calcGrade( ); public: void setScore(float s) { score = s; calcGrade( ); } float getScore( ) { return score; } char getLetter( ) { return letter; } };

class Test : public Grade { int numQuestions; float pointsEach; int numMissed; public: Test( int, int ); void adjustScore( ); // 新增加的函数 };

who //构造函数略 void Test::adjustScore( ) { if ((score - int(score)) >= 0.5f ) score += 0.5 ; calcGrade( ); } who

继承方式 基类成员在子类中的表现 private 1.基类的私有成员在子类中不可访问; 2.基类的保护成员变成了子类中的私有成员; 3.基类的公有成员变成了子类中的私有成员。 protected 2.基类的保护成员变成了子类中的保护成员; 3.基类的公有成员变成了子类中的保护成员。 public 3.基类的公有成员变成了子类中的公有成员。

Example: private base class protected base class public base class x is inaccessible private: y private: z protected: y protected: z public: z private: x protected: y public: z private base class protected base class public base class

注意 如果省略了继承修饰符,那么就是私有继承: class Test : Grade 不要将继承修饰符与成员的访问修饰符相混淆:成员访问修饰符是规定类外语句能否访问类中的成员,而继承修饰符是为了限定基类成员在子类中的表现。

5.3 构造函数和析构函数 当基类和子类都有构造函数时,如果定义一个子类对象,那么首先要调用基类的构造函数,然后再调用子类的构造函数; 析构函数的调用次序与此相反,即先调用子类的析构函数,然后再调用基类的析构函数。 Example: 例 5-3.

class BaseDemo { public: BaseDemo( ) { cout << "In BaseDemo constructor.\n"; } ~BaseDemo( ) { cout << "In BaseDemo destructor.\n"; } }; class DerivedDemo : public BaseDemo { DerivedDemo( ) { cout << "In DerivedDemo constructor.\n"; } ~ DerivedDemo( ) { cout << "In DerivedDemo destructor.\n"; }

void main( ) { } cout << "下面定义一个 DerivedDemo 类对象 \n" ; DerivedDemo object ; cout << "下面将要结束程序 \n" ; } 5-3.cpp

5.3.2 向基类的构造函数传参数 如果基类和子类都有缺省的构造函数,它们的调用是自动完成的,这是一种隐式调用。 5.3.2 向基类的构造函数传参数 如果基类和子类都有缺省的构造函数,它们的调用是自动完成的,这是一种隐式调用。 如果基类的构造函数带有参数,那么必须让子类的构造函数显式调用基类的构造函数,并且向基类构造函数传递适当的参数。 例 5-4

class Rectangle { protected: float width , length, area; public: Rectangle( ) { width = length = area = 0.0f ; } Rectangle ( float w, float l ) { width = w; length = l; area = width * length; } float getArea( ) { return area; } float getLen( ) { return length; } float getWidth( ){ return width; } };

class Cube : public Rectangle { protected: float height, volume; Cube(float, float, float); float getHeight ( ) { return height; } float getVol( ) { return volume; } }; Cube::Cube(float w, float l, float h) : Rectangle(w, l) height = h ; volume = area * height ; } 5-4.cpp

Note 如果基类没有缺省的构造函数,那么子类必须至少具有一个带参的构造函数,以便向基类构造函数传递参数。

5.3.3 初始化列表的作用 1. 如果类之间具有继承关系,子类必须在其初始化列表中调用基类的构造函数。例: class Base { Base( int x ); }; class Derived : public Base Derived(int x, int y): Base(x) { /* … */ }

5.3.3 初始化列表的作用 2. 类中的const常量只能在初始化列表中进行初始化,而不能在函数体内用赋值的方式来初始化。 class Base { const int SIZE ; Base(int size) : SIZE(size) { /* … */ } }; Base one(100);

5.3.3 初始化列表的作用 3. 对象类型的成员的初始化放在初始化列表中,则效率较高,反之较低。基本类型变量的初始化可以在初始化列表中,也可在构造函数中,效率上没区别。 class Base{ Base( ); Base(const Base &other); }; class Derived{ Base B_Member; public: Derived(const Base &a); }; 构造函数的实现: Derived::Derived(const Base & b ) : B_Member(b) { /* … */ } 也可这样实现,但效率较低。 Derived::Derived(const Base &b) { B_Member = b; }

5.4 覆盖基类的函数成员 重载的特点: (1) 重载表现为有多个函数,它们的名字相同,但参数 不全相同; 5.4 覆盖基类的函数成员 重载的特点: (1) 重载表现为有多个函数,它们的名字相同,但参数 不全相同; (2) 重载可以出现在同一个类中,也可出现在具有继承关系的父类与子类中; (3) 重载也可表现为外部函数的形式。

5.4 覆盖基类的函数成员 覆盖的特点: (1) 覆盖一定出现在具有继承关系的基类和子类之间; 5.4 覆盖基类的函数成员 覆盖的特点: (1) 覆盖一定出现在具有继承关系的基类和子类之间; (2) 覆盖除了要求函数名完全相同,还要求相应的参数个数和类型也完全相同 ; (3) 当进行函数调用时,子类对象所调用的是子类中定义的函数; (4) 覆盖是C++多态性的部分体现。

class MileDist { protected: float miles; public: void setDist( float d ) { miles = d; } float getDist( ) { return miles; } }; class FeetDist : public MileDist protected: float feet; void setDist(float); float getDist( ) { return feet; } float getMiles( ) { return miles; }

void FeetDist::setDist(float ft) { feet = ft; MileDist::setDist(feet/5280); //Call base class function } void main( ) { FeetDist feet; float ft; cout << "A distance in feet and convert it to miles:"; cin >> ft; feet.setDist(ft); cout << feet.getDist( ) << " feet equals "; cout << feet.getMiles( ) << " miles.\n"; 哪一个? 5-5.cpp

5.5 虚函数 函数覆盖体现了一定的多态性。但是,简单的函数覆盖并不能称为真正的多态性。 Example: 例 5-7

who class MileDist { protected: float miles; public: void setDist(float d){ miles = d; } float getDist( ) { return miles; } float square( ) { return getDist( ) * getDist( ); } }; who

class FeetDist : public MileDist { protected: float feet; public: void setDist( float ); float getDist( ) { return feet; } float getMiles( ) { return miles; } }; void FeetDist::setDist(float ft ) feet = ft; MileDist::setDist(feet / 5280); }

cout << "请输入以英尺为单位的距离:" ; cin >> ft ; feet.setDist( ft ) ; void main( ) { FeetDist feet; float ft; cout << "请输入以英尺为单位的距离:" ; cin >> ft ; feet.setDist( ft ) ; cout << feet.getDist( ) << " 英尺等于 " ; cout << feet.getMiles( ) << " 英里\n" ; cout << feet.getDist( ) << "英尺的平方等于 " ; cout << feet.square( ) << "平方英尺\n" ; } 5-7.cpp

错误的原因:C++编译器在缺省情况下,对函数成员的调用实施的是静态连编(也称静态绑定)。 解决方法:将getDist设置为虚函数。对于虚函数,编译器完成的是动态连编(也称动态绑定),即对函数的调用是在运行时确定的。 OOP: 覆盖和重载不能体现真正的多态性,只有虚函数才是多态性的表现。不支持多态性的语言,就不能称为OOP。

5.6 纯虚函数和抽象类 纯虚函数是在基类中声明的虚函数,没有函数体,要求继承基类的子类必须覆盖它。 5.6 纯虚函数和抽象类 纯虚函数是在基类中声明的虚函数,没有函数体,要求继承基类的子类必须覆盖它。 带有纯虚函数的类称为抽象类,不能定义抽象类的对象。 派生类可以根据自己的需要,分别覆盖它,从而实现真正意义上的多态性。 格式: virtual void showInfo( ) = 0;

class Student // 例 5-9 { protected: char name[51]; int hours; public: Student( ){ name[0] = hours = 0; } void setName(char *n) { strcpy(name, n); } // Pure virtual function virtual void setHours( ) = 0; virtual void showInfo( ) = 0; };

class CsStudent : public Student { int mathHours , csHours; public: void setMathHours(int mh){ mathHours = mh; } void setCsHours(int csh){ csHours = csh; } void setHours( ) { hours = mathHours + csHours; } void showInfo( ); };

void CsStudent::showInfo( ) { cout << " Name: " << name << endl; cout << "\t Math: " << mathHours << endl; cout << "\t CS : " << csHours; cout << "\n\t Total Hours: " << hours; } void main( ) { CsStudent student1; char chInput[51]; int intInput;

cout << "Enter the following information:\n"; cout << "Name: "; cin.getline(chInput, 51); student1.setName(chInput); cout << "Number of math hours completed: "; cin >> intInput; student1.setMathHours( intInput ); cout << "Number of CS hours completed: "; student1.setCsHours(intInput); student1.setHours( ); cout << "\nSTUDENT INFORMATION\n"; student1.showInfo( ); } 5-9.cpp

关于抽象类和纯虚函数小节 如果一个类包含有纯虚函数,那么它就是抽象类,必须让其它类继承; 基类中的纯虚函数没有代码; 不能定义抽象类的对象,即抽象基类不能实例化; 必须在子类中覆盖基类中的纯虚函数。

5.6.3 指向基类的指针 指向基类对象的指针可以指向其子类的对象; 5.6.3 指向基类的指针 指向基类对象的指针可以指向其子类的对象; 如果子类覆盖了基类中的成员,但通过基类指针所访问的成员仍是基类的成员,而不是子类成员。 Example: 例 5-10

void show( ){ cout << "In Base class.\n"; } }; class Base { public: void show( ){ cout << "In Base class.\n"; } }; class Derived : public Base { void show( ){ cout << "In Derived class.\n"; } void main( ) { Base *bptr; Derived dobject; bptr = &dobject; bptr->show( ); } 1. 调用谁? 2. 什么原因? 3. 如何解决? 4. 引用调用呢? 5-10.cpp

5.7 多重继承 class A class B class C 5.7 多重继承 class A Class C inherits all of Class B's members, including the ones Class B inherited from Class A. class B class C

5.8 多继承 class A class B class C 如果一个子类具有两个或多个直接父类,那么就称为多继承。 5.8 多继承 如果一个子类具有两个或多个直接父类,那么就称为多继承。 class A class B class C Constructors are called in the order they are listed in the first line of the class declaration.

class Date // 例 5-12 { protected: int day , month , year; public: Date(int d, int m, int y) day = d; month = m; year = y; } int getDay( ) { return day; } int getMonth( ) { return month; } int getYear( ) { return year; } };

class Time { protected: int hour , min , sec; public: Time(int h, int m, int s) hour = h; min = m; sec = s; } int getHour( ){ return hour; } int getMin( ) { return min; } int getSec( ) { return sec; } };

class DateTime : public Date, public Time { protected: char dTString[20]; public: DateTime( int, int, int, int, int, int); void getDateTime( char *str) strcpy(str, dTString); } }; 这个顺序很重要

DateTime::DateTime(int yr,int mon,int dy, int hr, int mt, int sc) : Time(hr, mt, sc), Date(dy, mon, yr) { char temp[10]; // In the form YY/MM/DD strcpy(dTString, itoa(getYear( ), temp, 10)); strcat(dTString, "/"); strcat(dTString, itoa(getMonth( ), temp, 10)); strcat(dTString, itoa(getDay( ), temp, 10)); strcat(dTString, " "); strcat(dTString, itoa(getHour( ), temp, 10)); strcat(dTString, ":"); strcat(dTString, itoa(getMin( ), temp, 10)); strcat(dTString, itoa(getSec( ), temp, 10)); } #include <stdlib.h> // itoa函数的用法 void main( ) { int i=12; char s[100]; itoa(i,s,16); cout<<s<<endl; }

pastDay.getDateTime( formatted ); void main( ) { char formatted[20]; DateTime pastDay(28, 7, 2010, 11, 32, 27) ; pastDay.getDateTime( formatted ); cout << formatted << endl; } 5-12.cpp

#include <ctime> #include <iostream> using namespace std; void main( ) { char tmpbuf[128]; // Display operating system-style date and time _strtime( tmpbuf ); cout<< "time:\t"<<tmpbuf<<endl ; _strdate( tmpbuf ); cout<< "date:\t"<<tmpbuf<<endl ; } 获取系统时间 5-a.cpp