1、chapter4第四章 继承性(inheritance)与类的派生(derivation)4.1 派生类(derived class)及其对象(object) 4.1.1 定义第三章中提到,在面向对象程序设计语言中,“类”是一组具有相同数据结构和相同操作(方法、函数)的集合,是一系列具有相同性质的对象的抽象内容,它描述的不是个别对象而是全体对象的共同特征。“类”是具有相同共性的各事物的集合,是这些事物的统一抽象内容。C+中,“类”被表达为一个具有特定功能的程序块,它提供代码共享(代码重用性),以便用户可以方便地建立所需要的任何数据类型。但一个“类”(基类)无法包含这些事物的全部共性,而只包含主
2、要共性。为包含其它次要共性,可通过继承机制使用其它类(称为派生类)。继承是C+面向对象程序设计的重要特性之一。所谓继承,是建立一个新的类(即直接派生类),从一个或多个已经定义的类(称为直接基类)中继承一部分或全部函数和数据,同时还能重新定义或增加新的数据和函数。继承机制在对象之间建立了派生关系,从而建立类的层次或等级(hierarchy of classes)。引入继承机制的目的是实现代码重用性(reusability):一方面可以重用先前的代码,避免不必要的重复设计;另一方面,如果原代码不能完全满足要求,可以在绝不改变原有代码的情况下,补充新的代码,增加新的功能。请见下图:圆形正方形平行四边
3、形矩形四边形三角形几何图形几何图形上图中,“几何图形”是基类,它包含了几何图形的一些基本共性。它的派生类“三角形”、“四边形”和“圆形”各自包含了本图形的一些次要共性。例如计算面积的表达式,这三个图形都不相同。而“四边形”又可派生出“正方形”、“平行四边形”和“矩形”,从而使它们各自具有不同属性。又例如,先有一个类为:class string int length; char *contents;public: string( ); int get_length( );过些时候,需要增加功能,设计一个新类:class edit_string int length; char *contents
4、; int cursor;public: edit_string( ); int get_length( ); int get_cursor_pos( );从中看出,这两个类的内容有不少重复。后一个类应该可以继承前一个类的全部或一部分内容(在后一个类中用红色标出)。可以将前一个类class string定义为基类(base class):然后另外定义一个类string_derived,称为派生类(derived class),如下:class string_derived : string int cursor;public: edit_string( ); int get_cursor_po
5、s( );这个派生出的新类具有以下特点(主要是前两点):1派生类是基类定义的延续 - 在派生类中可隐含地具有基类的任何成员。2派生类是基类定义的扩充 - 在派生类中可重新定义新的成员(在第五章中还将看到,可在派生类中改变或扩充基类成员函数的功能)。3派生类可以是基类的组合 - 派生类可由多个基类派生而来,此时派生类是所有基类的属性和行为的组合。这称为“可重用(可复用reusable)的软件构件”。其中基类称base class,派生类称derived class,父类称ancester class,子类称subclass。继承:从一个或多个先前定义过的类(称为直接基类)中接受全部或一部分数据或
6、函数(行为、操作),并且重新定义原有成员并补充定义新的成员,因而形成一个新的低层的类(称为直接派生类)。而该派生类又可用作更低层派生类的直接基类。这样就建立了类的层次(hierarchy of classes)可用有向无环图(Directed Acyclic Graph, DAG)来表示继承关系。DAGstringprivatestring_derived绝大部分书中的有向无环图(Directed Acyclic Graph, DAG)的箭头向上,但只有J.Grabe : Up and Running with C+书中箭头向下(而按照数据结构中”图”的定义箭头应向下)。本课中不用箭头而用连线
7、。以后还将看到其他箭头用于表示参数传递关系和构造函数调用顺序。4.1.2 对象的内存存储内容(存储形式)在建立对象之前,当用户程序被编译时,编译系统即为该程序分配内存数据空间(用于存储该类的静态数据)以及内存代码空间(用于存储成员函数)。如第二章中所述,一个对象被建立后,系统又为该对象分配内存栈区空间,用于存储每个对象的非静态数据。因此,用于存储对象数据成员的内存空间分为两部分:即内存栈区空间以及内存数据空间。当建立派生类的对象后,派生类对象的内存栈区空间中既包括基类部分的也包括派生类部分的所有非静态数据成员。这些数据成员中不管是否允许访问(详见本章4.2),都一律存放在派生类对象的内存栈区空
8、间中。内存栈区空间内的非静态数据成员的排列顺序是基类部分在前,派生类部分在后。如果有虚函数,则内存栈区空间内还包括虚指针VPTR,而该虚指针则指向内存数据空间内的虚函数地址表vtable。(详见第五章)应注意:非静态数据存放在内存栈区空间内、用new动态分配的数据存放在内存堆区空间内,以及静态数据和全局变量存放在内存数据空间内,这三类数据决不存放在同一个内存空间内。例1读取各对象(包括各派生类的对象)的长度及其栈区存储内容。/ obj_cont_3.cpp/ To show the sizes of the classes and the stored contents/ of the obj
9、ect of the derived class#include class base int x1;public: base (int a) x1=a; int read( ) return x1; / This is to show that it does not occupy any object space;class derive : base int x2;public: derive (int a, int b) : base( a ) x2=b; ;class grand : derive int x3;public: grand (int a, int b, int c)
10、: derive(a, b) x3=c; ;void main() cout size of class base is sizeof(base) endl; cout size of class derive is sizeof(derive) endl; cout size of class grand is sizeof(grand) endl; base p(7); derive der(5,10); grand gr(3, 6, 9), *ptr_gr; ptr_gr = &gr; int *ptr = (int *)ptr_gr; cout first part of gr is
11、* ptr+ endl; cout second part of gr is * ptr+ endl; cout third part of gr is * ptr endl;/* Results:size of class base is 4size of class derive is 8size of class grand is 12first part of gr is 3second part of gr is 6third part of gr is 9 */以上程序中,对象的栈区存储内容只是各对象的非静态数据。程序还显示出初始化列表的另一个用途:从派生类构造函数向基类构造函数传
12、递参数(此点下节将详述)。DAGbaseprivatederiveprivategrand各对象的栈区存储内容 对象p 对象der 对象grgr.base:x1=3p.x1=7der.base:x1=5gr.derive:x2=6der.x2=10gr.x3=94.1.3 继承中的初始化和支配规则在以上例子obj_cont_3.cpp中,派生类对象通过初始化列表对其基类部分实现初始化。再看一下该程序的片断内容:class base int x1;public: base (int a) x1=a; ;class derive : base int x2;public: derive (int
13、a, int b) : base( a ) x2=b; ;class grand : derive int x3;public: grand (int a, int b, int c) : derive(a, b) x3=c; ;其中class grand的构造函数的初始化列表derive(a, b)用于从class grand向class derive传递参数a和b,以便将class derive对象初始化。而class derive的构造函数的初始化列表base( a )用于从class derive向class base传递参数a,以便将class base对象初始化。这是初始化列表的重
14、要用途。对初始化列表的要求派生类构造函数的初始化列表及其直接基类的构造函数的参数表中的参数类型和数量必须完全相同。在进一步了解初始化列表之前,先看一下支配规则。支配规则(派生类成员支配基类同名成员)1.其它函数访问对象成员时,先访问本派生类对象中同名成员。2.如果派生类对象中没有该同名成员,则进而访问该对象的直接基类部分的同名成员。3.如果该对象的直接基类部分中仍然没有该同名成员,则不断上溯至该对象的间接基类部分中寻找,直至找到为止。4.如仍找不到,则出现编译错误。例1使用初始化列表(initialization table)将基类部分的非静态数据初始化/ initab_2.cpp#inclu
15、de class Xprotected: int i, j;public: X(int i, int j) X:i=i, X:j=j; void print() couti=i, j=jendl; ;class Y : public X int k;public: Y(int i, int j) : X(i, j) k = i * j; /初始化列表 void print() couti=i, j=j, k=kendl; ;class Z : Ypublic: Z(int i, int j) : Y(i, j) /初始化列表 void print() Y:print( ); /因coutk;不
16、行,将会如下出错: /error k : cannot access private member /declared in class Y /所以只能用Y:print( ) /详见本章4.2“ 派生类成员函数和其它函数/访问基类成员时的权限(访问控制表)” ;void main() X objx(5,10); Y objy(12, 24); Z objz(25, 50); coutX对象: ; objx.print(); coutY对象: ; objy.print(); coutZ对象: ; objz.print();/* Results:X对象: i=5, j=10Y对象: i=12, j
17、=24, k=288Z对象: i=25, j=50, k=1250 */按照支配规则,以上每个类对象调用print( )函数时,都只调用本类的函数。请参照下图。D A Gx(i, j) public 参y(i, j) 数 传privatez(i, j) 递栈区存储内容class x的对象objxobjx.i = 5objx.j = 10class y的对象objy class z的对象objzobjz.x:i = 25objy.x:i = 12objz.x:j = 50objy.x:j = 24objz.y:k = 1250objy.k = 288例2见以下本章4.4.1中例3 例3再看一下支
18、配规则的例子:/acc_mod_2_2.cpp#include class A void fa( ); ;class B : public A void fb( ); ;class C : public B void fc( ); ;void main( ) C objc; objc.fa( ); . 主函数想要调用fa( ),先到class C中、再到class B中、最后在class A中找到。在其它情况下,如还找不到,则程序出错。4.1.4 派生类对象为基类对象赋值由以上4.1.2例1程序obj_cont_3.cpp中看出,由于派生类对象既包括自身部分的非静态数据成员,又包括基类部分的非
19、静态数据成员,所以派生类对象的长度大于基类对象的长度。它所包含的基类部分的非静态数据成员可用于对基类的另一个对象的非静态数据成员赋值。见下例:例1各级派生类对象der和gr为基类对象p赋值/ obj_assign_1.cpp/ objects of derived classes of different levels / are used to assign member value to object of base class#include class base int x1;public: base (int a) x1=a; void show( ) coutx1endl; ;cl
20、ass derive : public base int x2;public: derive (int a, int b) : base( a ) x2=b; ;class grand : public derive int x3;public: grand (int a, int b, int c) : derive(a, b) x3=c; ;void main() base p(7); /(1) derive der(5,10); /(2) grand gr(3, 6, 9); /(3) p.show(); /(4) p = der; /(5)派生类对象向基类对象赋值 p.show();
21、/(6) p = gr; /(7)更下一级派生类对象向基类对象越级赋值 p.show(); /(8) /但反过来不行 /der = p; /error: binary = : no operator defined which takes a right-hand operand /of type class base (or there is no acceptable conversion)/* Results:753 */以上程序中 p = der; 和 p = gr;两条语句分别使用各派生类对象der和gr中的der.x1(=5)和gr.x1(=3)对基类数据成员x1赋值。不同赋值结果
22、分别为5和3。各级派生类对象的内存栈区空间的存储内容(简称栈区存储内容)见下图。各对象的栈区存储内容主函数前三句运行后(4.1.2例1中已有) 对象p 对象der 对象grgr.base:x1=3p.x1=7der.base:x1=5gr.derive:x2=6der.x2=10gr.x3=9主函数第五句“p=der;”运行后 对象p 对象der der.base:x1=5p.x1=5der.x2=10主函数第七句“p=gr;”运行后 对象p 对象grp.x1=3gr.base:x1=3gr.derive:x2=6gr.x3=94.4.2.1“调用顺序”中还有例2mul_inh_4.cpp供有
23、兴趣者参照。4.2 派生类成员函数和其它函数访问基类成员时的权限(访问控制表)4.2.1 类成员的访问控制(access control)(1)类成员的访问说明符(access specifier):默认值为私有,可声明为公有或保护;私有(private)成员的访问属性:只能供本类的成员函数和友员函数访问,不准其它函数访问;公有(public)成员的访问属性:可供任何函数访问;保护(protected)成员的访问属性:除与私有成员相同外,还允许派生类成员函数访问,但不准其它函数访问。重要注解此处其它函数系指主程序(主函数)、其他类的成员函数和普通函数等。(2)结构(struct)成员的访问控制
24、符:默认值为公有,可声明为私有或保护;(3)联合(union)成员的访问控制符:只能为公有。这也可用三句话表达:1.其它函数只能访问对象中公有成员。2.成员函数能够访问对象中所有成员。3.派生类成员函数不能访问对象中基类私有成员。例如:例1基类的访问控制情况/acc_mod_base.cpp#include class B int i; /privateprotected: int j;public: B(int a, int b, int c) i=a; j=b; k=c; void f( ) couti j kendl; ; int k;void main( ) B obj(1, 2, 3
25、); /coutobj.i; /not allowed /coutobj.j; /not allowed coutobj.kendl; obj.f( );/* Results:31 2 3 */其中class B的成员f( )能够访问所有成员 i, j, k,但主程序只能访问class B对象中的公有成员k和f( )。可将此访问权限列于以下表内。TABLE 1(class B的访问控制表) class namepublicprotectedprivateclass Bf( ), kji上述访问控制表内包含符合访问权限的数据成员和成员函数,也即: 本类(直接基类)的成员函数有权访问访问控制表内的
26、所有成员。派生类的成员函数只能访问具有public和protected属性的成员。而其他函数只能访问具有public属性的成员。与访问属性相同,派生(继承)方式也共有三种:(1)公有(public)继承方式(2)私有(private)继承方式(3)保护(protected)继承方式三种访问属性和三种派生(继承)方式形成了访问权限的九种不同组合,也就是说,成员的访问属性随着继承方式的不同而不同。下面详细分析。4.2.2 派生类对基类成员的继承已经知道,一个类中的成员函数能够访问同一类中的所有成员,无论是公有的或私有的都可以访问。试问:该基类的派生类中的成员函数也能够访问该基类中的所有成员吗?答案
27、不是一句话能包括的,因为这还决定于继承方式。 4.2.2.1 公有继承方式既然希望派生类中的成员函数能够访问尽量多的基类成员,当然希望应用公有继承方式。如下例:/ pub_inh_1.cpp/ To see if function of publicly-derived class can access base members#include class B int ib;protected: int jb; public: int kb; B(int i=1, int j=2, int k=3) ib=i; jb=j; kb=k; ;class D : public B /公有继承publ
28、ic: void fun_d( ) coutfun_d( ) in class D reads jb = jb, kb = kbendl; / cout ib; / error: ib : cannot access private member declared in class B /只能访问基类中保护和公有成员 ;class G : public D /公有继承的更下一层派生类public: void fun_g( ) coutfun_g( ) in class G reads jb = jb, kb = kbendl; ;void main() D objd; objd.fun_d( ); coutmain( ) reads objd.kb = objd.kb
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1