2.3 继承和派生

继承和派生

通过继承可以在一个一般类的基础上建立新类.被继承的类称为基类,在基类上建立的类称为派生类.如果一个类只有一个基类则称为单继承,否则为多继承

单继承

  • 从一个基类定义一个派生类的格式:
class <派生类名>:[<继承方式>]<基类名>
{
    [<派生类的成员>]
};

继承的方式有三种:
* public:公有,没有指定时默认的方式
* private:私有
* protected:保护
继承方式决定了派生类的继承基类属性的使用权限

1.公有继承(public)
  • 公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的
// 派生类的公有继承
#include <iostream>
using namespace std;

class CMeter
{
public:
    CMeter(int nPos = 10)
    {
        m_nPos = nPos;
    }
    ~CMeter(){}
    void StepIt()
    {
        m_nPos++;
    }
    int GetPos()
    {
        return m_nPos;
    }
protected:
    void SetPos(int nPos)
    {
        m_nPos = nPos;
    }
private:
    int m_nPos;
};

class CStick:public CMeter      // 从CMeter派生,公有继承
{
    int m_nStickNum;            // 声明一个私有数据成员
public:
    void DispStick();           // 声明一个公有成员函数
    void SetStick(int nPos)
    {
        SetPos(nPos);       // 从类中调用基类的保护成员
    }
};
void CStick::DispStick()
{
    m_nStickNum = GetPos();     // 调用基类CMeter的成员函数
    cout << m_nStickNum <<" ";
}

int main()
{
    CMeter oMeter(20);
    CStick oStick;
    cout << "CMeter:" << oMeter.GetPos() <<",CStick:" << oStick.GetPos() << endl;
    oMeter.StepIt();
    cout << "CMeter:" << oMeter.GetPos() <<",CStick:" << oStick.GetPos() << endl;
    oStick.StepIt();
    cout << "CMeter:" << oMeter.GetPos() <<",CStick:" << oStick.GetPos() << endl;
    oStick.DispStick();
    oStick.StepIt();
    oStick.DispStick();
    return 0;
}
/**
 * CMeter:20,CStick:10
 * CMeter:21,CStick:10
 * CMeter:21,CStick:11
 * 11 12 
 */
  • 注意:派生类中或派生类的对象可以使用基类的公有成员(包括保护成员),但是基类或基类的对象却不可以使用派生类的成员
2.私有继承(private)
  • 私有继承的特点:基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问
#include <iostream>
using namespace std;

class CMeter
{
public:
    CMeter(int nPos = 10)
    {
        m_nPos = nPos;
    }
    ~CMeter(){}
    void StepIt()
    {
        m_nPos++;
    }
    int GetPos()
    {
        return m_nPos;
    }
protected:
    void SetPos(int nPos)
    {
        m_nPos = nPos;
    }
private:
    int m_nPos;
};

class CStick:private CMeter         // 从CMeter派生,私有继承
{
    int m_nStickNum;                // 声明一个私有数据成员
public:
    void DispStick();               // 声明一个公有成员函数
    void SetStick(int nPos)
    {
        SetPos(nPos);               // 调用基类的保护成员
    }
    int GetStick()
    {
        return GetPos();            // 调用基类的公有成员
    }
};

void CStick::DispStick()
{
    m_nStickNum = GetPos();         // 调用基类CMeter的成员函数
    cout << m_nStickNum << " ";
}

int main()
{
    CMeter oMeter(20);
    CStick oStick;
    cout << "CMeter:" << oMeter.GetPos() << ",CStick:" << oStick.GetStick() << endl;
    oMeter.StepIt();
    cout << "CMeter:" << oMeter.GetPos() << ",CStick:" << oStick.GetStick() << endl;
    oStick.DispStick();

    return 0;
}
/**
 * CMeter:20,CStick:10
 * CMeter:21,CStick:10
 * 10
 */
  • 由于私有继承的派生类对象不能访问基类的所有成员.因此,CStick派生类对象oStick不能调用基类CMeter的GetPos()函数,但在派生类CStick中却可以访问.
保护继承(protected)
  • 特点:基类的所有公有成员和保护成员都称为派生类的保护成员.并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的
  • 注意:派生类的对象和派生类中的成员函数对基类的访问是不同的
继承方式 派生类对象 派生类成员函数
公有继承 基类中公有成员 基类中公有成员,保护成员
私有继承
保护继承
都不行 基类中公有成员,保护成员
继承方式 基类成员 基类的成员在派生类中的特性
公有继承(public) public
protected
private
public
protected
不可访问
私有继承(private) public
protected
private
private
private
不可访问
保护继承(protected) public
protected
private
protected
protected
不可访问

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

  • 基类的构造函数和析构函数不能被派生类继承
  • 当派生类的构造函数和析构函数被执行时,基类相应的构造函数和析构函数也会被执行
  • 派生类对象被建立时,先执行基类的构造函数,然后执行派生类的构造函数
  • 对于析构函数来说,先执行派生类的析构函数,再执行基类的构造函数
#include <iostream>
#include <string.h>
using namespace std;

class CAnimal
{
public:
    CAnimal(char *pName = "noname");
    ~CAnimal();
    void setName(char *pName)
    {
        strncpy(name,pName,sizeof(name));
    }
    char *getName(void)
    {
        return name;
    }
private:
    char name[20];
};

CAnimal::CAnimal(char *pName)
{
    setName(pName);
    cout << "调用CAnimal的构造函数" << endl;
}
CAnimal::~CAnimal()
{
    cout << "调用CAnimal的析构函数" << endl;
}

class CCat : public CAnimal
{
public:
    CCat()
    {
        cout << "调用CCat的构造函数" << endl;
    }
    ~CCat()
    {
        cout << "调用CCat的析构函数"  << endl;
    }
    void DisName()
    {
        cout << "猫的名字是:" << getName() << endl;
    }
};

int main()
{
    CCat cat;
    cat.DisName();
    cat.setName("Snoopy");
    cat.DisName();

    return 0;
}
/**
 * 调用CAnimal的构造函数
 * 调用CCat的构造函数
 * 猫的名字是:noname
 * 猫的名字是:Snoopy
 * 调用CCat的析构函数
 * 调用CAnimal的析构函数
 */
  • 在对派生类进行初始化时,如果需要对其基类设置初值,则可使用下面的格式:
<派生类>(总参表):<基类 1>(参数表 1),<基类 2>(参数表 2),...,<基类 n>(参数表 n),
    对象成员 1(对象成员参数表 1),对象成员 2(对象成员参数表 2),...,
    对象成员 n(对象成员参数表 n)
{
    ...
}
  • 其中,构造函数总参表后面给出的是需要参数初始化的基类名,对象成员名及各自对应的参数表.
  • 基类名和对象成员名之间的顺序可以是任意的
  • 对于使用默认构造函数的基类和对象成员,可以不列出基类名和对象成员名

多继承

  • 在实际的类继承中,还允许一个派生类继承多个基类,这种多继承的方式使得派生类具有多个类的特性.
  • 多继承格式:
class <派生类名> : [<继承方式>]<基类名 1>,[<继承方式 2>]<基类名 2>,...
{
    [<派生类的成员>]
};
  • 除了类的多继承性以外,C++还允许一个基类有多个派生类(称为多重派生),以及从一个基类的派生类中再进行多个层次的派生.

虚基类

  • 基类成员调用的二义性:一般来说,在派生类中对基类成员的访问应该是唯一的,但是,由于在多继承条件下可以造成对基类中某成员的访问出现不唯一的情况,称为基类成员调用的二义性.
#include <iostream>
using namespace std;

class A
{
public:
    int x;
    A(int a=0)
    {
        x = a;
    }
};
class B1:public A
{
public:
    int y1;
    B1(int a=0,int b=0)
        :A(b)
    {
        y1 = a;
    }
};

class B2: public A
{
public:
    int y2;
    B2(int a = 0,int b = 0)
        :A(b)
    {
        y2 =a;
    }
};

class C: public B1,public B2
{
public:
    int z;
    C(int a,int b,int d,int e,int m)
        :B1(a,b),B2(d,e)
        {
            z = m;
        }
        void print()
        {
            cout << "x = " << x << endl;    // 编译出错的地方
            cout << "y1 = " << y1 << ", y2 = " << y2 << endl;
            cout << "z = " << z << endl;
        }
};

int main()
{
    C c1(100,200,300,400,500);
    c1.print();

    return 0;
}

因为B1和B2都是从A中继承,因此在派生类中有两个基类A的拷贝,当编译到cout << "x = " << x << endl;时,无法确定成员x是从类B1中继承而来的,还是从类B2中继承而来的,从而出现编译出错
* 解决办法一:使用 域作用符:: 来消除二义性,即将print函数改为

void print()
{
    cout << "B1::x = " << B1::x << endl;  
    cout << "B2::x = " << B2::x << endl;  
    cout << "y1 = " << y1 << ", y2 = " << y2 << endl;
    cout << "z = " << z << endl;
}

重新运行,结果为:

/**
 * B1::x = 200
 * B2::x = 400
 * y1 = 100, y2 = 300
 * z = 500
 */
  • 解决方法二:使用 虚基类(或称为虚继承).目的:在多重派生的过程中,使公有的基类在派生类中只有一个拷贝.
#include <iostream>
using namespace std;

class A
{
public:
    int x;
    A(int a=0)
    {
        x = a;
    }
};
class B1:virtual public A   // 声明虚继承
{
public:
    int y1;
    B1(int a=0,int b=0)
        :A(b)
    {
        y1 = a;
    }
    void print(void)
    {
        cout << "B1:x = " << x << ",y1 = " << y1 << endl;
    }
};

class B2: virtual public A  // 声明虚继承
{
public:
    int y2;
    B2(int a = 0,int b = 0)
        :A(b)
    {
        y2 =a;
    }
    void print(void)
    {
        cout << "B2:x = " << x << ",y2 = " << y2 << endl;
    }
};

class C: public B1,public B2
{
public:
    int z;
    C(int a,int b,int d,int e,int m)
        :B1(a,b),B2(d,e)
        {
            z = m;
        }
        void print()
        {
            B1::print();
            B2::print();
            cout << "z = " << z << endl;
        }
};

int main()
{
    C c1(100,200,300,400,500);
    c1.print();
    c1.x = 400;
    c1.print();

    return 0;
}
/**
 * B1:x = 0,y1 = 100
 * B2:x = 0,y2 = 300
 * z = 500
 * B1:x = 400,y1 = 100
 * B2:x = 400,y2 = 300
 * z = 500
 */
  • 声明虚基类的格式:
    virtual <继承方式><基类名>
    其中,virtual是声明虚基类的关键字.声明虚基类和声明派生类一道进行,写在派生类的后面
  • 在派生类B1和B2中只有基类A的一个拷贝,当改变成员x的值时,在B1和B2中成员函数输出的成员x的值是相同的
  • 虚基类的构造函数的调用方法和一般基类的构造函数的调用方法是不同的.C++规定,由虚基类经过一次或多次派生出来的派生类,在其每一个派生类的构造函数的成员初始化列表必须给出对虚基类的构造函数的调用,如果未列出,则滴啊用虚基类的默认构造函数.在这种情况下,虚基类的定义中必须由默认的构造函数
发布了40 篇原创文章 · 获赞 55 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/A807296772/article/details/77622011
2.3