1、02:58:57,1,C+程序设计教程(第二版),第十三章 抽象类 Chapter 13 Abstract Class,清华大学出版社 钱 能,02:58:57,2,第十三章内容,抽象基类(Abstract Base-Class) 抽象类与具体类(Abstract & Concrete Classes) 深度隔离的界面 (Interface Which Deeply Parted) 抽象类作界面(Abstract Class As Interface) 演绎概念设计(Deducting Concept Design) 系统扩展(System Extension) 手柄(Handle),02:5
2、8:57,3,1. 抽象基类 ( Abstract Base-Class ),继承体系的多态问题:继承体系反映的是事物的分层分类,它是倒树状,顶端是基类越顶端越抽象,越底端越具体 基类往往是一种概念表达,或者像Account类那样,仅仅提取了各个子类的共性,本身并不构成有意义的实体这种基类的成员都是为子类提供的特别是虚函数,不同的子类有不同的实现,于基类中的定义版本并无意义 class Account/ . public:virtual void withdrawal(double amount)return; / 无意义 ;,02:58:57,4,虚函数都是从基类传播的,靠基类指针来掀动多态
3、因而,为多态性之故,非得在基类设置虚函数不可:,class A; / 基类中无fn()成员 class B : public A public:virtual void fn(); ; class C : public A public:virtual void fn(); ; void f(A* pa)pa-fn(); / 编译错 void g()f( ,02:58:57,5,编译器的语法规定,如果一个函数被调用了,则该函数若只有声明而没有定义是万万不能的,class A public:virtual void fn(); / 无定义 ; class B : public A public:
4、void fn() ; class C : public A public:void fn() ; void f(A* pa)pa-fn(); / 链接错 void g()f( ,02:58:57,6,纯虚函数,class Account /抽象类 public:virtual void withdrawal(double amount)=0; ;Account a(“3”, 30); / 错:创建对象之故,前提:不同的子类表现不同的行为多态,而基类并不产生对象只是摆设 目的:为了安全性,将基类抽象化,仅用来继承,不准许产生对象 手法:设置纯虚函数。即在基类虚函数声明后面加上”=0”,不须提供
5、定义体,表明为抽象类任何抽象类若有创建对象操作,则是非法的,02:58:57,7,2. 抽象类与具体类 ( Abstract & Concrete Classes ),运行下列程序: void g(Display* d)d-init();d-write(); int main()g( 结果为: Monochrome ColorAdapter,class Display public:virtual void init() = 0;virtual void write() = 0; ; class Monochrome : public Displayvirtual void init()vir
6、tual void write()cout“Monochromen”; ; class ColorAdapter : public Display public:virtual void write()cout“ColorAdaptern”; ; class SVGA : public ColorAdapter public:virtual void init() ;,如果要解决的问题涉及单一的类对象,无须继承.如果涉及许多相关的类对象,则需建立一个具有多态的继承体系.也许该继承体系的基类只是用来继承,别无目的,但抽象基类却足以将问题中的概念描述清楚.,02:58:57,8,3. 深度隔离的界
7、面 ( Interface Which Deeply Parted ),类定义头文件若有修改,将引起类的实现和类的应用程序重新编译. 界面不变是指外界可以访问的公有成员不变,而不是类定义头文件不变. 类的实现细节可能涉及私有成员的变更.例如:下列两个类界面相同,但类定义不同,头文件自然就不同了其类的实现也不会相同,日期的年月日版 class Dateint year, month, day; public:Date(const string,日期的天数版 class Dateint absDay; public:Date(const string,02:58:57,9,设法将界面和类定义分离,
8、来实现深度界面隔离.,该类作为界面,便不会影响应用编程 class DateDatemid* m_p; public:Date(const string,DateMid类即为以前的Date类: class DateMidint year, month, day; public:DateMid(const string,02:58:57,10,界面类的实现,便是Date到DateMid的转换#include”date.h” #include”datemid.h”Date:Date(const string s):m_p(new DateMid(s) Date:Date(int n):m_p(ne
9、w DateMid(n) Date:Date(int y, int m, int d):m_p(new DateMid(y,m,d) Date:Date(const DateMid 这样一来,类DateMid的实现也不影响界面Date. 以Date类作为分界线,便可以进行充分的抽象编程了,02:58:57,11,4. 抽象类作界面 ( Abstract Class As Interface ),抽象类IDate作界面 class IDate public:virtual IDate()virtual IDate ,作为界面的Date类转而去调用DateMid类的对应成员,何不将界面Date类做
10、成抽象类呢?!这样一来,应用程序可以通过类体系的多态性来自在使用Date类另一方面,DateMid的实现可以作为继承界面类Date的具体类.,02:58:57,12,可以还具体类Date以本来面貌,但这次是从IDate类继承而来: class Date:public IDateint year, month, day; public:Date(const string,02:58:57,13,应用编程时,将对象都以指针或引用的方式来操作,而且创建对象的操作由于没有多态,也需要另外实现. 将创建对象的工作作成返回对象引用的普通函数,其声明放在抽象基类的头文件中,作为界面的一部分 IDate ,0
11、2:58:57,14,5. 演绎概念设计 ( Deducting Concept Design ),抽象编程的要旨是分离各个实现。模块编程很好地履行了抽象编程的要旨,用名字代表模块,从而构筑程序的框架,之后再实现之,便是从抽象到具体,从上到下的编程方式 用名字构架程序结构的抽象编程,实际上是在演绎概念设计,02:58:57,15,概念设计: 类的继承体系设计 界面确定 由于语言的支持,可以直接以代码设计的形式来进行,在概念设计之后进行的工作:程序结构的框架(包括文件组织) 实现的环境和运行方式类代码设计 应用代码设计,02:58:57,16,6. 系统扩展 ( System Extension
12、 ),在类继承体系中,进行功能扩展的情形主要有两种: 普遍增加一种操作(在基类中添上纯虚函数) 在某个子类中继承一个非基类第一种情况: 可以通过继承抽象基类的方法,达到功能扩展,维护了应用与类实现的隔离而又维持了老系统的类继承体系,Sony,Sony2,老系统界面,新系统界面,02:58:57,17,第二种情况 系统扩展,带来设施的添加,由于界面不变,所以新设施的编程只是带来添加部分的编程和新设施的实现编程它们两者是独立的,02:58:57,18,7. 手柄 ( Handle ),指针操作多态,一是参数传递上用到,二是动态转型上用到 指针有的时候是复制指针值,使两个指针指向同一个实体,有的时候
13、是复制指针所指向的对象,这时候往往要创建另一个指针来指派 指针涉及申请动态内存和释放操作,这些操作是建立在多态编程中的纷繁的复制操作之中 指针编程因而很容易产生错误(特别是对初学者),而且,产生的错误一般都是致命错误,02:58:57,19,class SonyHandleSony* sp; public:Sony* operator-() return sp; SonyHandle(Sony* pp) : sp(pp) ;,对象的析构是自动的 让对象指针做成对象,便可以免遭人工释放所带来的误操作之苦 “外套”就是指针的类形式,02:58:57,20,class SonyHandleSony* sp;int* count; public:SonyHandle(Sony* pp);SonyHandle(const SonyHandle,再让手柄类具有一些操作,使得该类能够进行普通的指针操作将其实用起来,