




已阅读5页,还剩81页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
从已有的对象类型出发建立一种新的对象类型,使它继承原对象类型的特点和功能,这种思想是面向对象设计方法的主要贡献。 通过对已有类进行特殊化(派生)来建立新的数据类型,就使得面向对象语言具有极大的能力和丰富的表现力。从概念上讲,类的派生创建了一种软件结构,它真实地反映了实际问题。从软件角度来看,类的派生创建了一种类族。派生类的对象也是基类的一种对象,它可以被用在基类对象所使用的任何地方。可以用多态成员函数仔细调整这种关系,以便使派生类在某些地方与它的基类一致,而在别的地方表现出它自身的行为特征。 本章主要讨论C+语言继承方面的语法特征和一般的使用方法。,第6章 继承和派生,主要内容,6.1 继承和派生的基本概念 6.2 单一继承 6.3 多重继承 6.4 二义性及其支配规则 6.5 典型问题分析,交通工具分类层次图的UML表示,交通工具,火车,汽车,飞机,轮船,卡车,旅行车,小汽车,工具车,轿车,面包车,类的派生: 这种通过特殊化已有的类来建立新类的过程,叫做“类的派生”。从类的成员的角度看,派生类自动地将基类的所有成员作为自己的成员,这叫做“继承”。 基类、派生类: 原有的类叫做“基类”,新建立的类则叫做“派生类”。 基类和派生类又可以分别叫做“父类”和“子类”,有时也称为“一般类”和“特殊类”。,类的派生和继承是面向对象程序设计方法和C+语言最重要的特征之一。 继承使得程序员可以在一个较一般的类的基础上很快地建立一个新类,而不必从零开始设计每个类。 从一个或多个以前定义的类(基类) 产生新类的过程称为派生,这个新类称为派生类。派生的新类同时也可以增加或重新定义数据和操作,这就产生了类的层次性。 类的继承是指新类继承基类的成员。继承常用来表示类属关系,不能将继承理解为构成关系。,当从现存类中派生出新类时,可以对派生类做如下几种变化: 可以增加新的数据成员; 可以增加新的成员函数; 可以重新定义已有的成员函数; 可以改变现有成员的属性。,如图6.1所示,C+中有两种继承:单一继承和多重继承。对于单一继承,派生类只能有一个基类;对于多重继承,派生类可以有多个基类。,图6.1 类的单一继承和多重继承的UML结构图,C+派生类从父类中继承性质时,可使派生类扩展它们,或者对其做些限制,也可改变或删除,甚至不作任何修改。所有这些变化可归结为两类基本的面向对象技术。 第一种称为性质约束,即对基类的性质加以限制或删除。第二种称为性质扩展,即增加派生类的性质。,派生类的定义格式,单继承的定义格式如下: Class : ; 多继承的定义格式如下: Class : , , ; 常使用如下三种关键字给予表示: public 表示公有继承 private 表示私有继承 protected 表示保护继承,派生类的三种继承方式,公有继承(public) 基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的 私有继承(private) 基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问 保护继承(protected) 基类的所有公有成员和保护成员都作为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的 系统的默认值是私有继承(private)。,基类和派生类的关系,任何一个类都可以派生出一个新类,派生类也可以再派生出新类 类A是类C的间接基类, 类B是类A的直接派生类 基类与派生类之间的关系:可复用的软件构件 派生类是基类的具体化 派生类是基类定义的延续 派生类是基类的组合,6.2.1 单一继承的一般形式 在 C+中,声明单一继承的一般形式为: class 派生类名:访问控制 基类名 private: 成员声明列表 protected: 成员声明列表 public: 成员声明列表 ;,6.2 单一继承,这里和一般的类的声明一样,用关键字class 声明一个新的类。 冒号后面的部分指示这个新类是哪个基类的派生类。所谓“访问控制”是指如何控制基类成员在派生类中的访问属性,它是3个关键字public, protected和private 中的一个。 一对大括号“ ”中是用来声明派生类自己的成员的。这和类的声明一样,不再赘述。,单继承,每一个类可以有多个派生类 每一个派生类只能有一个基类从而形成树形结构,成员访问权限的控制,公有继承public 私有继承private 保护继承protected,公有继承(public),公有继承方式创建的派生类对基类各种成员访问权限如下 : 基类公有成员相当于派生类的公有成员,即派生类可以象访问自身公有成员一样访问从基类继承的公有成员。 基类保护成员相当于派生类的保护成员,即派生类可以象访问自身的保护成员一样,访问基类的保护成员。 对于基类的私有成员,派生类内部成员无法直接访问。派生类使用者也无法通过派生类对象直接访问。,例 分析程序中的访问权限 #include class A public: void f 1( ); protected: int j1; private: int i1; ;,class B:public A public: void f2( ); protected: int j2; private: int i2; ; class C:public B public: void f3( ); ;,回答下列问题: 派生类B中成员函数f2( )能否访问基类A中的成员:f1( ), i1 和 j1呢? 派生类B的对象b1能否访问基类A中的成员:f1( ), i1 和 j1呢? 派生类C中成员函数f3( )能否访问直接基类B中的成员:f2( )和j2呢?能否访问间接基类A中的成员:f1( ), i1 和 j1 呢? 派生类C的对象c1能否访问直接基类B中的成员:f2( )和j2呢?能否访问间接基类A中的成员:f1( ), i1和j1呢? 从对(1)(4)问题的回答可得出什么结论?,Ans:,可以访问f1( )和j1,而不可以访问i1。 可以访问f1( ),而不可以访问j1和i1。 可以访问直接基类中的f2( )和j2以及间接基类中的f1( )和j1,而不可以访问i2和i1。 可以访问直接基类中的f2( )和间接基类中的f1( ),其它的都不可以访问。 在公有继承时,派生类的成员函数可访问基类中的公有成员和保护成员;派生类的对象仅可访问基类中的公有成员。,私有继承 (private),派生类对基类各种成员访问权限如下 : 基类公有成员和保护成员都相当于派生类的私有成员,派生类只能通过自身的函数成员访问他们 对于基类的私有成员,无论派生类内部成员或派生类使用者都无法直接访问。,例分析程序,回答问题,#include class A public: void f(int i)cout i endl: void g( ) cout “gn“; ; class B:A public: void h( ) cout “hn“; A:f; ;,void main( ) B d1; d1.f(6); d1.g( ); d1.h( ); ,回答下列问题: 执行该程序时,哪个语句会出现编译错?为什么? 去掉出错语句后,执行该程序后输出结果如何? 程序中派生类B是从基类A继承来的,这种缺省继承方式是哪种继承方式? 派生类B中,A:f的含义是什么? 将派生类B的继承改为公有继承方式该程序输出什么结果?,Ans:,1 d1.g( );语句出现编译错误,因为B是以私有继承方式继承类A的,所以B类的对象不可访问A类的成员函数。 2 d1.g( );语句注释后,执行该程序输出以下结果: 6 h 3 使用class关键字定义类时,缺省的继承方式是private。 4 A:f;是将基类中的公有成员说明为派生类的公有成员。 5 将class B:A改为class B:public A以后,输出如下: 6 g h,保护继承(public),保护继承方式创建的派生类对基类各种成员访问权限如下 : 基类的公有成员和保护成员都相当于派生类的保护成员,派生类可以通过自身的成员函数或其子类的成员函数访问他们 对于基类的私有成员,无论派生类内部成员或派生类使用者都无法直接访问,例 分析程序,回答问题,Include #include class A public: A(const char *nm)strcpy(name, nm); private: char name80; ; class B:public A public: B(const char *nm):A(nm); void PrintName( )const; ;,void B:PrintName( ) const cout “name“ name endl; void main( ) B b1(“wang li“); b1.PrintName( ); ,回答下列问题:,执行该程序将会出现什么编译错? 对出现的编译错如何在访问权限上进行修改? 修改后使该程序通过编译,执行执行该程序后输出结果是什么?,Ans:,1 编译时出错行是:cout“name:“nameendl; 错误信息提示name是私有成员不能访问。 2 在类A中,将private改写为protected。这样就可以通过编译。 派生类可访问基类的保护部分,并把它作为派生类的公有部分;但程序其他部分把name作为私有成员。例如在main中,不能运行 strcpy(s1,) 3 执行修改后的该程序输出如下结果: wang li,class C:public B public: void f3( ); ;,例 分析程序中的访问权限 #include class A public: void f 1( ); protected: int j1; private: int i1; ; class B:public A public: void f2( ); protected: int j2; private: int i2; ;,公有继承public 私有继承private 保护继承rotected,void main( ) B b1 b1.f1( ); b1.f2( ); ,6.2.2 构造函数和析构函数,1. 构造函数 派生类对象是由基类中说明的数据成员和派生类中说明的数据成员共同构成 基类中说明的数据成员和操作所构成的封装体称为基类子对象 派生类的构造函数必须通过调用基类的构造函数类初始化基类子对象 在定义派生类的构造函数时除了对自己的数据成员进行初始化外,还必须负责调用基类构造函数使基类的数据成员得以初始化。如果派生类中还有子对象时,还应包含对子对象初始化的构造函数。,在派生类中继承的基类成员的初始化,需要由派生类的构造函数调用基类的构造函数来完成,这和初始化对象成员有类似之处。 定义派生类的构造函数的一般形式为: 派生类名 : 派生类名(参数表0) : 基类名(参数表) ./函数体 ,派生类构造函数,派生类构造函数的格式如下: () : (),() ; 派生类构造函数的调用顺序如下: 基类的构造函数 子对象类的构造函数(如果有的话) 派生类构造函数,例,#include class A public: A( )a=0; cout“As default constructor called.n“; A(int i)a=i; cout“As constructor called.n“; A( )cout “As destructor called.n“; void Print( ) const cout a “,“; int Geta( )return a; private: int a; ;,class B:public A public: B( )b=0; cout “Bs default constructor called.n“; B(int i, int j, int k); B( )cout “Bs destrutor called.n“; void Print( ); private: int b; A aa; ; B:B(int i, int j, int k) : A(i),aa(j) b = k; cout “Bs constructor called.n“; ,void B:Print( ) A:Print( ); cout b “,“ aa.Geta( ) endl; void main( ) B bb2; bb0 =B(1,2,5); bb1 =B(3,4,7); for(int i=0; i2; i+) bbi.Print( ); ,Ans:,As default constructor called. As default constructor called. 构造函数 Bs default constructor called. As default constructor called. As default constructor called. 构造函数 Bs default constructor called. As constructor called. As constructor called. Bs constructor called. 赋值 Bs destructor called. As destructor called. As destructor called. As constructor called. As constructor called. Bs constructor called. 赋值 Bs destructor called. As destructor called. As destructor called. 1, 5, 2 3, 7, 4 Bs destructor called. As destructor called. As destructor called. Bs destructor called. As destructor called. As destructor called.,2. 析构函数,当对象被删除时,派生类的析构函数被执行 由于析构函数不能被继承,因此在执行派生类的析构函数时,基类的析构函数也将被调用 先执行派生类的析构函数,再执行基类的析构函数,例,#include class M public: M( )m1 = m2 = 0; M(int i, int j)m1 = i; m2 = j; void print( )cout m1 “,“ m2 “,“: M( )cout “Ms destructor called.n“; private: int m1, m2; ;,class N:public M public: N( )n = 0; N(int i, int j, int k); void print( )M:print( ); cout n endl; N( )cout “Ns destructor called.n“; private: int n; ; N:N(int i, int j, int k) : M(i,j), n(k) void main( ) N n1(5,6,7), n2(-2,-3,-4); n1.print( ); n2.print( ); ,Ans:,5, 6, 7 -2, -3, -4, Ns destructor called. Ms destructor called. Ns destructor called. Ms destructor called.,如何初始化派生类的对象呢?当然也应在派生类中声明一个与派生类同名的函数。假设从基类Point派生一个描述矩形的类Rectangle 。类Rectangle继承Point类的两个数据成员作为矩形的一个顶点,再为Rectangle类增加两个数据成员H 和W,分别描述它的高和宽。为类Point设计一个构造函数Point(int,int)和显示数据的函数Showxy。为Rectangle类也设计构造函数Rectangle(int,int, int, int)和显示函数Show。由此可见,派生类增加了两个新的数据成员以及相应的成员函数,同时继承Point的全部成员。 【例6.1】是它们的程序实现。 【例6.1】使用默认内联函数实现单一继承。 #include using namespace std;,6.2.2 派生类的构造函数和析构函数,class Point private: int x,y; public: Point(int a, int b)x=a; y=b; cout“Point.“endl; void Showxy()cout“x=“x“,y=“yendl; Point()cout“Delete Point“endl; ; class Rectangle : public Point private: int H, W;,public: Rectangle(int a, int b, int h, int w):Point(a,b) /构造函 /数初始化列表 H=h; W=w; cout“Rectangle.“endl; void Show()cout“H=“H“,W=“Wendl; Rectangle()cout“Delete Rectangle“endl; ; void main() Rectangle r1(3,4,5,6); r1.Showxy(); /派生类对象调用基类的成员函数 r1.Show(); /派生类对象调用派生类的成员函数 ,程序输出如下: Point. /调用基类构造函数 Rectangle. /调用派生类构造函数 x=3,y=4 /调用基类成员函数Showxy() H=5,W=6 /调用派生类成员函数Show() Delete Rectangle /调用派生类析构函数 Delete Point /调用基类析构函数 在派生类中继承的基类成员的初始化,需要由派生类的构造函数调用基类的构造函数来完成,这和初始化对象成员有类似之处。 定义派生类的构造函数的一般形式为: 派生类名 : 派生类名(参数表0) : 基类名(参数表) ./函数体 ,冒号后“基类名(参数表)”称为成员初始化列表,参数表给出所调用的基类构造函数所需要的实参。实参的值可以来自“参数表0”,或由表达式给出。可以像Rectangle那样,在类中直接定义为内联函数。下面是在类说明之外定义的示范: Rectangle:Rectangle(int a, int b, int h, int w):Point(a,b) H=h; W=w; cout“Rectangle.“endl; “参数表0”有4个参数,基类Point的参数表是自己的2个数据成员。 构造函数(包括析构函数)是不被继承的,所以一个派生类只能调用它的直接基类的构造函数。当定义派生类的一个对象时,首先调用基类的构造函数,对基类成员进行初始化,然后执行派生类的构造函数,如果某个基类仍是一个派生类,则这个过程递归进行。当该对象消失时,析构函数的执行顺序和执行构造函数时的顺序正好相反。输出结果也证实了这个结论。,现在修改Rectangle 的Show函数,使得它可以一次显示x 、y 、H 和W 。怎样修改成员函数Show呢?修改成下面的内容能实现这一目的吗? void Rectangle:show() cout “x=“ x “,y=“y “,H=“ H “,W=“Wendl; 这段简单程序并不能通过编译。类Rectangle有4个私有成员x 、y 、H 和W 。这4个私有成员的来源是不一样的。H 和 W是Rectangle 自己定义的,而x 和y 是从Point那里继承来的。 换句话说,x和y是类Point的私有成员。类的私有成员是只能被它自己的成员函数(不讨论友元)访问的,而Show函数是在类Rectangle 中定义的,它是类Point子类的成员函数,并不是类Point的成员函数,因而不能访问x 和y 。,6.2.3 类的保护成员,C+语言规定,公有派生类的成员函数可直接访问基类中定义的或基类(从另一个基类)继承来的公有成员,但不能访问基类的私有成员。这和私有成员的定义是一致的,符合数据封装思想。但这样也有问题,就拿上面的程序来说,在类Rectangle 看来,x 、y 、H 和W 的地位是平等的,现在希望对它们“一视同仁”,但C+语言关于私有成员继承的规定却妨碍这样做。为解决这一矛盾,C+引入了保护成员的概念。 在类声明中,关键字protected之后声明的是类的保护成员。保护成员具有私有成员和公有成员的双重角色:对派生类的成员函数而言,它是公有成员,可以被访问;而对其他函数而言则仍是私有成员,不能被访问。因此,要想在类Rectangle 中使用统一的Show函数,只要把x 和y 定义成类Point的保护成员就行了。【例6.2】是修改过的程序。,#include using namespace std; class Point protected: int x,y; public: Point(int a, int b)x=a; y=b; void Show()cout“x=”x“,y=”yendl; /基类的Show()函数 ; class Rectangle : public Point private: int H, W; public: Rectangle(int, int, int, int); /构造函数原型 void Show() cout“x=“x“,y=“y“,H=“H “,W=“Wendl; ;,【例6.2】演示使用protected成员。,/定义构造函数 Rectangle:Rectangle(int a, int b, int h, int w):Point(a,b) H=h; W=w; void main() Point a(3,4); Rectangle r1(3,4,5,6); a.Show(); /基类对象调用基类Show()函数 r1.Show(); /派生类对象调用派生类Show()函数 程序还演示了在类体内声明Rectangle构造函数原型,类体外定义它。派生类虽然继承了基类的成员函数Show,但它改造了这个函数,使它能显示所有数据。这并不会影响基类函数原来的功能。程序有意定义了Point的对象a,执行a.Show()验证之。 程序输出如下: x=3,y=4 x=3,y=4,H=5,W=6 为了将来还可以派生这种新类,建议把H 和W 也定义成保护成员。然后就可以放心大胆地使用统一的Show函数了。,1. 公有派生和赋值兼容规则 在前面的例子中,使用了公有派生。在公有派生的情况下,基类成员的访问权限在派生类中保持不变。这就意味着在程序中: 基类的公有成员在派生类中仍然是公有的。 基类的保护成员在派生类中仍然是保护的。 基类的不可访问的和私有的成员在派生类中也仍然是不可访问的。 所谓“不可访问”,是说一个成员甚至对于其自身所在类的成员来说也是不可访问的。这似乎有点儿难以理解,但在类Rectangle 中已经遇到过了,x 和y 就是不可访问的。在根类(不是从别的类派生出来的类)中,没有成员是不可访问的。对于根类来说,可能的访问级别是 private 、public和protected 。但是在派生类中,可以存在第4种访问级别:不可访问(inaccessible)。,6.2.4 访问权限和赋值兼容规则,不可访问成员总是由基类继承来的,要么是基类的不可访问成员,要么是基类的私有成员。因此,在公有派生的情况下,可以通过定义派生类自己的成员函数来访问派生类对象继承来的公有和保护成员,但是不能访问继承来的私有成员。这一点很重要,当希望类的某些成员能够被子类所访问,而又不能被其他的外界函数访问的时候,就应当把它们定义为保护的,上一节就是这样做的。千万不能把它们定义成私有的,否则在子类中它们就会是不可访问的。事实上,当这样做了以后,“每一只黑狗都是狗”,每一个派生类的对象,都是基类的一个对象,于是可以得出赋值兼容规则:所谓赋值兼容规则是指在公有派生情况下,一个派生类的对象可以作为基类的对象来使用的情况。 约定类derived 是从类base公有派生而来的,则指如下3种情况:,赋值兼容规则:所谓赋值兼容规则是指在公有派生情况下,一个派生类的对象可以作为基类的对象来使用的情况。 约定类derived 是从类base公有派生而来的,则指如下3种情况:, 派生的对象可以赋给基类的对象。例如: derived d; base b; b = d; 派生类的对象可以初始化基类的引用。例如: derived d; base 但要注意,在后两种情况下,通过pb或br只能访问对象d 中所继承的基类成员。下面使用【例6.2】定义的类,编写一个主程序来演示赋值兼容规则并说明这一问题。,【例6.3】使用Point和Rectangle类演示赋值兼容规则的例子。 void main( ) /表演公有继承的赋值兼容性规则 Point a(1,2); /基类对象a Rectangle b(3,4,5,6); /派生类对象b a.Show(); b.Show(); Point /派生类对象的地址赋给指向基类的指针,p-Show(); /实际调用的是基类的Show函数 Rectangle* pb= 程序输出如下: x=1,y=2 /用a的Show函数输出对象a的数据成员 x=3,y=4,H=5,W=6 /用b的Show函数输出对象b的 /数据成员 x=3,y=4 /用b的基类Show函数输出其基类数据成员,x=3,y=4 /用b的基类Show函数输出其基类数据成员 x=3,y=4,H=5,W=6 /b的指针调用Show函数输出对 /象b的数据成员 x=3,y=4 /用a的Show函数输出对象a的数据成员 以指针为例,为什么“p-Show();”不是使用b的Show函数输出“x=3,y=4,H=5,W=6”?因为“base* pb = ”,从输出结果可以看出,它们的含义就是基类用派生类的属性值代替自己原来的属性值。这就是公有派生的“isa”原则。,2. “isa”和“ has-a”的区别 类与类之间的关系有两大类。一是继承和派生问题,二是一个类使用另一个类的问题。后者的简单用途是把另一个类的对象作为自己的数据成员或者成员函数的参数。 继承首先要掌握公有继承的赋值兼容规则,理解公有继承“就是一个(isa)”的含义。如果写成类B(子类 )公有继承于类A(父类),在可以使用类A(父类)的对象的任何地方,则类B的对象同样也能使用,因为每一个类B的对象“就是一个”类A的对象。另一方面,如果需要一个类B的对象,则类A的对象就不行:每个B都是A,但反之则不然。C+强制执行公有继承的这种规则。例如: class Person .; class Student : Public Person .; 这两个类所断言的是:每个学生都是人,但并不是每个人都是学生。可以期望任何一件对于人来说是真实的事情,对学生也是真实的,例如他或她都有生日,对于学生同样也是真实的。但却不能期望每一件对于学生来说是真实的,事情,对所有的人都是真实的。譬如说他或她就读于一所指定的学校,这对于一般的人就不能都是真实的。“人”的概念要比“学生”的概念来得更广泛些;而“学生”则是一种特殊类型的“人”。 在C+范围内,任何一个要求提供Person类型(或指向Person的指针及引用)参数的函数,也能够使用Student对象(或指向Student的指针及引用)作为参数。例如: void dance(const Person / 对,s是Student,student也是(isa)Person,study(s); / 对 study(p); / 错! p不都是Student 这只是对公有继承才是正确的。仅当Student类是从Person类中公有地派生出来的时候,C+才具有像上面所描述的那种性质。 公共继承和“isa”的等价性看起来很简单,但在实际应用中并不容易。有时自己的直觉会产生误导。譬如说,企鹅是鸟是一件事,鸟会飞是另一件事。企鹅是鸟,但不能从鸟会飞误导出企鹅会飞。 如果已有一个address类,它描述地址这个概念,现在要建立一个worker类,它描述职工这个概念,每个职工都有一个住址,worker类可以有两种定义形式:使用继承或对象成员。如果采用继承,可以按如下形式定义:,class worker:public address / ; 如果在worker类中定义一个address类的类对象成员,可以按如下形式定义: class worker private address workerAddr; / ; 使用继承的方法是说职工是一个地址,使用对象的方法声称职工包含有一个地址属性。这两种说法中后者的说法是正确的,即地址只能作为职工的一个属性,在概念上,它们之间没有联系,所以上例中第2种的描述形式是合理的。这就是分层实现。,由此可见,类用于描述一类对象的共同特性,不同种类的对象之间的联系使用继承来表示,对象所具有的属性使用类的成员来表示。分层就是一种处理过程,它通过让分层的类里包含被分层的类的对象作为其数据成员,以便把一个类建立在另一些类之上。例如: class String ; / 字符串 class Address ; / 某人居住的地方 class PhoneNumber; class Person private: String name; / 被分层的对象 Address address; / 被分层的对象,PhoneNumber voiceNumber; / 被分层的对象 PhoneNumber faxNumber; / 被分层的对象 public: ; 在这个例子中,Person类要被分层到String、Address和PhoneNumber这几个类的上面,这是因为它里面含有这些类型的数据成员。分层也可以叫做包含、嵌入或者聚合。公有继承的意思是“isa”。与此相反,分层的意思是指“has-a(有一个)”或者“is-implemented-in-terms-of(是按实现的)”。上面的Person类表示的是一种“has-a”的关系。一个Person对象有一个名字、一个地址和用于语音及传真通信的两个电话号码。我们不会说一个人就是一个字符串,或者一个人就是一个地址,但会说一个人有一个字符串,并且有一个地址等。,许多人做这种分辨时困难不大,搞不清“isa”和“has-a”之间区别的人相对来说也很少见。稍微有些伤脑筋的是“isa”和is-implemented-in-terms-of之间的差异,这可通过例子来加深理解。在6.5节中,将给出分别使用这两种方法设计的例子。 3. 公有继承存取权限表 派生类一般都使用公有继承。使用基类的有基类本身、派生类、对象和外部函数,对派生类而言,使用它的有派生类本身、对象和外部函数。 类中可以使用三种成员,表6.1总结了它们之间的关系。,由此可见,保护类型的成员(数据成员和成员函数)介于私有和公有之间。对派生类来讲,它的作用与public成员一样。对类的对象、外部函数以及不属于本类系之外的类来说,它与private成员一样,均是不可访问的,从而保持了类的封装性。 “访问控制”决定着基类各成员在派生类中的访问权限,上面讨论了最常用的公有继承方式,下面将讨论private 和protected继承方式。,4. 私有派生 通过私有派生,基类的私有和不可访问成员在派生类中是不可访问的,而公有和保护成员这时就成了派生类的私有成员,派生类的对象不能访问继承的基类成员,必须定义公有的成员函数作为接口。更重要的是,虽然派生类的成员函数可通过自定义的函数访问基类的成员,但将该派生类作为基类再继续派生时,这时即使使用公有派生,原基类公有成员在新的派生类中也将是不可访问的。下面就来看一个私有派生的例子。 【例6.4】 私有派生的类继续派生的例子。,#include using namespace std; class Point /基类定义 private: int x,y; public: Point(int a, int b)x=a; y=b; void Show()cout“x=“x“,y=“yendl; ; class Rectangle : private Point/派生类定义 private: / 新增私有数据 int H, W; public: /新增外部接口 Rectangle(int a, int b, int h, int w):Point(a,b) H=h; W=w;,void Show()Point:Show(); cout“H=“H“,W=“Wendl; ; class Test: public Rectangle public: Test(int a, int b, int h, int w):Rectangle(a,b,h,w) voidShow()Rectangle:Show(); ;,在这个例子中,基类的公有成员函数Show通过私有派生成了派生类的私有成员函数,Test虽然是公有派生,但它已经无法使用基类的Show函数,即不能通过“Point:Show();”方式使用Point类的Show函数,这就彻底切断了基类与外界的联系。私有派生的这一特点不利于进一步派生,因而实际中私有派生用得并不多。,5. 保护派生 派生也可以使用protected。这种派生使原来的权限都降一级使用:private变为不可访问;protected变为private;public变为protected。因为限制了数据成员和成员函数的访问权限,所以用得较少。它与private继承的区别主要在下一级的派生中。如果将上例Rectangle改为保护继承方式,则在Test类中可以使用基类的Show函数,则下面函数的定义是正确的: class Test: public Rectangle public: Test(int a, int b, int h, int w):Rectangle(a,b,h,w) void Show()Point:Show(); Rectangle:Show(); /可 /以使用基类Point的成员 ;,protected 成员举例,int main() A a; a.x=5; /错误! B b; b.x=5; /错误! b. Function(); ,class A protected: int x; class B: public A public: void Function(); ; void B:Function() x=5; ,一个类从多个基类派生的一般形式是: class 类名1:访问控制 类名2, 访问控制 类名3 , ., 访问控制 类名n ./ 定义派生类自己的成员 ; 类名1继承了类名2到类名n的所有数据成员和成员函数,访问控制用于限制其后的类中的成员在类名1中的访问权限,其规则和单一继承情况一样。多重继承可以视为是单一继承的扩展。 【例6.5】演示多重继承的例子。,6.3 多重继承,#include using namespace std; class A private: int a; public: void setA( int x )a=x; void showA( )cout “a=“ a endl; ; class B private: int b; public: void setB( int x ) b = x; void showB ( ) cou “b=“ b endl; ;,class C : public A, private B private: int c; public: void setC(int x, int y ) c=x; setB(y); void showC( ) showB( ); cout “c=“c endl; ; void main( ) C obj; obj.setA(53); obj.showA( ); /输出a=53 obj.setC(55,58); obj.showC( ); /输出b=58 c=55 ,类C从类A公有派生,因此,类A的公有成员(保护成员)在类C中仍是公有的(保护的)。类C从类B私有派生,类B的所有成员在类C中是私有的。这些成员在派生类中的可访问性和单一继承中讨论的一样。 类B被私有继承,因此,类C负责维护类B的数据成员值和显示,所以在showC和setC中分别调用类B的成员函数showB和setB。使用obj.setB(5)和obj.showB( )都是错误的。,6.4.1 二义性和作用域分辨符 对基类成员的访问必须是无二义性的,如使用一个表达式的含义能解释为可以访问多个基类中的成员,则这种对基类成员的访问就是不确定的, 称这种访问具有二义性。,6.4 二义性及其支配规则,C类的成员函数hunc访问func时,无法确定是访问基类A还是基类B,出现二义性。使用A : func( )或B : func( )可以解决这种二义性。,【例6.6】 访问具有二义性的例子。 #include using namespace std; class A public: void func( ) cout“a.func“endl; ;,class B public: void func( ) cout“b.func“endl; Void gunc( ) cout“b.gunc“endl; ; class C : public A, public B public: void gunc( ) cout“c.gunc“endl; void hunc( )func(); / 具有二义性 ;,void main( ) C obj; obj.gunc(); / 不具有二义性,子类覆盖父类 ,下面是正确的派生类C的实现方法: class C : public A, public B public: void gunc( )cout“c.gunc”endl; void hun1( )A:func(); / 使用基类A的func void hun2( )B:func(); / 使用基类B的func ; 不过,程序仍然含有二义性,如用C类的对象obj访问函数func,则具有二义性: obj.func( ); / 不能确定是A的func还是B的func 使用成员名限定可以消除二义性,例如: obj.A : func( ); / A的func obj.B : func( ); / B的func obj.gunc( ); / C的gunc obj. B :gunc( ); / B的gunc,void main( ) C obj obj
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025大蒜电商B2C平台供应链服务合同
- 2025年度绿色生态住宅项目认筹协议书
- 2025版石矿开采安全生产责任追究与赔偿承包合同
- 2025版淘宝店铺跨境贸易合作协议范本
- 2025版科技园区前期物业服务管理协议
- 2025版水渠施工合同履约保证金合同
- 2025版酒店员工培训与绩效管理合同范本下载
- 2025版涂料施工劳务分包合同(含施工安全)规范文本
- 2025年印刷企业委托加工印刷品采购合同
- 2025版肉类产品电商平台用户数据保护合同
- 环境卫生学监测及采样方法介绍(院感培训)
- 餐饮行业油脂废物处理应急预案
- 鞋厂品质管理
- 船舶安全经验分享
- 内部控制基础性评价工作方案(3篇)
- 《制造业成本核算》课件
- 刘润年度演讲课件20241026
- 《植物种植要领》课件
- 分子生物学课件第一章医学分子生物学绪论
- 某港池航道疏浚和吹填造陆工程施工组织设计
- 质量为纲-华为公司质量理念与实践
评论
0/150
提交评论