继承与派生

类具有封装性、继承性和多态性

继承的概念

类的继承是在现有类的基础之上,创建新类的机制。称现有的类为基类,新建立的类为派生类。
a.新类继承了基类的属性和行为
b.新类是基类的特殊情况。
不必从“草稿”开始创建特殊的程序对象. 继承是处理“特殊情况”的面向对象编程机制

派生类的定义格式:
派生类只有一个直接基类为单继承
class 派生类名:继承方式 基类名
{
public:
     //派生类公有成员…
private:
     //派生类私有成员…
}
派生类有多个直接基类为多继承
class 派生类名:继承方式 基类名1,…,继承方式 基类名n
{
public:
     //派生类公有成员…
private:
     //派生类私有成员…
}
注意
a.每一个“继承方式” ,只用于限制对紧随其后之基类的继承。
b.类的继承方式是派生类对基类成员的继承方式。
c.类的继承方式指定了类外对象对于派生类从基类继承来的成员的访问权限

父类被称为子类的直接基类
父类的父类或更高层次的父类被称为这个子类的间接基类
class base
{……};
class deriver1:public base
{……};
class deriver2:public deriver1
{……};

派生类的成员包括:(1)继承基类的成员,(2)派生类定义时声明的成员。
派生类自己增加的成员,完成两个需求:(1)修改基类成员,(2)描述新的特征或方法。


继承方式

三种继承方式:public、protected、private
不同继承方式的影响主要体现在:a.派生类成员对基类成员的访问控制;b.派生类对象对基类成员的访问控制。
定义派生类时要声明继承方式

1.同名覆盖:

派生类修改基类的成员,是在派生类中声明了一个与基类成员同名的新成员。在派生类作用域内或者在类外通过派生类的对象直接使用这个成员名,只能访问到派生类中声明的同名新成员,这个新成员覆盖了从基类继承的同名成员,这种情况称为同名覆盖。
//同名覆盖示例
#include<iostream >
using namespace std ;
class base
{
public:
      void function(){ cout<<"function of class base"<<endl; }
};
class deriver : public base
{
public:
      void function(){ cout<<"function of class deriver"<<endl; }
};
void main()
{
      deriver derobj;
      derobj. function();
}

输出结果:
function of class deriver

2.继承的访问控制:

派生类继承了基类中除构造函数和析构函数之外的所有成员
不同的继承方式使得派生类从基类继承的成员具有不同的访问控制权限,以实现数据的安全性和共享性控制。
不同继承方式决定的不同访问控制权限体现在:派生类的成员函数对其继承的基类成员的访问控制;其它模块通过派生类对象对其继承的基类成员的访问控制

a.公有继承

public是定义公有继承方式的关键字
     公有继承方式定义的派生类,继承了基类中除构造函数和析构函数外的其余成员:公有成员、保护成员和私有成员,被继承的基类成员在派生类中仍将保持其原来的访问属性。
     派生类的成员函数可以访问基类的公有成员和保护成员,不能访问基类的私有成员;
     派生类以外的其它函数可以通过派生类的对象,访问从基类继承的公有成员, 但不能访问从基类继承的保护成员和私有成员。
派生类中的成员函数可以直接访问基类中的public和protected成员,但不能访问基类的private成员。
使用派生类的对象只能访问基类的public成员。

b.保护继承

protected是定义保护继承方式的关键字
     以保护继承方式定义的派生类,继承了基类中可以继承的成员:公有成员、保护成员和私有成员,其中基类的公有成员和保护成员在派生类中访问控制属性变成保护类型的,基类的私有成员保持原来属性
     派生类的成员函数可以访问基类的公有成员和保护成员,不能访问基类的私有成员。
     派生类以外的其它函数则不能通过派生类的对象访问从基类继承的任何成员。
派生类中的成员函数可以直接访问基类中的public和protected成员,但不能访问基类的private成员。
使用派生类的对象不能访问基类中的任何成员。

c.私有继承

private是定义私有继承方式的关键字
     以私有继承方式定义的派生类,继承了基类中可以继承的成员:公有成员、保护成员和私有成员,这些成员在派生类中的访问属性都是私有的。
     派生类的成员函数可以访问基类的公有成员和保护成员,不能访问基类的私有成员。
     派生类以外的其它函数则不能通过派生类的对象访问从基类继承的任何成员。
派生类中的成员函数可以直接访问基类中的public和protected成员,但不能访问基类的private成员。
使用派生类的对象不能访问基类中的任何成员。

总结:
不同的继承方式不影响派生类的成员函数对从基类继承的成员的访问,即派生类中的成员函数可以直接访问基类中的public和protected成员,但不能访问基类的private成员;
不同的继承方式对类外其它函数通过派生类对象访问从基类继承的公有成员有不同的影响:只有公有继承方式时通过派生类对象可以访问从基类继承的公有成员,其它继承方式下,不能访问从基类继承的任何成员;
在不同的继承方式下,基类成员被继承到派生类后,访问属性有不同的变化,如下表:

继承\存取publicprotectedprivate
public继承publicprotectedprivate
protected继承protectedprotectedprivate
private继承privateprivateprivate


派生类构造函数的定义

派生类继承了基类中除构造函数和析构函数之外的所有成员。
基类的构造函数和析构函数不能被派生类所继承,派生类需要自己定义的构造函数和析构函数。
派生类构造函数的一般形式:
派生类名::派生类名(基类所需的形参, 本类成员所需的形参):基类1(基类参数表1),…,基类n(基类参数表n),对象成员1(对象参数表1),…,对象成员m(对象参数表m)
{
     本类基本类型数据成员初始化;
}
class base1
{
private:
        int m_base_data;
public :
        base1( int data){m_base_data= data;}
        //…
};
class base2
{
private:
        int m_base_data;
public :
        base2( int data){m_base_data= data;}
        //…
};
class Abc
{
private:
        float m_abc_data;
public :
        Abc( float data){ m_abc_data= data; }
        //…
};
class deriver :public base1 ,  public base2
{
private:
        Abc m_member1,  m_member2;
        double m_deriver_data;
public :
        deriver( int bd1 , int bd2 , float id1 , float id2 , double dd);
};
deriver::  deriver (int bd1 , int bd2 , float id1 , float id2 , double dd ) : base1(bd1 ), base2 (bd2 ), m_member1(id1), m_member2( id2)
{
       m_deriver_data= dd;
}
//使用基类无参构造函数
deriver :: deriver ( float id1 , float id2 , double dd ) : m_member1 ( id1 ),  m_member2 ( id2 )
{
       m_deriver_data= dd;
}
//使用对象数据成员的无参构造函数
deriver ::  deriver ( int bd1 , int bd2 , double dd ) : base1 ( bd1 ),base2 ( bd2 )
{
       m_deriver_data= dd;
}

如果基类和对象数据成员的构造函数都无参数,派生类构造函数形参表中将只包含用于初始化它自己的基本类型数据成员的参数。
如果这个派生类恰好没有基本类型的数据成员,则其构造函数的形参表为空,可以不定义构造函数,而使用系统提供的默认构造函数。

基类的构造函数不被继承,需要在派生类中自行定义。
定义构造函数时,以合适的初值为参数,初始化本类中新增成员。
利用成员初始化表隐含调用基类和新增对象数据成员的构造函数,初始化它们各自的数据成员。
构造函数的调用次序:(系统会使用派生类构造函数的形参表的参数调用基类和内嵌对象成员的构造函数)
a.系统在建立派生类对象时,首先调用基类的构造函数,再调用派生类的构造函数。
b.系统在建立组合类对象时,先调用内嵌子对象的构造函数,在调用组合类的构造函数。
c.如果一个派生类又是组合类,则系统先调用其基类的构造函数,再调用其内嵌子对象的构造函数,再后系统才调用派生类的构造函数
d.如果是多继承,系统调用基类构造函数的顺序是按照定义派生类时这些基类被继承的顺序进行的,与这些基类构造函数在派生类构造函数成员初始化列表的先后次序无关。
e.如果派生类有多个对象数据成员,则系统调用这些对象数据成员的构造函数的顺序是依据派生类定义这些成员的顺序进行的,与派生类成员初始化列表中对象数据成员构造函数排列先后次序无关。


派生类的析构函数
派生类不能继承基类的析构函数,需要自己定义析构函数,以便在派生类对象消亡之前进行必要的清理工作。
派生类的析构函数只负责清理它新定义的非对象数据成员,对象数据成员由对象成员所属类的析构函数负责析构。

如果没有特殊指针数据成员需要清理,可以使用由系统提供的默认析构函数。
当派生类对象消亡时,系统调用析构函数的顺序与建立派生类对象时调用构造函数的顺序正好相反,即先调用派生类的析构函数,再调用其对象数据成员的析构函数,最后调用基类的析构函数


多继承
1.多继承与二义性:
多继承类结构中,派生类可能有多个直接基类或间接基类,充分体现了软件重用的优点,但也可能会引起成员访问的二义性或不确定性问题。

2.虚基类:
为解决二义性问题,将共同基类设置为虚基类,创建派生类对象时,虚基类的构造函数只会调用一次,虚基类的成员在第三层派生类对象中就只有一份拷贝,不会再引起二义性问题
语法形式:
class 派生类名 : virtual 继承方式基类名
{
     //……
}

在多继承情况下,虚基类关键字的作用范围和继承方式关键字相同,只对紧随其后的基类起作用。

在多继承类结构中,说明虚基类之后,虚基类的成员在派生类中将不会因继承关系对虚基类的多次继承而形成多份拷贝,只为最远的派生类提供唯一的基类成员,消除了多继承结构中的二义性问题。
需要注意的是在第一级继承时就要将共同基类设计为虚基类。


3:多继承派生类构造函数的定义:
在包含虚基类的继承结构中,系统在建立派生类的对象时,调用构造函数的顺序是:
(1)首先按照虚拟基类被继承的顺序,调用它们的构造函数;
(2)其次按照非虚拟基类被继承的顺序,调用它们的构造函数;
(3)再次按照对象数据成员声明的顺序,调用它们的构造函数;
(4)最后调用派生类自己的构造函数。

如果基类使用带参的构造函数,则派生类需要在其构造函数的形式参数表中提供相应的参数给基类,对其对象数据成员亦如此。
如果基类和对象数据成员都使用默认构造函数,派生类也没有需要初始化的基本类型数据成员,也可以使用默认构造函数。
析构派生类的对象时,析构函数的调用顺序正好与构造函数的调用顺序相反。

#include<iostream >
using namespace std ;
class base
{
private:
        int m_data;
public :
        base ( int m)
       {  
              m_data= m;
               cout << "base construction" <<endl ;
       }
        ~base (){ cout << "base deconstruction" <<endl ;}
        void setdata( int data ){ m_data= data ;}
        int getdata( int data ){  return m_data;}
};
class Fderiver1 : virtual public base
{
private:
        int m_value;
public :
        Fderiver1( int value, int data ): base ( data )
       {
              m_value= value;
               cout << "Fderiver1 construction" <<endl ;
       }
        ~Fderiver1(){cout << "Fderiver1 deconstruction" << endl ;}
        void setvalue ( int value){  m_value= value;}
        int getvalue (){ return m_value; }
        void fun (){};
};
class Fderiver2 : virtual public base
{
private:
        int m_number;
public :
        Fderiver2( int number, int data ): base ( data )
       {
              m_number= number;
               cout << "Fderiver2 construction" <<endl ;
       }
        ~Fderiver2(){cout << "Fderiver2 deconstruction" << endl;}
        void setnumber ( int number){ m_number = number;}
        int getnumber (){ return m_number; }
        void fun (){};
};
class Sderiver :  public Fderiver1 , public Fderiver2
{
private:
        int m_attrib;
public :
        Sderiver ( int data, int number, int value, int attrib ): base ( data), Fderiver1(value ,data ), Fderiver2 ( number , data )
       {
              m_attrib = attrib;
               cout << "Sderiverc onstruction"<<endl ;
       }
        ~Sderiver (){ cout << "Sderiver deconstruction" <<endl ;}
        void setattrib ( int attrib){ m_attrib = attrib;}
        int getattrib (){ return m_attrib;}
        void newfun1 (){};
        int newfun2 (){};
};
void main ()
{
        int d (1), v(2), n(3), a(4);
        Sderiver object( d, v, n, a);
        object. setdata(7);
}
/*************************
base construction
Fderiver1 construction
Fderiver2 construction
Sderiverc onstruction
Sderiver deconstruction
Fderiver2 deconstruction
Fderiver1 deconstruction
base deconstruction
*************************/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值