继承和派生

来源:互联网 发布:美国研究生费用 知乎 编辑:程序博客网 时间:2024/06/04 18:19
一、什么是继承和派生
  封装、继承、多态是C++的三个重要的特性。在面向对象的技术中强调软件的可重用性,而继承机制就是用来解决软件的重用问题。在C++中,所谓“继承”就是在一个已经存在的类的基础上建立一个新的类。已经存在的类成为基类或父类,新建立的类称为派生类或子类。
  一个类从一个已有的类那里获得已有的特性,这种现象称为类的继承。通过继承,一个新建的子类从父类那里获得父类的特性。从另一角度说,从已有的类(父类)产生一个新的类(子类),称为类的派生。派生类继承类基的所有数据成员和成员函数,并可以对成员做出必要的调整。一个基类可以派生出多个派生类,每一个派生类又能作为基类再派生出新的派生类。因此基类和派生类是相对而言的。类的每一次派生都继承了其基类的基本特征,同时又根据需要做出新的调整。一个派生类只从一个基类派生,这种称为单继承。一个派生类也有从两个或多个基类,这种称为多继承。关于基类和派生类的关系可以理解为:基类是派生类的抽象,派生类是基类的具体化。

二、派生类的声明方式
声明一个单继承的派生类的一般形式:
   class 派生类名:继承方式 基类名
   {
         派生类新增加的成员
   };
  其中继承方式有三种:public(公用的),protected(受保护的),private(私有的)。此项如果不写则默认为private(私有的)。
例:假设已经声明了一个基类Time,在此基础上通过单继承声明一个派生类Date
classDate:publicTime                              //公用继承
{
public:
                voiddisplay()
                {
                                cout << _year << _month << _day << endl;
                }
private:
                int_year;
                int_month;
                int_day;
};

三、派生类的构成
 派生类的成员包括从基类继承过来的成员和自己增加的成员两大部分。但是并不是说把基类的成员和派生类增加的成员简单的加在一起就成为了派生类。构造一个派生类包括以下3个部分:
1、从基类接受成员
 派生类把基类全部成员(不包括构造和析构函数)接受过来。不能选择接受一部分而舍弃另一部分,这是不可选择的。因此如果不能合理的选择基类的话,会造成数据的冗余。
2、调整从基类接收的成员
 虽然接受基类成员是不可选择的,但是可与对这些成员做出调整,例如同继承方式改变基类成员在派生类中的访问属性。此外,还可以在派生类中声明一个与基类成员同名的成员,则派生类中的新成员会覆盖基类的同名成员。要注意,如果是成员函数的话,不仅要求函数名相同,还要求函数的参数列表也相同。
3、在派生类中增加新的成员
 基类只是提供了最基本的功能,而另有些功能未实现,所以就需要在声明派生类时加入某些具体的功能,形成适用于某一特定应用的派生类。此外,在声明派生类时,一般还有定义适用于派生类的构造函数和析构函数。

四、派生类成员的访问属性
1、基类成员函数只能访问基类成员
2、派生类成员函数可以访问派生类自己增加的成员
3、基类的成员函数不能访问派生类新增加的成员。
4、派生类成员函数在类内或类外访问基类的成员,这种情况比较复杂,它不仅取决于基类成员在基类中的访问属性,还要考虑派生类所声明的对基类的继承方式。


通过上表可以看出,在派生类中有4中不同的访问属性:
1、公用的,在派生类内和派生类外都可以访问。
2、受保护的,派生类内可以访问,派生类外不可以访问。
3、私有的,只能在派生类内进行访问。
4、不可访问的,或者说不能直接访问的,可以通过基类成员函数进行间接访问。

五、派生类的构造函数
  因为基类的构造函数和析构不能通过继承得到,因此,对继承过来的基类成员的初始化工作也要由派生类的构造函数承担,所以就要在执行派生类的构造函数时,调用基类的构造函数。其一般形式为:
      派生类构造函数名(总参数列表):基类构造函数名(参数列表)
      {派生类中新增加数据成员初始化语句}

  基类成员的初始化要通过初始化列表进行初始化。总参数列表中包含了基类构造函数所需要的参数和对派生类自增的数据成员初始化所需要的参数。而基类构造函数名后面的参数列表中只有参数名而不包括参数类型,因为在这里是调用基类的构造函数,这些参数是实参。
例:
classTime
{
public:
                Time(inthour= 0,intminute= 0,intsec= 0) :_hour(hour)
                                , _minute(minute)
                                , _sec(sec)
                {
                }
private:
                int_hour;
                int_minute;
                int_sec;
};


classDate:publicTime
{
public:
                Date(inthour= 0,intminute= 0,intsec= 0,intyear= 0,intmonth= 0,intday= 0) :Time(hour,minute,sec)                            //调用基类的构造函数对基类成员进行初始化
                {
                                _year =year;
                                _month =month;
                                _day =day;
                }
private:
                int_year;
                int_month;
                int_day;
};
有子对象的派生类,对子对象初始化也是同样的方法。

所以派生类构造函数应该包含3个部分:
1、对基类数据成员初始化
2、对子对象初始化
3、对派生类数据成员初始化

  系统时自动调用基类的构造函数的,将派生类的构造函数写成上面那种方式只是为了给基类的构造函数传参而已,假如不写成上面那种形式,系统还是会调用基类的默认构造函数的(子对象也是相同的道理)。系统先调用基类的构造函数再执行派生类的构造函数。

六、派生类中的析构函数
  我们只需要定义派生类中新增成员的构造函数即可。基类的析构函数和子对象的析构函数是系统自动调用的。调用析构函数的顺序与调用构造函数的顺序相反,先执行派生类的析构函数再调用基类的析构函数。

七、多重继承
多重继承:运行一个派生类同时继承多个基类。
1、声明多重继承的方法
  例:如果已经声明了类A、类B、类C,可以声明多重继承的派生类D:
    class D:private A,protected B,public C
    { 类D新增的成员};
  D是多重继承的派生类,它以私有继承方式继承A类,以保护继承的方式继承B类,以公用继承的方式继承C类。

2、多重继承派生类的构造函数
 多重继承派生类的构造函数形式与单继承的构造函数形式基本相同,只是在初始化表中包含多个基类构造函数。如:
    派生类构造函数名(总参数列表):基类1构造函数(参数列表),基类2构造函数(参数列表)...
       {派生类中新增数据成员初始化语句 }; 

3、多重继承引发的问题
多重继承会引发二义性问题,即继承的成员同名,这时引用的话会产生二义性而发生错误。
  例:如果类A和类B中都有数据成员name和成员函数display。如果类C是类A和类B的直接派生类。
  如果在main函数中有:
        C c1;
        c1.name;
        c1.display();
  因为类A和类B都有数据成员name和成员函数display,系统无法判别要访问的是哪个基类的成员,因此编译出错。要解决      这个问题,可以用基类名来限制作用域:
        c1.A::name;
        c1.A::display();     //这样就表示引用的是类A中的成员。

八、虚基类
假如一个派生类有多个直接基类,而这些直接基类又有一个共同的基类。如图:

  那么就会出现一个问题,在类E中会保存3份类A的成员,如果人们只需要一份类A的成员,那么这种情况下就会占用较多的内存空间,还增加访问的难度。而在实际中人们往往只需要一份类A的成员。为了解决这个问题,C++中提供了虚基类的方法,使得在继承间接共同基类时只保留一份成员。
现在将类A声明为虚基类:
class A
{...};
class B:virtual public A    //A是B的虚基类
{...};
class C:virtual public A    //A是C的虚基类
{...};
class D:virtual public A    //A是D的虚基类
{...};
注意:虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。


声明虚基类的一般方法如下:
class 派生类名:virtual 继承方式 基类名

 经过虚基类的声明后,当基类通过多条派生路径被一个派生类继承时,该派生类值继承该派生类一次,即基类成员派生类只保留一次。

1、虚基类的初始化
class A
{
A(int i=0){}
...};

class B:virtual public A    //A是B的虚基类
{
B(int j=2):A(j){}
...};

class C:virtual public A    //A是C的虚基类
{
 C(int k=3):A(k)()
...};

class D:virtual public A    //A是D的虚基类
{
D(int n=4):A(n){}
...};

class E:public B,public C,public D
{
E(int i=0,int j=0,int k=0,int n=0):A(i),B(j),C(k),D(n){}
...};

注意:
  在定义类E的构造函数时,与以往使用的方法有所不同,由于虚基类在派生类中只有一份数据成员,所以这份数据成员必须由派生类之间给出。规定:在最后的派生类中不仅要负责对其之间基类进行初始化,还有负责对虚基类进行初始化。C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类对虚基类构造函数的调用。


九、基类与派生类的转换
  派生类是继承了基类的数据成员,那么基类对象与派生类对象之间是否存在赋值关系,可否进行类型的转换???
  答案是可以的。基类对象与派生类对象之间 有赋值兼容的关系,由于派生类中包含从基类中继承的成员,因此可以将派生类的值赋给基类对象,在使用基类对象的时候可以用派生类对象替代。但是注意,这种关系是单向的,不可逆的。

具体有以下4个方面:
1、派生类对象可以向基类对象赋值,这种关系是单向的。
2、派生类对象可以代替基类对象向基类对象的引用进行赋值或初始化。
3、如果函数的参数是基类对象的或基类对象的引用,相应的实参可以是派生类的对象。
4、派生类的地址可以赋给指向基类对象的指针。即指向基类的指针可以指向派生类的对象。不过,指向的是派生类对象中从基类继承的部分。
2 0
原创粉丝点击