16:14:531C++程序设计教程(第二版)第十三章抽象类Chapter13AbstractClass清华大学出版社钱能16:14:532第十三章内容1.抽象基类(AbstractBase-Class)2.抽象类与具体类(Abstract&ConcreteClasses)3.深度隔离的界面(InterfaceWhichDeeplyParted)4.抽象类作界面(AbstractClassAsInterface)5.演绎概念设计(DeductingConceptDesign)6.系统扩展(SystemExtension)7.手柄(Handle)16:14:5331.抽象基类(AbstractBase-Class)继承体系的多态问题:继承体系反映的是事物的分层分类,它是倒树状,顶端是基类.越顶端越抽象,越底端越具体.基类往往是一种概念表达,或者像Account类那样,仅仅提取了各个子类的共性,本身并不构成有意义的实体.这种基类的成员都是为子类提供的.特别是虚函数,不同的子类有不同的实现,于基类中的定义版本并无意义.classAccount{//...public:virtualvoidwithdrawal(doubleamount){return;//无意义}};16:14:534虚函数都是从基类传播的,靠基类指针来掀动多态.因而,为多态性之故,非得在基类设置虚函数不可:classA{};//基类中无fn()成员classB:publicA{public:virtualvoidfn();};classC:publicA{public:virtualvoidfn();};voidf(A*pa){pa-fn();//编译错}voidg(){f(&B());f(&C());}16:14:535编译器的语法规定,如果一个函数被调用了,则该函数若只有声明而没有定义是万万不能的classA{public:virtualvoidfn();//无定义};classB:publicA{public:voidfn(){}};classC:publicA{public:voidfn(){}};voidf(A*pa){pa-fn();//链接错}voidg(){f(&B());f(&C());}16:14:536纯虚函数classAccount{//抽象类public:virtualvoidwithdrawal(doubleamount)=0;};Accounta(“3”,30);//错:创建对象之故前提:不同的子类表现不同的行为——多态,而基类并不产生对象——只是摆设目的:为了安全性,将基类抽象化,仅用来继承,不准许产生对象.手法:设置纯虚函数。即在基类虚函数声明后面加上”=0”,不须提供定义体,表明为抽象类.任何抽象类若有创建对象操作,则是非法的16:14:5372.抽象类与具体类(Abstract&ConcreteClasses)运行下列程序:voidg(Display*d){d-init();d-write();}intmain(){g(&Monochrome());g(&SVGA());}结果为:MonochromeColorAdapterclassDisplay{public:virtualvoidinit()=0;virtualvoidwrite()=0;};classMonochrome:publicDisplay{virtualvoidinit(){}virtualvoidwrite(){cout“Monochrome\n”;}};classColorAdapter:publicDisplay{public:virtualvoidwrite(){cout“ColorAdapter\n”;}};classSVGA:publicColorAdapter{public:virtualvoidinit(){}};如果要解决的问题涉及单一的类对象,无须继承.如果涉及许多相关的类对象,则需建立一个具有多态的继承体系.也许该继承体系的基类只是用来继承,别无目的,但抽象基类却足以将问题中的概念描述清楚.16:14:5383.深度隔离的界面(InterfaceWhichDeeplyParted)类定义头文件若有修改,将引起类的实现和类的应用程序重新编译.界面不变是指外界可以访问的公有成员不变,而不是类定义头文件不变.类的实现细节可能涉及私有成员的变更.例如:下列两个类界面相同,但类定义不同,头文件自然就不同了.其类的实现也不会相同.日期的年月日版classDate{intyear,month,day;public:Date(conststring&s);Date(intn=1);Date(inty,intm,intd);Dateoperator+(intn)const;Date&operator+=(intn);Date&operator++();friendostream&operator(ostream&o,constDate&d);};日期的天数版classDate{intabsDay;public:Date(conststring&s);Date(intn=1);Date(inty,intm,intd);Dateoperator+(intn)const;Date&operator+=(intn);Date&operator++();friendostream&operator(ostream&o,constDate&d);};16:14:539设法将界面和类定义分离,来实现深度界面隔离.该类作为界面,便不会影响应用编程classDate{Datemid*m_p;public:Date(conststring&s);Date(intn=1);Date(inty,intm,intd);Dateoperator+(intn)const;Date&operator+=(intn);Date&operator++();friendostream&operator(ostream&o,constDate&d);};DateMid类即为以前的Date类:classDateMid{intyear,month,day;public:DateMid(conststring&s);DateMId(intn=1);DateMid(inty,intm,intd);DateMidoperator+(intn)const;DateMid&operator+=(intn);DateMid&operator++();friendostream&operator(ostream&o,constDateMid&d);};16:14:5310界面类的实现,便是Date到DateMid的转换#include”date.h”#include”datemid.h”Date::Date(conststrings):m_p(newDateMid(s)){}Date::Date(intn):m_p(newDateMid(n)){}Date::Date(inty,intm,intd):m_p(newDateMid(y,m,d)){}Date::Date(constDateMid&d):m_p(newDateMid(d)){}Date::~Date(){deletem_p;}DateDate::operator+(intn)const{return*m_p+n;}Date&Date::operator+=(intn){*m_p+=n;return*this;}Date&Date::operator++(){*m_p+=1;return*this;}ostream&operator(ostream&o,constDate&d){returno*(d.m_p);}这样一来,类DateMid的实现也不影响界面Date.以Date类作为分界线,便可以进行充分的抽象编程了16:14:53114.抽象类作界面(AbstractClassAsInterface)抽象类IDate作界面classIDate{public:virtual~IDate(){}virtualIDate&operator+(intn)=0;virtualIDate&operator+=(intn)=0;virtualIDate&operator++()=0;virtualvoidprint(ostream&o)const=0;};IDate&createDate(inty,intm,intd);IDate&createDate(intn);IDate&createDate(conststrings);inlineostream&operator(ostream&o,constIDate&d){d.print(o);returno;}作为界面的Date类转而去调用DateMid类的对应成员,何不将界面Date类做成抽象类呢?!这样一来,应用程序可以通过类体系的多态性来自在使用Date类.另一方面,DateMid的实现可以作为继承界面类Date的具体类.16:14:5312可以还具体类Date以本来面貌,但这次是从IDate类继承而来:classDate:publicIDate{intyear,month,day;public:Date(conststring&s);Date(intn=1);Date(inty,intm,intd);Dateoperator+(intn)const;Date&operator+=(intn);Date&operator++();friendostream&operator(ostream&o,constDate&d);};16:14:5313应用编程时,将对象都以指针或引用的方式来操作,而且创建对象的操作由于没有多态,也需要另外实现.将创建对象的工作作成返回对象引用的普通函数,其声明放在抽象基类的头文件中,作为界面的一部分.IDate&createDate(inty,intm,intd){return*newDate(y,m,d);}一旦创建了对象,其他使用对象的操作与原来相同:voidfn(IDate&d){d.print();}intmain(){IDate&rd=createDate(2005,1,6);fn(rd);delete&rd;}16:14:53145.演绎概念设计(DeductingConceptDesign)抽象编程的要旨是分离各个实现。模块编程很好地履行了抽象编程的要旨,用名字代表模块,从而构筑程序的框架,之后再实现之,便是从抽象到具体,从上到下的编程方式.用名字构架程序结构的抽象编程,实际上是在演绎概念设计.16:14:5315概念设计:类的继承体系设计.界面确定由于语言的支持,可以直接以代码设计的形式来进行在概念设计之后进行的工作:程序结构的框架(包括文件组织)实现的环境和运行方式类代码设计应用代码设计16:14:53166.系统扩展(SystemExtension)在类继承体系中,进行功能扩展的情形主要有两种:1普遍增加一种操作(在基类中添上纯虚函数)2在某个子类中继承一个非基类第一种情况:可以通过继承抽象基类的方法,达到功能扩展,维护了应用与类实现的隔离.而又维持了老系统的类继承体系.SonySony2老系统界面新系统界面16:14:5317第二种情况系统扩展,带来设施的添加,由于界面不变,所以新设施的编程只是带来添加部分的编程和新设施的实现编程.它们两者是独立的.16:14:53187.手柄(Handle)指针操作多态,一是参数传递上用到,二是动态转型上用到.指针有的时候是复制指针值,使两个指针指向同一个实体,有的时候是复制指针所指向的对象,这时候往往要创建另一个指针来指派.指针涉及申请动态内存和释放操作,这些操作是建立在多态编程中的纷繁的复制操作之中.指针编程因而很容易产生错误(特别是对初学者),而且,产生的