C++教程第08章_继承与派生-1-4

上一篇 / 下一篇  2011-01-26 22:18:51 / 个人分类:C++

第8章 继承与派生

 


    8-1. 教学目的与要求

1.理解继承的概念;

2.掌握继承的实现方法;

3.继承中常见问题的处理方法。

    

      8-2.  主要内容及重点:

类是C++中进行数据封装的逻辑单位。继承是面向对象程序设计的一个重要特征之一,它允许在既有类的基础上创建新的类,新类可以从一个或多个既有类中继承操作和数据,而且可以重新定义或加进新的数据和操作,从而形成类的层次或等级。既有类称为基类或父类,在它基础上建立的新类称为派生类、导出类或子类。

本章的重点是派生类的定义和使用、创建派生类对象时构造函数的调用顺序、多重继承中的冲突及其消除方法以及作用域运算符的几种使用方法等。

本章的难点是基类的初始化、多重继承中的冲突以及虚基类等。

 


8-3. 第8章 继承-课件   

 

 


3-4.   8-4. 第8章 继承-复习总结

  继承是面向对象程序设计方法的基本特性之一,继承可以提高软件的重要性。

   本章主要介绍了C++中的继承性的实现方法以及在继承性常见问题的处理方法。包括基类的初始化、访问类型的调整、冲突及其消除方法、成员覆盖、赋值兼容性以及虚基类等。

   类的派生包括三种类型:私有派生、保护派生、公有派生。利用构造函数的初始化成员列表,可以在派生类中对基类成员进行初始化。在继承层次关系中要避免冲突的产生,通常是采用指定作用域和定义虚基类这两种方法来解决问题。

 

 


8-5. 第8章 继承-练习

8-5-1.思考题:

1).派生类有几种方式,每种方式中派生类对基类成员的继承如何?

2). 在创建派生类对象时,构造函数的执行次序是怎样的?析构函数的执行次序是怎样的?

3). 派生类对象如何对基类中的成员进行初始化?

4). 在多重继承中,在什么情况下会产生冲突?如何消除冲突?

5). 列举我们已学习的作用域运算符“::”的所有用途。

6). 属于不同类的对象在什么情况下可以互相赋值?

7).什么叫虚基类?为什么要引进虚基类?

 

8-5-2.练习题:

   课后练习题见练习单元。

 

 

 

 


第8章 继承与派生

 

类是C++中进行数据封装的逻辑单位。继承是面向对象程序设计的一个重要特征之一,它允许在既有类的基础上创建新的类,新类可以从一个或多个既有类中继承操作和数据,而且可以重新定义或加进新的数据和操作,从而形成类的层次或等级。既有类称为基类或父类,在它基础上建立的新类称为派生类、导出类或子类。

8.1 继承与派生的基本概念

交通工具

汽车

小汽车

卡车

游行车

工具车

轿车

面包车

                  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

8.1.1单继承和多继承:

单继承:一个类继承一个一般类特性的继承称为单继承。

多继承:一个类可以继承多个一般类的特性,然后再在继承来的这些一般类的基础上增加自己的特殊性,这种继承方式称为多继承。

 

 如:一个助教博士,他既是一个学生,又是一个老师,如果从学生类和老师类两个一般类继承特性,则助教博士类可以获得这两个类的特性,因而能进一步增强开发效率。

baseClass

derived A

derived B

单继承

baseClass A

derived C

baseClass B

多继承

 

 

 

 

 

 


单继承是所有面向对象程序设计语言都具有的功能,但并不是所有的语言都支持多继承,这是由多继承实现的复杂性决定的。C++语言支持多继承。

 

8.1.2基类和派生类:

在定义类B时,如果它使用了一个已定义的类A的部分或全部成员,则称类B继承了A,或由类A派生出类B。并称类A为基类或父类,类B为派生类或子类。

     一个派生类又可以作为另一个类的基类,这样一个基类可以直接或间接派生出若干个派生类,构成树状的继承关系。

     但是注意:继承关系不可循环。如:A继承B,B继承,C又继承A,这是非法的。

例类Y继承类X的特性,类Z又继承类Y的特性,从而间接继承来类X的特性。因而类X是类Y的直接基类,是类Z的间接基类,类Y是类X的直接派生类,类Z是类X的间接派生类。类X、Y、Z形成了一个类层次。

如前图.

 

8.1.3 两个类之间要具有继承关系,它们通常满足:

1)有共同的属性或操作。

2)有细微的差别。

 

8. 2 派生类说明及其构造函数和析构函数

8.2.1 派生类的说明

从一个基类派生出子类的一般格式为;

class <派生类名>:<Access派生方式或访问属性><基类名>

{

…   //类体,派生类新增的数据成员和成员函数    

} ;

其中:

1) class 是类定义的关键字,用于告诉编译器下面定义的是一个类。

2)派生类名是新定义的类名。

3)访问属性是访问说明符,可以是private、public、protected之一。此项的默认值为private,派生类名和访问属性之间用冒号隔开。派生类的访问控制由访问属性来确定,它按下述方式来继承基类的访问属性。

①.公有派生

  如果访问属性是为public,则基类的public成员是派生类的public成员;基类的protected成员是派生类的protected成员;基类的private成员是派生类的private成员;即基类的private成员对派生类仍保持private属性。

   显然,派生类中通过公有派生得到的成员还可以被它的子类继承。

②.私有派生

如果访问属性为private,则基类的public和protected成员都是派生类的private成员;这些私有成员能够被派生类的成员函数直接访问;但在派生类之外不可以被直接访问。但基类的private成员对派生类仍然保持private属性,即不能被派生类成员函数访问。总之当访问属性为private时,派生类的对象不能访问基类中以任何方式定义的成员函数。

     显然通过私有派生得到的派生类再派生子类时,其继承得到的基类成员不能被它的子类所继承。

③.保护派生

如果访问属性为protected,则基类的public和protected成员均是派生类的protected成员;基类的private成员对派生类仍保持private属性。具体来说,基类中声明为protected的数据只能被基类的成员函数或其派生类的成员函数访问;不能被派生类以外的成员函数访问。

对保护派生,基类中的公有成员和保护成员在派生类中均变为保护成员,它们仍然可被它的子类所继承。

4)值得注意的是,C++规定派生类只能访问基类中的公有成员和保护成员,而不能访问类中的私有成员。即

无论哪种派生方式,基类中的私有成员既不允许外部函数访问,也不允许派生类中的成员函数访问,但是可以通过基类提供的公有成员函数访问。

5)公有派生与私有派生的不同点在于基类中的公有成员在派生类中的访问属性。

 ●公有派生时,基类中的所有公有成员在派生类中也都是公有的。

 ●私有派生时,基类中的所有公有成员只能成为派生类中的私有成员。

6)基类名可以有一个,也可以有多个。如果只有一个基类,则这中继承方式叫做简单继承,也叫单继承;如果基类名有多个,则这种继承方式称为多重继承。各个基类名之间用逗号隔开。

7) 在派生类中声明的名字支配基类中声明的同名的名字,即如果在派生类的成员函数中直接使用该名字的话,则表示使用派生类中声明的名字。

8)在C++中下列成员不能继承:

  构造函数和析构函数、友元关系、重载的new和delete运算符。

 

8.2.2  多重继承

 由多个基类派生出一个子类的一般格式为:

 class <类名>:<Access派生方式1><类名1>,<Access2><类名2> - - - <Access N><类名n>

{

---  //类体,派生类新增的数据成员和成员函数

} ;

冒号后面的部分称基类表,各基类之间用逗号分隔,派生方式规定了派生类从基类中按什么方式继承,缺省的派生方式是private。

任一基类在派生类中只能被继承一次。

例:

     class z :public x ,y {   //类z公有继承了类x ,私有继承了类y

        //...

      } ;

    class z :: x , public y {   //类z私有继承了类x ,公有继承了类y

    //...

    };

    class z::public x ,public y {    //类z公有继承了类x和类y

     //...

    };

 

8.2.3 派生类的构造函数和析构函数

即基类的初始化

由于派生类继承了基类的一些成员,在创建派生类对象时,对派生类中的数据成员和基类中的数据成员都必须进行初始化,从基类中继承的数据成员可以直接使用基类的构造函数来完成初始化,而新增加的数据成员的初始化要由自己的构造函数来完成。在撤消派生类对象时,也要通过基类的析构函数对派生类中继承得到的数据处理。

1.C++规定,在创建派生类对象时,首先调用基类构造函数,然后再调用派生类构造函数。当撤消派生类对象时,析构函数的调用次序与构造函数的调用次序相反。

2.初始化基类成员通常通过派生类的构造函数实现,一般格式为:

<派生类名>::<派生类构造函数名>(<args>):<基类名1>(<args1>),--- ,<基类名n>(<args n>)

{

- - -   派生类的构造函数

}

 其中:

  1)<args>是派生类构造函数的形参表,它可以由任意类型的变量组成。

  2)冒号后列举的部分为初始化成员列表,在初始化成员列表中,一个基类名只能出现

一次。

  3)当初始化成员列表中某一个基类的构造函数的实参表为空时,可以将该构造函数

从初始化列表中删除。

  4)<args1>、- - - 、<args n>分别为调用相应基类构造函数的实参表,其必须与相应构造函数的形参表一致。实参表中的参数可以为任意合法的表达式,该表达式中可以包含<args>中的参数。

  5)如果基类中没有默认参数值的构造函数,则必须在派生类的构造函数中通过上述方式向相应的基类的构造函数传递参数。

6)构造函数的调用顺序只与派生类继承的基类的顺序有关,而与初始化成员列表中构造函数顺序无关,与派生类中参数的顺序也无关。

 

7) 如果基类A的派生类为B,B又派生了C,则在对B进行初始化之前,必须对它的基类A进行初始化,通常是通过B的构造函数实现。但是不能通过C的构造函数实现。即一个类的构造函数只能对它的直接基类的构造函数传递实参。对于更多层继承关系,都可按这种方式递归进行。

8 )如果派生类中包含对象成员,则创建派生类对象时,C++规定构造函数的调用次序为:

   基类的构造函数、对象成员的构造函数、派生类的构造函数。

   如果,此时基类中有对象成员,调用基类的构造函数之前还要先调用基类对象成员的构造函数,调用次序与前述类同。

   在初始化成员列表中,对象成员采用的是对象名,而对基类的初始化使用的是基类的类名,即基类的构造函数名。

调用顺序如下图所示:

 

 

 

基类的基类的构造函数

基类对象成员的构造函数

基类的构造函数

派生类对象的构造函数

派生类的构造函数

       

 

 

 

 

 

 

 

 

 

 8.2.4    总结

派生方式(基类的被继承方式)  在基类中的存取权限   在派生类中的存取权限       

==================================================

       public                   public                    public

       public                   potected                  protected

    public                   private                  (inaccessible)

       potected               public                    potected

       potected                potected                 protected

       potected                private         (inaccessible)

       private                   public                    private

       private                   potected                  private

       private                   private         (inaccessible)

==================================================

 

派生类中可出现四种成员:

      1) 不可访问的成员 -- 基类的private私有成员被继承过来后,这些成员在派生类中是不可访问的。 

      2) 私有成员 -- 包括在派生类中新增加的private私有成员以及从基类私有继承过来的某些成员。这些成员在派生类中是可以访问的。   

      3) 保护成员 -- 包括在派生类中新增加的potected保护成员以及从基类继承过来的某些成员。这些成员在派生类中是可以访问的。 

      4) 公有成员 -- 包括在派生类中新增加的public公有成员以及从基类公有继承过来的基类的public成员。这些成员不仅在派生类中可以访问,而且在建立派生类对象的模块中,也可以通过对象来访问它们。

 

 

8.2.5  例题

(8.2.1  派生类的说明)例题

1.公有派生

  如果访问属性是为public,则基类的public成员是派生类的public成员;基类的protected成员是派生类的protected成员;基类的private成员是派生类的private成员;即基类的private成员对派生类仍保持private属性。

   显然,派生类中通过公有派生得到的成员还可以被它的子类继承。

例1:公有派生中各成员的访问权限

# include <iostream.h>

class CBase

{     int x ;

protected  ;

      int y  ;

public:

      int  z ;

      CBase (int a , int b , int c) {x=a ;y=b; z=c;}

int Getx (void )  {return x ;}

int Gety (void )  {return y ;}

void ShowBase (void)

{ cout<<”x=”<<x<<” \ty=”<<y<<”\ t z=”<<z<<endl ;}

};

class CDerived :public CBase

{   int Length ,Width ;

public :

CDerived ( int a , int b ,int c ,int d , int e) :CBase(a,b,c)  //A 表示派生类的构造函数调用

//基类的构造函数,从而对继承得到的基类的成员进行初始化。见11.2节。

{ Lengh=d ; Width=e  ;}

void Show (void )

{  cout<<”Length=”<<Length<<”\tWidth=”<<Width<<endl ;

cout<<”x=”<<Getx( )<<”\ t y=”<<y<<”\t z=”<<z<<endl ;    //B,x是私有的,不能被继承。

}

int Sum (void)

{ return Gex ( )+y+z+Length+Width ; }

};

void main ( void )

{ CDerived d1 (1 ,2,3,4,5) ;

d1.ShowBase( )  ;             //E,公有成员函数,可以被继承为公有。

d1.show ( ) ;

cout<<”sum=”<<d1.Sum()<<’\n” ;

cout<<”y=”<<d1.Gety( )<<’\n’ ;   //C ,y是保护成员,不能被外部访问。

cout<<”z=”<<d1.z<<’\n’ ;         //D,z是公有成员,可以直接访问。

}

执行结果:

x=1    y=2   z=3

Length=4        Width=5

x=1    y=2   z=3

Sum=15

y=2

z=3

2.私有派生

如果访问属性为private,则基类的public和protected成员都是派生类的private成员;这些私有成员能够被派生类的成员函数直接访问;但在派生类之外不可以被直接访问。但基类的private成员对派生类仍然保持private属性,即不能被派生类成员函数访问。总之当访问属性为private时,派生类的对象不能访问基类中以任何方式定义的成员函数。

     显然通过私有派生得到的派生类再派生子类时,其继承得到的基类成员不能被它的子类所继承。

例2: 私有派生示例

# include <iostream.h>

class CBase

{  protected :

      int a ;

public :

      int  b ;

      void Set (int x ,int y)

{a=x ; b=y ;}

void Show ( void )

{cout<<a<<’\t’<<b<<endl ; }

};

class CDerived :private CBase

{

public:

      void SetD(int x , int y)

             {Set (x , y) ;}

void ShowD(void)

{cout<<a<<’\t’<<b<<endl ;}

} ;

void main ( )

{ CBase b1 ;

CDerived d1 ;

b1.Set (1 ,2) ;

b1.Show( ) ;

d1.SetD(10 ,100 ) ;   //若d1.set(10 , 100) 则错误,因为set()被私有继承,不能被访问

d1.ShowD( ) ;       // d1.Show ( ) 则错误,同上

}

执行结果:

1     2

10    100

 

假如,还有一个类CCD由派生类CDerived派生出来,定义如下:

class CDD :public Cderived

{

---  //类体

}

则CDD的类体中的成员不能访问CDerived从基类CBase私有继承来的成员,因为这些成员是类CDerived的私有成员,他们不能被类CDD所继承。当然对类CDerived中的保护成员和公有成员在类CDD中仍然可以被访问。

3.保护派生

如果访问属性为protected,则基类的public和protected成员均是派生类的protected成员;基类的private成员对派生类仍保持private属性。具体来说,基类中声明为protected的数据只能被基类的成员函数或其派生类的成员函数访问;不能被派生类以外的成员函数访问。

    对保护派生,基类中的公有成员和保护成员在派生类中均变为保护成员,它们仍然可被它的子类所继承。

例3  # include<iostream.h>

   class base

{   protected :

     int  a  ,b ;

   public:

     void setab (int n ,int m)

      {a=n ; b=m;}

    };

     class derive :public base

{

       int c ;

      public:

        void setc (int n)

       {c= n;}

      void showabc()

      {cout<<a<<' '<<b<<' '<<c<<endl;}   //允许派生类成员函数访问保护成员a和b

      };

     main()

    {

    derive obj ;

    obj.setab(2,4);

    obj.setc(3);

    obj.showabc();

    return 0 ;

    }

执行结果:

2 4 3

Press any key to continue

例4 私有继承方式的保护成员

   # include<iostream.h>

    class base

{  protected:

       int a ;

     public:

       void seta(int sa)

         {a=sa ;}

};

    class derive 1:private base

 {  protected :

       int b ;

     public:

       void setb(int sb)

       {b=sb ;}

    };

     classs derive2:public derive1

{      int c ;

      public:

        void setc(int sc)

        {c=sc;}

      void show()

      {

       cout<<"a="<<a<<endl;      // 非法

       cout<<"b="<<b<<endl ;      //合法

       cout<<"c="<<c<<endl ;      //合法

       }

     };

      void main()

      { base  op1 ;

       op1.seta(1) ;

       derive1 op2 ;

       op2.setb(2 ) ;

       derive2 op3 ;

       op3.setc(3 ) ;

       op3.show ( )

     }

 

(8.2.3   多重继承及派生类的构造函数和析构函数) 例 题

例5 派生类中的构造函数和析构函数的调用顺序

# include<iostream.h>

class CBase1

{  int x ;

public:

CBase1(int a)

{x=a ;cout<<”调用了基类1的构造函数!\n” ;}

~CBase1 ( void )

{ cout<<”调用了基类1的析构函数!\n” ;}

};

class CBase2

{ int y ;

public :

CBase2 (int a)

{y=a :cout<<”调用了基类2的构造函数!\n” ;}

~CBase2 ( void )

{ cout<<”调用了基类2的析构函数!\n” ;}

};

class CDerived :public CBase1 , public CBase2 //A 此处基类顺序影响构造函数的调用顺序。

{ int z ;

public:

CDerived ( int a , int b ,int c):CBase1(a) ,CBase2(b)    //B初始化基类成员

{  z=c ; cout<<”调用了派生类的构造函数!\n” ;}

~ CDerived(void)

{  cout<<”调用了派生类的析造函数!\n” ;  }

};

void main ( void )

{CDerived d (2, 4 , 6) ;}

执行结果:

调用了基类1的构造函数!

调用了基类2的构造函数!

调用了派生类的构造函数!

调用了派生类的析造函数!

调用了基类2的析构函数!

调用了基类1的析构函数!

 

7)如果派生类中包含对象成员,则创建派生类对象时,C++规定构造函数的调用次序为:

   基类的构造函数、对象成员的构造函数、派生类的构造函数。

   如果,此时基类中有对象成员,调用基类的构造函数之前还要先调用基类对象成员的构造函数,调用次序与前述类同。

   在初始化成员列表中,对象成员采用的是对象名,而对基类的初始化使用的是基类的类名,即基类的构造函数名。

调用顺序如下图所示:

基类的基类的构造函数

基类对象成员的构造函数

基类的构造函数

派生类对象的构造函数

派生类的构造函数

 

 


       

 

 

 

 

 

 

 

例6 派生类中包含对象成员,将前例中的派生类的定义作如下的修改:

# include<iostream.h>

class CBase1

{  int x ;

public:

CBase1(int a)

{x=a ;cout<<”调用了基类1的构造函数!\n” ;}

~CBase1 ( void )

{ cout<<”调用了基类1的析构函数!\n” ;}

};

class CBase2

{ int y ;

public :

CBase2 (int a)

{y=a :cout<<”调用了基类2的构造函数!\n” ;}

~CBase2 ( void )

{ cout<<”调用了基类2的析构函数!\n” ;}

};

class CDerived :public CBase1 , public CBase2

{ int z ;

 CBase 1 b1 ,b2 ;

public:

CDerived ( int a , int b , int c ) :CBase1(a) ,CBase2(b) ,b1(20) ,b2(a*b)

{  z=c ; cout<<”调用了派生类的构造函数!\ n “ ;}

~CDerived (void )

{ cout<<” 调用了派生类的析构函数!\ n “ ;}

};

void main ( void )

{CDerived d (2, 4 , 6) ;}

 

如果其它内容不变,则程序输出:

调用了基类1的构造函数!

调用了基类2的构造函数!

调用了基类1的构造函数!

调用了基类1的构造函数!

调用了派生类的构造函数!

调用了派生类的析造函数!

调用了基类1的析构函数!

调用了基类1的析构函数!

调用了基类2的析构函数!

调用了基类1的析构函数!

 

 

8) 如果基类A的派生类为B,B又派生了C,则在对B进行初始化之前,必须对它的基类A进行初始化,通常是通过B的构造函数实现。但是不能通过C的构造函数实现。即一个类的构造函数只能对它的直接基类的构造函数传递实参。对于更多层继承关系,都可按这种方式递归进行。

 

例7多层继承的基类成员的初始化

# include<iostream.h>

class A

{protected:

  int x ;

public:

A(int a)

{ x=a ;cout<<”调用了类A的构造函数\n” ;}

};

class B :public A

{ protected :

int y ;

public:

        B ( int a ,int b) :A(a)

 { y=b ;cout<<”调用了类B的构造函数\n” ;}

};

class c :public B

{ protected:

  int z ;

public:

 C ( int a ,int b, int c) :B(a,b)

 { z=c ;cout<<”调用了类C的构造函数\n” ;}

};

void main ( )

{A a(1) ;

B b (10 ,20) ;

C c(100 ,200,300) ;

}

 执行结果:

调用了类A的构造函数

调用了类A的构造函数

调用了类B的构造函数

调用了类A的构造函数”

调用了类B的构造函数

调用了类C的构造函数

 

 

例8:求圆柱的体积

#define PI 3.1415926

# include <iosteam.h>

class CCircle

{ float r ;

public :

CCircle ( float R) { r = R ;}

float Getr ( void ) {return  r ;}

};

class Crectangle

{  float l , w ;     //矩形的长和宽

public :

Crectangle ( float L)

{l=L ;}

float Getl ( void )

{return l ;}

};

class CColumn :public CCircle , public Crectangle

{  double v ;                //圆柱体的体积

public:

CColumn (float L ,float w) :CCircle(W/2),Crectangle(L)

{  }

void Volumn(void)

{v=PI*Getr()*Getr()*Get1() ;

}

void ShowVolumn(void )

{cout<<”Volumn=”<<v<<’\n’ ;}

};

void main (void)

{CColumn col (7.5,4) ;

col.Volumn( ) ;

col.ShowVolumn();

}

执行结果:

Volumn=94.2478

例9   (program 8-3  p260)

 

CA

CB

CC

类之间的继承关系

CD

 

 

 

 

 

 

 

 

 

 


#include<iostream.h> 

class CA{

         int a ;

public:

         CA(int n){a=n;  cout<<"CA::a="<<a<<endl; };

         ~CA(){cout<<"CAobj is destructing"<<endl;};

};

class CB{

         int b;

public:

         CB(int n){ b=n;  cout<<"CB::b="<<b<<endl; };

         ~CB(){cout<<"CBobj is destructing"<<endl;};

}; 

class CC: public CA

{  int c;

public:

         CC(int n1,int n2):CA(n2) { c=n1;  cout<<"CC::c="<<c<<endl; }

         ~CC(){cout<<"CCobj is destructing"<<endl;}

};

class CD:public CB,public CC{

         int d;

public:

CD(int n1,int n2,int n3,int n4):CC(n3,n4),CB(n2)

{ d=n1;   cout<<"CD::d="<<d<<endl; }

 ~CD(){cout<<"CDobj is destructing"<<endl;}

};

 

void main(void)

{  CD CDobj(2,4,6,8); }

 

运行结果为:

CB::b=4

CA::a=8

CC::c=6

CD::d=2

CDobj is destructing

CCobj is destructing

CAobj is destructing

CBobj is destructing

思考:将派生类CD改写为如下形式后,请给出输出结果。

 

class CD:public CB,public CC {

         int d;

         CC obcc;

         CB obcb;

public:

CD(int n1,int n2,int n3,int n4) :CC(n3,n4), CB(n2), obcb(100+n2), obcc(100+n3,100+n4)

{  d=n1;       cout<<"CD::d="<<d<<endl;      };

~CD(){cout<<"CDobj is destructing"<<endl;};

};

      

/*

运行结果为:

CB::b=4

CA::a=8

CC::c=6

CA::a=108

CC::c=106

CB::b=104

CD::d=2

CDobj is destructing

CBobj is destructing

CCobj is destructing

CAobj is destructing

CCobj is destructing

CAobj is destructing

CBobj is destructing

Press any key to continue

*/CCobj is destructing

CBobj is destrcting

 

例10   (program 8-1  p252)   

假设公司雇员分为:雇员(employee)、经理(manager)、工程师(engineer)、高级主管(director)。而且假定只关心这几类雇员各自的如下一些数据:

              employee(雇员)类:    姓名、年龄、工资;

              manager(经理)类:     姓名、年龄、工资、行政级别;

              engineer(工程师)类:  姓名、年龄、工资、专业、学位;

              director(高级主管)类:姓名、年龄、工资、行政级别、职务。

employee

manager

engineer

director

4个类之间的继承关系

 

 

 

 

 

 

 

 

 

 

 

 


#include <iostream.h>

#include <string.h>

class employee   //employee类(类型),将作为其它几个类的基类

{  short age;

   float salary;

protected:

   char * name;  

public:

        employee (short ag, float sa, char * na)

{      age=ag;

              salary=sa;

              name=new char[strlen(na)+1];

              strcpy(name,na);

        } 

        void print () const

{  cout<<"    "<<name<<": ";

              cout<<age<<" : ";

              cout<<salary<<endl;

         }

 

        ~employee() {delete[]name;}

};

class manager:public employee   //派生类manager

{    int level;

public:

manager(short ag, float sa, char* na, int lev) :employee (ag,sa,na)     //对基类初始化负责

 { level=lev;         } 

 void print()const

{  employee::print();   //调用基类print显示“共性”数据

cout <<" level:"<<level<<endl;

}     

};

      

  /*注意:允许派生类中的print与基类的print重名,按如下规定进行处理:对子类而言,不加类名限定时默认为是处理子类成员,而要访问父类重名成员时,则要通过类名限定*/

 

class engineer:public employee  //派生类engineer

{   char speciality,adegree;

       /*专业:‘E’--电子,‘M’—机械,‘C’—计算机,‘A’—自动化专业;

     学位:‘O’—博士 , ‘M’—硕士 , ‘B’—学士 , ‘N’—无学位 * /

  public:

 engineer (short ag , float sa , char * na ,char sp ,char ad): employee (ag,sa,na)

{speciality=sp;   //派生类的构造函数

 adegree =ad;

}

void print()const

{  employee::print();   //调用基类print显示“共性”数据

cout <<"   speciality:"<< speciality<<endl;

cout <<"   academic degree:"<< adegree <<endl;

}     

};

enum ptitle {PS,GM,VPS,VGM};

class director:public manager   //派生类director

{  ptitle post;

 public:

 director(short ag ,float sa ,char*na , int lev ,ptitle po): manager(ag,sa,na,lev)

{ post =po ; }   //派生类的构造函数

void print()const

{  manager::print();  

cout <<"   post:"<< post <<endl;

}     

};

 

void main() {                       //主函数

       employee emp1(23,610.5,"zhang"), emp2(27,824.75,"zhao");

       manager man1(32,812.45,"li",11), man2(34,1200.5,"cui",7);

       engineer eng(26,1420.10,"meng",'E','M');

       director dir(38,1800.2,"zhou",2,GM);

       emp1.print();

       emp2.print();

       man1.print();

       man2.employee::print();       //调用基类的print

       eng.print();

       dir.print();

}

 

程序执行后的显示结果如下:

    zhang: 23 : 610.5

    zhao: 27 : 824.75

    li: 32 : 812.45

    level:11

    cui: 34 : 1200.5

    meng: 26 : 1420.1

    speciality:E

    academic degree:M

    zhou: 38 : 1800.2

    level:2

       post:1

 

 

例11   (program 8-2  p257)   

      分析下述程序中的继承与派生关系.

#include <iostream.h>

classB{   

           int priDat;

  protected:

           int proDat;

  public:

           int publDat;

};

 

class D11 : public B {

public:

       void f11() {

              pubDat=11;    //OK! 仍为public

              proDat=12;        //OK! protected

              priDat=13;         /ERROR! 基类的私有成员不可访问

       }

};

class D21 : public D11 {     

public:

       void f21() {

              pubDat=121;    //OK! 仍为public

              proDat=122;       //OK! 仍为protected

              priDat=131;       //ERROR! 基类的私有成员不可访问

       }

};

    

class D12 : protected B {    

public:

       void f12() {

              pubDat=21;      //OK! 变为protected

              proDat=22;        //OK! 仍为protected

              priDat=23;         //ERROR! 基类的私有成员不可访问

       }

};

 

class D22 : public D12 {     

public:

       void f22() {

              pubDat=221;    //OK! 仍为protected

              proDat=222;       //OK! 仍为protected

              priDat=223;       //ERROR! 基类的私有成员不可访问

       }

};

 

class D13: private B {

public:

       void f13() {

              pubDat=31;     //OK! 变为private 可以访问

              proDat=32;        //OK! 变为private 可以访问

              priDat=33;        //ERROR! 基类的私有成员不可访问

       }

};

 

class D23 : public D13 {

public:

       void F23() {

              pubDat=311;    //ERROR! 基类的私有成员不可访问

              proDat=321;       //ERROR! 基类的私有成员不可访问

              priDat=331;       //ERROR! 基类的私有成员不可访问

       }

};

 

void main() {

       B ob0;

       ob0.pubDat=1;          //OK! 可以访问public成员

       ob0.proDat=2;    //ERROR! 不可以访问protected成员

       ob0.priDat=3;   //ERROR!不可以访问private成员

       D11 d11;D21 d21; D22 d22;D23 d23;

       d11.pubDat=4;         //OK! 可以访问public成员

       d11.proDat=5;   //ERROR!  不可以访问protected成员

       d11.priDat=6;     //ERROR! 不可以访问private成员

       d21.pubDat=7;         ///OK! 可以访问public成员

       d21.proDat=8;   //ERROR! 不可以访问protected成员

       d21.priDat=9;   //ERROR! 不可以访问private成员

       d22.pubDat=7;         //ERROR! 不可以访问protected成员

       d22.proDat=8;   //ERROR! 不可以访问protected成员

       d22.priDat=9;   //ERROR! 不可以访问private成员

d23.pubDat=10;       //ERROR! 不可以访问private成员

}

 

 

8. 3  其他特征的继承关系

8.3.1  友元关系以及静态成员的继承

8.3.1.1  友元关系

①. 基类的友元不被继承。

   即如果基类有友元类或友元函数,则其派生类不会因继承关系也一定有此友元类或友元函数。 

②.如果基类是某类的友元,则这种友元关系是被继承的。

   即 被派生类继承过来的成员,如果原来是某类的友元,那么它作为派生类的成员仍然是某类的友元。 

8.3.1.2  静态成员的继承

  如果基类的静态成员被派生类继承,其静态属性也随静态成员被继承。

即 如果基类的静态成员是公有的或保护的,则它们被其派生类继承为派生类的静态成员。

①.这些成员通常用“<类名>::<成员名>方式引用或调用。

②.无论有多少个对象被创建,这些静态成员都只有一个拷贝。它为基类和派生类的所有对象所共享。

 

8.3.2  与基类对象和派生类对象相关的赋值兼容性问题

8.3.2.1  基类和派生类中的赋值运算符的使用

①.如果派生类有自己的赋值运算符的重载定义,按该重载函数处理。

②.如果派生类未定义自己的赋值操作,而基类定义了赋值操作,则系统自动定义派生类赋值操作,其中基类成员的赋值按基类的赋值操作进行。

③.基类和派生类都未定义专门的赋值操作,系统自动定义缺省赋值操作(按位进行拷贝)。

 

8.3.2.3 基类对象和派生类对象的赋值兼容

基本数据类型,当两个变量的类型不一致时,只要相互之间兼容也可以进行赋值,比如可以将一个实型数据赋给整型变量。

同样,同类型的对象之间可以互相赋值:

在具有继承关系的类之间,公有派生对象可以直接赋值给基类的对象;之后基类对象只能访问派生类中从基类继承的公有成员,不能访问派生类中增加的公有成员。

但私有派生类对象或保护派生类对象不可以赋给基类对象;

基类对象不可以赋给派生类对象;

公有派生类对象的地址可以赋给基类指针变量;公有派生类对象可以初始化基类引用。

即:

①基类对象=派生类对象 。   反之不行。

②指向基类对象的指针=派生类对象的地址 。反之不行。

③基类的引用=派生类对象 。反之不行。

 

例1 . 赋值兼容规则

# include <iostream.h>

class CBase

{protected :

    int x ;

public:

CBase (int a){x=a ;}

void Show1()

{cout<<”x=”<<x<<’\n’ ;}

};

class CDerived :public CBase

{protected:

   int  y ;

public :

  CDerived(int a , int b ):CBase (b)

{ y=a ;}

void Show1()

{cout<<”y=”<<y<<’\n’ ;}

void Show2()

{cout<<”y=”<<y<<’\n’ ;}

};

void main ( )

{CDerived c (2 ,3) ;

CBase b1 (4) , *b2 ;

CBase &b3=c ;        //A 派生类对象初始化基类引用

b1=c ;               //B 派生类对象赋给基类对象

b2=&c ;              //C 派生类地址赋给基类指针

b1.Show1( );         //D

b2->Show1( ) ;        //E

b3.Show1( ) ;          //F

// c=b1  ;            //错误,不能将基类对象赋给派生类对象

//b1.Show2() ;       //错误,不能访问派生类中增加的成员

//b2->Show2() ;         //错误,理由同上

//b3.Show2() ;           //错误,同上

执行结果:

x=3

x=3

x=3

 

//例  program 8-4 .cpp  P264                 

#include<iostream.h>

class base   //定义基类

{      int a;

  public:

       base( ) { }

       base(int sa) { a=sa;}

       int geta() {return a;}

};

class derived:public base     //派生类

{  int b;

  public:

       derived( ) { }

       derived(int sa, int sb):base(sa) { b=sb; }

       int getb() {return b;}

};            

void main( )

{      base bs1(123);  // 基类base的对象

   cout<< "bs1.geta()=" << bs1.geta() <<endl;

       derived der(246,468);   // derived类的对象

       bs1=der;                                //OK!

    cout<< "bs1.geta()=" << bs1.geta() <<endl;

       //der=bs1;                                     //ERR!

       base *pb = &der;    //OK! ②指向基类对象的指针=派生类对象的地址

       cout<<"pb->geta()="<<pb->geta()<<endl;            //OK!

       //cout<<pb->getb()<<endl;       //ERR! --访问非基类成员部分

       cout<<"((derived *)pb)->getb()="<<((derived *)pb)->getb()<<endl;      //OK! -- 经过类型转换

       //derived *pd = &bs1;                 //ERR!

}

/*

bs1.geta()=123

bs1.geta()=246

pb->geta()=246

((derived *)pb)->getb()=468

Press any key to continue

*/

 

8.4  派生关系中的二义性处理

   继承和派生把不同的类联系到一起,就产生了一个同名成员的处理问题,有下面几种情况。

8.4.1  单一继承时基类与派生类间重名成员的处理

在派生类中新增加的成员可以与基类的成员同名,这种同名不会产生冲突。

C++规定,当没有对这类成员指定作用域时,在派生类中所使用的这类成员为在派生类中定义的成员,即这时就好象派生类中定义的成员覆盖了从基类中继承的同名成员,因此这种关系称为成员覆盖(overridden),或称为支配规则。

在派生类中如果要使用这种隐藏了的成员、方法时利用作用域运算符,格式为:

 <基类名>::<成员名>

 

即 ①派生类的成员函数访问重名成员时;派生类的对象访问重名成员时,

不加类名限定时,默认为派生类中定义的成员。

  ②上述情况下,如果要使用这种隐藏了的成员、方法时利用作用域运算符,格式为:

 <基类名>::<成员名>

 

例1.成员覆盖示例

# include<iostream.h>

class B

{ protected :

    int x ;

 public :

     void Show (void) {cout<<”基类B中的x=”<<x<<’\n’ ;}

};

class D:public B           //E 公有派生

{

 protected:

     int x ;

public:

void Show ( )

       {cout<<”派生类D中的x=”<<x<<’\n’ ;}

void SetX (int a)

{x=a ;}           //F覆盖了基类的x,不会产生二义性。

void SetBX (int a )

{B::x=a ;}           //G利用作用域运算符访问继承类B中的成员。

void ShowB ( )    

{B::Show() ;}       //H同G

} ;

void main (void)

{D c;

c.SetX(20) ;

c.SetBX(30) ;

c.Show() ;         //I,访问是类D的成员

c.B::Show ();    //J同G,此处若不是公有派生,而是私有派生,则会出错。

}

执行结果:

派生类D中的x=20

基类B中的x=30

 

//例2.program 8-5. cpp  P266

#include <iostream.h>

class CB

{    public:

       int a;

       CB(int x){a=x;}

       void showa(){cout<<"Class CB -- a="<<a<<endl;}

};               

class CD:public CB {

public:

       int a;                 //与基类a同名

       CD(int x, int y):CB(x){a=y;}

       void showa(){            //与基类showa同名

       cout<<"Class CD -- a="<<a<<endl;

       }

       void print2a() {

              cout<<"a="<<a<<endl;               //子类a

              cout<<"CB::a="<<CB::a<<endl;      //父类a

       }

};        

void main() {

       CB CBobj(12);

       CBobj.showa();

       CD CDobj(48, 999);

       CDobj.showa();        //子类的showa

       CDobj.CB::showa();      //父类的showa

       cout<<"CDobj.a="<<CDobj.a<<endl;

       cout<<"CDobj.CB::a="<<CDobj.CB::a<<endl; 

}

/*

Class CB -- a=12

Class CD -- a=999

Class CB -- a=48

CDobj.a=999

CDobj.CB::a=48

Press any key to continue

*/  

   

8.4.2  多重继承时两基类间重名成员的处理

 

    如果一个类由两个或多个基类派生,即多重派生,且在不同的基类中具有名字相同的公有成员或保护成员,此时若在派生类中使用这类同名成员,就会出现不唯一,产生二义性的错误,这种情况称为冲突。解决方法:

 1)避免同名。

 2)使用作用域运算符来限定所访问的成员是属于哪一个基类的。格式:

    <类名>::<成员名>

多继承情况下二基类间成员重名时,按如下方式进行处理:对子类而言,不加类名限定时默认为是处理子类成员,而要访问父类重名成员时,则要通过类名限定。

3)注意:对于连续派生时,即派生类作为基类派生出新的类时,作用域运算符不能连续使用,即下列形式不允许:

   CName1::Cname2::Cnamen。

 

例3 冲突的程序示例

# include <iostream.h>

class A

{protected :

   int x ;

public:

   A (int a ) {x=a ;}

   void Show (void )

{cout<<”x=”<<x<<’\n’ ; }

} ;

class B

{  protected :

   int  x ;

public :

   B ( int a ){x=a ;}

void Show(void )

{cout<<”x=”<<x<<’\n’ ;}

};

class  C :public A , public B

  int y ;

public :

  C (int a , int b ) :A(a) ,B (b) { }

  void Setx(int a){x=a ;}     //A,冲突,二义性,编译时,不知是A的x,还是B的x.

                            //改为:{A::x=a ;}

  void Sety(int b){y=b ;}

  int Gety(void ) {return y ;}

};

void main (void )

{

C c1 (10 ,100)

c1.Show ( ) ;         //B,冲突,二义性,编译时,不知是A的show,还是B的show.

                        //改为;c1.B::Show();

}                             

执行结果:

x=100

Press any key to continue

 

 

例4  具有形如图11.4的类结构的冲突问题

3)注意:对于连续派生时,即派生类作为基类派生出新的类时,作用域运算符不能连续使用,即下列形式不允许:

   CName1::Cname2::Cnamen。

 

类B1

类B2

类D1

类D2

类C

图11.4

# include <iostream.h>

class B1

{protected :

  int x ;

public :

    void Show ( )

{cout<<”x=”<<x<<’\n’ ;}

};

class B2

{protected :

  int x ;

public:

  void Show ( ) {cout<<”x=”<<x<<’\n’ ;}

};

class D1 :public B1 {  };

class D2 :public B2 {  } ;

class C::public D2 , public D1

{public :

void SetX1(int a){D1:::B1::x=a ;} //A错误,域运算符不能连续使用,改为:{D1::x=a ;}

  void SetX2(int a){D2::x=a ;}   //B 正确,或为B2::x=a ;

};

void main (void )

{  C c;

   c.SetX1(2) ;

   c.D1::B1::Show() ;   //C 错误,域运算符不能连续使用,改为c.D1::Show()

   c.SetX2(3) ;

   c.D2::Show() ;      //D  正确,或c.B2::Show() ;

}

执行结果为:

x=2

x=3

 

8.4.3       多级混合继承(非虚拟继承)包含两个基类实例情况的处理

 

类CD

类CB2

类CB1

类CA

图 多级混合继承

 

 

 

 

 

 

 

 


如上图,若CA有成员数据a和成员函数show(),则CB1和CB2 都各继承了一个ah和show(),而类CD继承了CB1和CB2,所以类CD拥有继承来的两个a 和两个show(),如果创建 CD的对象CDobj,则CD类的成员函数引用或通过CD的对象引用CDobj.a 和CDobj. show()时会产生二义性。

解决方法:

1)使用作用域运算符来限定所访问的成员是属于哪一个基类的。格式:

    <类名>::<成员名>

如上例可以:CDobj.CB1::a  和 CDobj.CB2::a

CDobj. CB1::show()  和CDobj. CB2::show()

例5

#include <iostream.h>

class A {

public:

       int a;

       A(int x){a=x;}

       void showall(){cout<<"a="<<a<<endl;}

};        

class B:public A {

public:

       int b;

       B(int x):A(x-1){b=x;}

};

 

class C:public A {

public:

       int c;

       C(int x):A(x-1){c=x;}

};        

class D:public B,public C {

public:

       int d;

       D(int x, int y, int z):B(x+1),C(y+2){d=z;}

       void showall() {

              cout<<"C::a="<<C::a<<endl;

                 //在类D定义范围内,要通过类名限定来指定

                 //访问两个类A实例中的哪一个

              cout<<"B::a="<<B::a<<endl;

              cout<<"b,c,d="<<b<<", "<<c<<", "<<d<<endl;

                 //b、c、d不重名,具有唯一性

       }

};      

void main() {

       D Dobj(101, 202, 909); 

       Dobj.showall();

       cout<<"-------------------"<<endl;

       cout<<"Dobj.C::a="<<Dobj.C::a<<endl;

              //访问类D的从C继承而来的a

       cout<<"Dobj.B::a="<<Dobj.B::a<<endl;

}  

/*C::a=203

B::a=101

b,c,d=102, 204, 909

-------------------

Dobj.C::a=203

Dobj.B::a=101

*/

2)通过虚基类。见下节。 

上述多级混合继承关系应用例举:

      例1.  类A--人员类;  类B--学生类; 类C--助教类; 类D--学生助教类。

      例2.  类A--人员类;  类B--学生类; 类C--工人类; 类D--工人学生类。

      例3.  类A--家具类;  类B--沙发类; 类C--床类;   类D--沙发床类。

 

 


TAG:

 

评分:0

我来说两句

日历

« 2024-05-10  
   1234
567891011
12131415161718
19202122232425
262728293031 

数据统计

  • 访问量: 86809
  • 日志数: 218
  • 书签数: 1
  • 建立时间: 2010-11-06
  • 更新时间: 2011-03-21

RSS订阅

Open Toolbar