关闭

c++继承与派生

1621人阅读 评论(0) 收藏 举报
面向对象程序设计有4个主要特点:抽象、封装、继承和多态性。在前面3章中学习了类和对象,了解了面向对象程序设计的两个重要特征——数据抽象与封装,已经能够设计出基于对象的程序,这是面向对象程序设计的基础。

   面向对象技术强调软件的可重用性(softwarereusability)。C++语言提供了类的继承机制,解决了软件重用问题。

一、 继承与派生的概念

   在C++中可重用性是通过继承(inheritance)这一机制来实现的。因此,继承是C++ 的一个重要组成部分。
   前面介绍了类,一个类中包含了若干数据成员和成员函数。在不同的类中,数据成员和成员函数是不相同的。但有时两个类的内容基本相同或有一部分相同。

例如:已声明了学生基本数据的类Student:

class Student
   { publc:
      void display() //对成员函数display的定义
      
 { cout<<"num:"<<num<<endl;
         cout<<"name:"<<nallle<<endl;
          cout<<"sex:"<<sex<<endl;}
      pnvate:
        int num;
        string name;
        char sex; };

   如果学校的某一部门除了需要用到学号、姓名、性别以外,还需要用到年龄、地址等信
息。当然可以重新声明另一个类class Student1:

class Student1
   { public:
      void display();//此行原来已有
        
{ cout<<"num:"<<num<<endl;//此行原来已有
          
cout<<"name:"<<name<<endl;//此行原来已有
          
cout<<"sex:"<<sex<<endl;//此行原来已有
          
cout<<"age:"<<age<<endl;
          cout<<"address:"<<addr<<endl;}
     private:
      int num;//此行原来已有
      
string name; //此行原来已有
      
charsex: //此行原来已有
      
int age;
      char addr[20]; };

   可以看到有相当一部分是原来已有的。很多人自然会想到能否利用原来声明的类Student作为基础,再加上新的内容即可,以减少重复的工作量。C++提供的继承机制就是为了解决此类问题。

   马的例子来说明继承的概念。见图示意。

继承与派生定义:

   一个新类从已有的类那里获得其已有特性,这种现象称为类的继承。通过继承,一个新建子类从已有的父类那里获得父类的特性。从另一角度说,从已有的类(父类)产生一个新的子类,称为类的派生。
   1、一个派生类只从一个基类派生,这称为单继承(singleinheritance),这种继承关系所形成的层次是一个树形结构。
   2、一个派生类有两个或多个基类的称为多重继承(mulUple inheritance),

   关于基类和派生类的关系,可以表述为:

   派生类是基类的具体化,而基类则是派生类的抽象。从下图中可以看到:小学生、巾学生、大学生、研究生、留学生是学生的具体化,他们是在学生的共性基础上加上某些特点形成的子类。而学生则是对各类学生共性的综合,是对各类具体学生特点的抽象。基类综合了派生类的公共特征,派生类则在基类的基础上增加某些特性,把抽象类变成具体的、实用的类型。

二、生类的声明方式

   先通过一个例子说明怎样通过继承来建立派生类,从最简单的单继承开始,假设已经声明了一个基类Student(见前面的介绍),在此基础上通过单继承建立一个派生类Studentl:
声明派生类的一般形式为:

class派生类名:[继承方式] 基类名
{
派生类新增加的成员
}

说明:继承方式包括:public(公用的),prtvate(私有的)和protected(受保护的),继承方式是可选的,如果不写此项,则默认为private(私有的)。

例如:
class Studentl:public Student //声明基类是Student
{ public:
void display_1() //新增加的成员函数
{cout<<"age:"<<age<<endl;
cout<<"address:"<<addr<<endl;}
private:
int age; //新增加的数据成员
string addr;}; //新增加的数据成员

三、派生类的构成

   派生类中的成员包括从基类继承过来的成员和自己增加的成员两大部分。从基类继承的成员体现了派生类从基类继承而获得的共性,而新增加的成员体现了派生类的个性。正是这些新增加的成员体现了派生类与基类的不问,体现了不问派生类之间的区别。为了形象地表示继承关系,采用下图来示意。

   在基类中包括数据成员和成员函数(或称数据与方法)两部分,派生类分为两大部分:一部分是从基类继承来的成员,另一部分是在声明派生类时增加的部分,每一部分均分别包括数据成员和成员函数。
   实际上,并不是把基类的成员和派生类自己增加的成员简单地加在一起就成为派生类。构造一个派生类包括以下3部分工作:
   1、从基类接收成员。派生类把基类全部的成员(不包括构造函数和析构函数)接收过来,也就是说是没有选择的。注意:派生可能会造成数据的冗余
   2、通过指定继承方式来调整从基类接收的成员。注意:函数的重载与覆盖。
   3、在声明派生类时增加的成员,这部分内容是很重要的,它体现了派生类对基类功能的扩展。
   通过以上的介绍,可以看到:派生类是基类定义的延续。可以先声明一个基类,在此基类中只提供某些最基本的功能,而另外有些功能并未实现,然后在声明派生类时加入某些具体的功能,形成适用于某一特定应用的派生类,通过对基类声明的延续,将一个抽象的基类转化成具体的派生类。因此,派生类是抽象基类的具体实现。

四、派生类成员的访问属性(可见性讨论)

   既然派生类:中包含基类成员和派生类自己增加的成员,就产生了这两部分成员的关系和访问属性的问题。在建产派生类的时候,并不是简单地把基类的私有成员直按作为派生类的私有成员,把基类的公用成员直作为派生类的公用成员。实际上,对基类成员和派生类自己增加的成员是按不同的原则处理的。
具体说,在讨论访问属性时,需要号虑以下几种情况:

(1)基类的成员函数访问基类成员。
(2)派生类的成员函数访问派生类自己增加的成员。
(3)基类的成员函数访问派生类的成员。 ×
(4)派生类的成员函数访问基类的成员。(与继承方式有关)
(5)在派生类外访问派生类的成员。(公、私成员作用)
(6)在派生类外访问基类的成员。(与继承方式有关)

   在派生类中,对基类的继承方式可以有public(公用的),private(私有的)和protected(保护的)3种。不同的继承方式决定了基类成员在派生类中的访问属:

(1)公用继承(public inheritance):基类的公用成员和保护成员在派生类中保持原访问属性,其私有成员仍为基类私有。
(2)私有继承(pnvate inheritance):基类的公用成员和保护成员在派生类中成了私有成员。其私有成员仍为基类私有:
(3)受保护的继承(protected inheritance):基类的公用成员和保护成员在派生类中成了保护成员,其私有成员仍为基类私有。保护成员的意思是:不能被外界引用,但可以被派生类的成员引用。

1、公用继承

定义:
   
在定义一个派生类时将基类的继承方式指定为public的,称为公用继承,用公用继承方式建立的派生类称为公用派生类(public derived class),其基类称为公用基类(public base class)。

功能:
   
前面已指出:采用公用继承方式时,基类的公用成员和保护成员在派生类中仍然保持其公用成员和保护成员的属性,而基类的私有成员在派生类中并没有成为派生类的私有成员,它仍然是基类的私有成员,只有基类的成员函数可以引用它,而不能被派生类的成员函数引用,因此就成为派生类中的不可访问的成员。

 

例1 访问公有基类的成员。
下面写出类的声明部分:
#include <iostream>
#include <string>
using namespace std;
class Student    //声明基类
   
{ public:   //基类公用成员
      
void get_value()
        { cin>>num>>name>>sex;}
         void display( )
           { cout<<"num: "<<num<<endl;
             cout<<"name: "<<name<<endl;
             cout<<"sex: "<<sex<<endl;}
     private : //基类私有成员
      
int num;
      string name;
      char sex;   };

class Student1: public Student //以public方式声明派生类Studentl
   
{ public:
      void get_value_1()
        { cin>>age>>addr;}
      void display_1()
        { //cout<<"num: "<<num<<endl;//企图引用基类的私有成员,错误
         
//cout<<"name: "<<name<<endl; //企图引用基类的私有成员,错误
          
//cout<<"sex: "<<sex<<endl;//企图引用基类的私有成员,错误
          
cout<<"age: "<<age<<endl;   //引用派生类的私有成员,正确
          
cout<<"address: "<<addr<<endl;}//引用派生类的私有成员,正确
     
private:
      int age;
      string addr; };

   由于基类的私有成员对派生类来说是不可访问的,因此在派生类中的display_l函数中直接引用基类的私有数据成员num,name和sex是不允许的。只能通过基类的公用成员函数来引用基类的私有数据成员。
可以将派生类Studentl的声明改为:

class Student1:public Student //以public方式声明派生类Studentl
   
{ public:
      void display_1()
        { cout<<"age:"<<age<<endl; //引用派生类的私有成员,正确
         
cout<<”address:”<<addr<<endl;//引用派生类的私有成员,正确
    
private:
      int age;
      stnng addr; };

   然后在main函数中分别调用基类的display函数和派生类中的display_l函数,先后输出5个数据:

//以这样写main函数(假设对象stud中已有数据)
int main()
 { Student1 stud1; //定义派生类Studentl的对象stud
      
.
      .
   stud1.display(); //调用基类的公用成员函数,输出基类中3个数据成员的值
   
stud1.display_1(); //调用派生类的公用成员函数,输出派生类中两个数据成员的值
   
return 0; }

说明
   1、在类外不应用出现: stud.age=18;//错误。在类外不能引用派生类的私有成员
   
2、可以在stud.display_1();中调用基类的display函数,可输出5个数据。

2、私有继承

定义:
   
在声明一个派生类时将基类的继承方式指定为private的,称为私有继承,用私有继承方式建立的派生类称为私有派生类(private derived class),其基类称为私有基类(private base class)。

功能:
   
私有基类的公用成员和保护成员在派生类中的访问属性相当于派生类中的私有成员,即派生类的成员函数能访问它们,而在派生类外不能访问它们。私有基类的私有成员在派生类中成为不可访问的成员,只有基类的成员函数可以引用它们。一个基类成员在基类中的访问属性和在私有派生类中的访问属性可能是不同的。


说明:一个成员在不同的派生层次中的访问属性可能是不同的。它与继承方式有关。

例2 将例1中的公用继承方式改为用私有继承方式(基类Student不改)。

可以写出私有派生类如下:
class Student1: private Student //用私有继承方式声明派牛类Sludentl
   
{ public:
      void display_1()
        { display();//输出两个数据成员的值
          
cout<<"age: "<<age<<endl;//引用派生类的私有成员,正确
          
cout<<"address: "<<addr<<endl;} //引用派生类的私有成员,正确
    
private:
      int age;
      string addr; };

请分析下面的主函数:
int main()
 { Student1 stud1;//定义一个Studentl类的对象studl
   
stud1.display();//错误,私有基类的公用成员函数在派生类中是私有函数
   
stud1.display_1();//正确。Display l函数是Studentl类的公用函数
   
stud1.age=18; //错误。外界不能引用派生类的私有成员
   
return 0; }

通过上例可看:
   1、不能通过派生类对象(如studl)引用从私有基类继承过来的任何成员(如studl.display()或studl.nnm)。
   2、派生类的成员函数不能访问私有基类的私有成员,但可以访问私有基类的公用成员。可以通过派生类的成员函数调用私有基类的公用成员函数。

采用的方法是:
   ①在main函数中调用派生类中的公用成员函数studl.display_1;
   ②通过该公用成员函数调用基类的公用成员函数display(它在派生类中是私有函数,可以被派生类中的任何成员函数调用);
   ③通过基类的公用成员函数display引用基类中的数据成员。

   根据上面的要求,补充和完善上面的程序,使之成为完整、正确的程序。程序中应包括输入数据的函数。

注意: 由于私有派生类限制太多,使用不方便,一般不常使用。

3、保护成员和保护继承

定义:
   
protected声明的成员称为受保护的成员,它与private和public一样是用来声明成员的访问权限的。简称保护成员。在定义一个派生类时将基类的继承方式指定为protected的,称为保护继承,用保护继承方式建立的派生类称为保护派生类(protected derived class),其基类称为受保护的基类(protected base class),简称保护基类。

功能:
   
保护基类的公用成员和保护成员在派生类中都成了保护成员,其私有成员仍为基类私有。也就是把基类原有的公用成员也保护起来,不让派生类外任意访问。受保护成员不能被类外访问,这点和私有成员类似,可以认为保护成员对类的用户来说是私有的。从类的用户角度来看,保护成员等价于私有成员。但有一点与私有成员不同,保护成员可以被派生类的成员函数引用。


通过以上的介绍,可以知道:

(1)在派生类中,成员有4种不同的访问属性:
   ①公用的,派生类内和派生类外都可以访问。
   ②受保护的,派生类内可以访问,派生类外不能访问,其下一层的派生类可以访问:
   ③私有的,派生类内可以防问,派生类外不能访问。
   ④不可访问的,派生类内和派生类外都不能访问。
用下表表示:

需要说明:
   
①这里所列出的成员的访问属性是指在派生类中所获得的访问属性,例如,某一数据成员在基类中是私有成员,在派生类中其访问属性是不町访问的,因此在派牛类中它是不可访问的成员。
   ②所谓在派生类外部,是指在派生类对象的作用域中,在派生类范围之外。
   ③如果奉派生类继续派生,则在不同的继承方式下,成员所获得的防问属性是不同的,在本表中只列出在下一层公用派生类中的情况,如果是私有继承或保护继承,读者可以从表3中找到答案。

(2)类的成员在不同作用域中有不同的访问属性
   
一个成员的访问属性是有前提的,要看它在哪一个作用域中。在未介绍派生类之前,类的成员只属于其所属的类,不涉及其他类,不会引起歧义。在介绍派生类后,就存在一个问题:在哪个范围内讨论成员的特征,同一个成员在不同的继承层次中有不同的特征。一定要说明是对什么范围而言的可见性。

例3 在派生类中引用保护成员。
#include <iostream>
#include <string>
using namespace std;
class Student //声明基类
{public: //基类公用成员
void display( );
protected : //基类保护成员
int num;
string name;
char sex;
};

void Student::display( ) //定义基类成员函数
{cout<<"num: "<<num<<endl;
cout<<"name: "<<name<<endl;
cout<<"sex: "<<sex<<endl; }

class Student1: protected Student //用protected继承方式声明一个派生类
{public:
void display1( ); //派生类公用成员函数
private:
int age; //派生类私有数据成员
string addr; //派生类私有数据成员
};

void Student1::display1( )
{cout<<"num: "<<num<<endl; //引用基类的保护成员,合法
cout<<"name: "<<name<<endl; //引用基类的保护成员,合法
cout<<"sex: "<<sex<<endl; //引用基类的保护成员,合法
cout<<"age: "<<age<<endl; //引用派生类的私有成员,合法
cout<<"address: "<<addr<<endl; //引用派生类的私有成员,合法

int main( )
{Student1 stud1; //stud2是派生类student2的对象
stud1.display1( ); //display是派生类中的公用成员函数
return 0; }

注意:
1、 在派生类的成员函数中引用基类的保护成员是合法的。保护成员和私有成员不同之处,在于把保护成员的访问范围扩展到派生类中。
2、在程序中通过派生类Student1的对象stud1的公用成员函数display1去访问基类的保护成员num.name和sex,不要误认为可以通过派生类对象名去访问基类的保护成员(如stud1.num是错误的)。

4 多级派生时的访问属性

   常常有多级派生的情况,如果有图9所示的派生关系:类A为基类,类B是类A的派生类,类c是类B的派生类,则类c也是类A的派生类。类B称为类A的直接派生类,类c称为类A的间接派生类。类A是类B的直接基类,是类C的间接基类。在多级派生的情况下,各成员的访问属性仍按以上原则确定。

例4 多级派生的访问属性。

class A //基类
 
{ public:
    int i;
   protected:
    void f2();
    int j;
   private;
    int k; };

classB:publicA //public方式
  
{ public:
     void f3();
    protected:
     void f4();
    private:
     int m; };

classc:protected B //protected方式
   
{ public:
      voidf 5();
    pnvate:
      int n; };
类A是类B的公用基类,类B是类c的保护基类。各成员在不同类中的访问属性如下:

           i f2   j    k      f3   f4   m     f5   n
基类A      公用 保护 保护 私有
公用派生类B 公用 保护 保护 不可访问 公用 保护 私有
保护派生类c 保护 保护 保护 不可访问 保护 保护 不可访问 公用 私有

通过以上分析可以看到

   1、无论哪—种继承方式,在派生类中是不能访问基类的私有成员的,私有成员只能被本类的成员函数所访问,毕竟派生类与基类不是同一个类。如果在多级派生时都采用公用继承方式,那么直到最后一级派生类都能访问基类的公用成员和保护成员。如果采用私有继承方式,经过若干次派生之后,基类的所有的成员已经变成不可访问的子。如果采用保护继承方式,在派生类外是无法访问派生类中的任何成员的。
   2、而且经过多次派生后,人们很难清楚地记住哪些成员可以访问,哪些成员不能访问,很容易出错。因此,在实际中,常用的是公用继承。

 

转自: http://210.44.195.12/cgyy/text/HTML/text/14.htm


0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:457617次
    • 积分:6137
    • 等级:
    • 排名:第4332名
    • 原创:153篇
    • 转载:89篇
    • 译文:0篇
    • 评论:69条
    文章分类
    个人信息
    邮箱:xiaoyang_ios@sina.com
    最新评论