面向对象程序设计与C++第八章继承教师:王涛电话:51688243办公室:九教北512E-mail:twang@bjtu.edu.cn1.派生类的概念•类是具有相同特征(属性特征和行为特征)的对象集合的抽象。•继承是对象类的一种相关关系,具有如下性质:–类间的共享特征–类间的细微区别–类间的层次结构鱼海水鱼淡水鱼黄花鱼带鱼鲤鱼墨鱼•引入继承的目的在于重用已有代码。为什么使用继承如已有字符串类stringclassstring{//字符串类char*contents;public:intget_length();char*get_contents();voidset_contents(char*contents);voiddisplay();};为什么使用继承•要实现一个可编辑的字符串类edit_string:classedit_string{//可编辑字符串类char*contents;intcursor;//当前游标public:intget_length();char*get_contents();voidset_contents(char*contents);voiddisplay();intget_cursor_pos();intadd_at_cursor(char*new_text);intreplace_at_cursor(char*new_text);intdelete_at_cursor(inthow_much);};•通过引入继承:classedit_string:publicstring{//可编辑字符串类intcursor;public:intget_cursor_pos();intadd_at_cursor(char*new_text);intreplace_at_cursor(char*new_text);intdelete_at_cursor(inthow_much);};为什么使用继承•派生类的定义有三种:–私有派生–公有派生–保护派生派生类的定义classx{/*…*/};classy:x{//私有派生//…};classy:publicx{//共有派生//…};classy:protectedx{//保护派生//…};派生类•当继承基类时,派生类就获得了基类所有数据成员的副本,同时也拥有了基类声明的所有接口.classA{inta,b;};classB:classA{intx,y;};sizeof(A)=8;sizeof(B)=16;AabBabxy•派生类对基类成员的访问权:在派生类的域范围内,是否可以直接访问基类对象的成员?•派生类对基类成员的继承:是否可以正常使用继承于基类的成员?classA{private:intx;protected:inty;public:intz;};classB:publicA{voidf(){Aa;a.x=0;a.y=0;a.z=0;//是否可访问a.x,a.y,a.z?x=0;y=0;z=0;//是否可使用A中声明的x,y,z}};2.派生类对基类成员的访问与继承派生类对基类成员的访问权•派生类是不同于基类的新类,他对基类成员的访问权取决于基类成员类型,与派生方式无关。•派生类对基类成员的访问权可归结如下表:基类成员类型私有派生公有派生private否否protected否否public是是•示例程序:08_01从上表可看出,派生类对基类成员的访问权,与其他任何类对基类的访问权一样。•派生类对基类成员的继承性(也就是派生类中是否可使用基类中声明的成员),取决于基类成员的类型,与派生方式无关。可归结为下表:派生类对基类成员的继承性•示例程序:08_02基类成员类型私有派生公有派生private否否protected是是public是是派生方式影响什么?•派生方式决定:基类成员在派生类中的安全类型。–私有派生.基类的public成员和protected成员继承到派生类后,成为派生类的private成员.–保护派生.基类的public成员和protected成员继承到派生类后,成为派生类的protected成员.–公有派生.公有派生不改变继承成员的安全类型。•通过如下方式可以改变继承成员的安全属性。classA{publicintx,y;voidf1();};classB:A{publicA::x;//注意:前面不要写int.A::f1;};//x,f1都为public类型•示例程序:08_03。派生类域:同名变量和函数•派生类域被自动嵌套在基类类域中.classbase{protected:intx;public:base(){x=0;inc();}voidinc(){++x;}voiddisplay(){cout“base:x=“xendl;}};classderive:publicbase{protected:intx;public:derive(){x=10;inc();base::inc();}voidinc(){x+=10;}voiddisplay(){cout“derive:x=“xendl;}};main(){baseb;derived;b.display();d.display();d.base::display();}输出:base:x=1derive:x=20base:x=2注:sizeof(base)=4sizeof(derive)=83.派生类的构造函数与析构函数•派生类构造函数执行之前,自动调用基类的构造函数.派生类析构函数执行之后,自动调用基类析构函数.classA{public:A(){printf(“\nconstructA.”);}~A(){printf(“\ndestroyA.”);}};classB:publicA{public:B(){printf(“\nconstructB.”);}~B(){printf(“\ndestroyB.”);}};main(){Bb;}输出:constructA.constructB.destroyB.destroyA.•若基类构造函数带参数,必须在派生类的构造函数的初始化成员列表中将参数传入.classA{intx;public:A(intxv){x=xv;}};classB:publicA{public:B(intxv):A(xv){}};main(){Bb(0);}派生类的构造函数与析构函数•若派生类中包含类对象成员,则构造函数调用顺序:基类成员对象派生类;析构函数调用顺序正好相反。派生类的构造函数与析构函数classA{protected:intx;public:A(intn){x=n;cout“A():x=“xendl;}~A(){cout“~A():x=“xendl;}};classB:publicA{Aa;public:B(intn):a(1),A(0){x=n;cout“B():x=“xendl;}~B(){cout“~B():x=“xendl;}};main(){Bb(2);}输出:A():x=0A():x=1B():x=2~B():x=2~A():x=1~A():x=24.多继承•当一个类具有多个类所具有的特点,就可能用到多继承。交通工具商品汽车ABCDEFABCDEF单继承多继承多继承的定义•多继承的定义如下:classA{/*…*/};classB{/*…*/};classC:A,B{/*…*/};//对A,B都为私有派生classC:publicA,B{/*…*/};//对A公有派生,对B私有派生classC:A,publicB{/*…*/};//对A私有派生,对B公有派生classC:publicA,publicB{/*…*/};//对A,B都为公有派生•在多继承中,派生类对基类成员的访问权和继承性的规则与单继承相同.多继承的构造函数与析构函数•多继承中,派生类的构造函数和析构函数调用时,也会自动调用基类的构造函数和析构函数.•基类构造函数的调用顺序与定义继承的顺序一致,析构函数的调用顺序则相反。classA{public:A(){cout“A().”endl;}~A(){cout“~A().”endl;}};classB{public:B(){cout“B().”endl;}~B(){cout“~B().”endl;}};classC:B,A{public:C(){cout“C().”endl;}~C(){cout“~C().”endl;}};main(){Cc;}输出:B().A().C().~C().~A().~B().虚拟继承和虚基类classx{protected:inta;public:x(){a=10;}};classx1:publicx{public:x1(){coutaendl;}};classx2:publicx{public:x2(){coutaendl;}};classy:x1,x2{public:y(){coutaendl;}//error.应使用x1::a或x2::a};axax1ax2x1::ayx2::a•当类之间的继承关系出现如下棱形关系时,如使用普通继承,派生类中就会出现公共基类成员的多个副本。•为了使派生类中只保留公共基类成员的一个副本,需要使用虚继承:classx1:virtualpublicx{/*…*/};classx2:virtualpublicx{/*…*/};虚拟继承和虚基类xx1x2y虚拟继承和虚基类classx{protected:inta;public:x(){a=10;}};classx1:virtualpublicx{//虚拟继承public:x1(){coutaendl;}};classx2:virtualpublicx{//虚拟继承public:x2(){coutaendl;}};classy:x1,x2{public:y(){coutaendl;}//ok};axax1ax2ay•在多继承时,使用虚继承的基类与使用非虚继承的基类初始化顺序不同:–虚继承的构造函数在非虚继承的之前调用.–若同一层次中包含多个虚继承,这些虚继承的构造函数按他们说明的顺序调用.–无论任何方式的继承,基类的构造函数在派生类的构造函数之前调用.•示例程序08_04虚继承的初始化顺序虚拟继承和虚基类•有关多继承,虚拟继承和虚基类等知识的详细说明,请参考《C++Primer》(E3)或《ThinkinginC++》(E2)中相关章节。•多继承在成员使用及向上映射时容易引发二义性问题,应尽量避免使用。•虚拟继承比非虚拟继承开销大,也应尽量避免使用。5.继承举例•示例程序08_05:给定一个线性表类list,分别派生出栈类stack和队列类queue.–线性表:元素依次呈线性结构(链表,数组都是线性表)–栈:操作受限的线性表,只能对栈顶的元素进行操作.–队列:操作受限的线性表.删除时只能删除队头;插入时只能插入到队尾....栈栈顶...队列队尾队头上机题•教材P251习题7-5,7-10,7-11.•仿照示例程序08_05,用数组实现线性表类array,从array中派生子类stack和queue,实现出栈,入栈,出队列,入队列等操作