C++的继承(inherit)

基本用法

继承能更好的实现代码服用,假如我要实现和父类相类似,但是有要用很多父类的,那么继承就方便。

class A{
	private:
	int pri;
	protected:
	int pro;
	pubulic:
	void func(){
		pri = 10;
	}
};
class B:A
{
}
class C:public A
{
}

如上,B是子类(派生类),A是父类。

成员的继承

普通的成员变量和成员函数
类之间有三种继承关系:公有继承,私有继承,保护继承。
子类会继承父类的所有成员,且继承过来的成员的访问权限,取继承权限和父类成员权限之中更小的那一个。
class 的默认继承权限私有继承,struct的继承权限是公有继承
如上相当于B这个类就是私有继承,那么其继承的三个成员都是是有的。

pri,pro,func 都是私有成员

虽然子类会继承父类的私有成员,但是在子类里,没有对父类private成员的访问或者权限,但是确实子类继承了父类的私有成员,可以通过public成员或者protected成员进行间接访问操作。
静态成员
子类与父类是共用一套静态成员,继承不算很好的描述,但是其继承过来权限是一样的,因为如果
父类的静态成员是private,子类依然是没有资格访问的。

class A{
    private:
    static int a;
};
int A::a = 0;
class B :public A{
    B()
    {
        //A::a++;//这里解除注释就会发现报错。
    }
};

默认成员函数

1.构造函数

子类会自动在初始化列表调用父类构造函数,初始化从父类继承来的成员,或者显示调用传参初始化也可以。(如果父类的构造函数没有全缺省或者无参数,那就需要显示调用父类构造函数去初始化**
且父类构造函数一定是至于子类所有成员初始化之前调用的

//示例代码

class ParentA{
    public:
    ParentA(int a = 10)
    {
        cout<<"ParentA"<<endl;
    }
};
class B:public ParentA
{
    public:
    B()
    :ParentA(10)        //类的构造函数里面,应该实现父类会自动调用初始化,如果需要显示调用就要如此写(编译器自动生成的也会如此做
                        //例如父类构造函数不是没有全缺省和无参数构造函数,就必须要显示调用
                        //且父类构造函数总是在之前调用
    {
        cout<<"B"<<endl;
    }
    int b;
};

2.拷贝构造函数

一样的具备构造函数的特性,会有初始化列表,也会在初始化列表最前端去调用一次父类构造函数,值得注意的是拷贝构造函数,其默认的初始化列表式中调用的父类构造函数那一部分,没写就调用的是默认的构造函数了。这就导致,基类给基类拷贝构造的时候,是不会存在父类那一部分对象相同的。

示例代码,打断点监视即可观察。

class ParentA{
    public:
    ParentA(int a = 10)
    {
        cout<<"ParentA"<<endl;
    }
    ParentA(const ParentA& pa)
    {
        cout<<"ParentA(const ParentA& pa)"<<endl;
    }

    int c;
};
class B:public ParentA
{
    public:
    B()
    :ParentA(10)        //类的构造函数里面,应该实现父类会自动调用初始化,如果需要显示调用就要如此写(编译器自动生成的也会如此做
                        //例如父类构造函数不是没有全缺省和无参数构造函数,就必须要显示调用
                        //且父类构造函数总是在之前调用
    {
        cout<<"B"<<endl;
    }
    // B(const B& b)
    //  //:ParentA(b)//拷贝构造如果自己不调用,就调用父类的默认构造函数
    // {
    //     cout<<"B(const B& b)";
    // }

    int b;
};
void test_1()
{
    B b;
    b.a = 10;
    B c(b);
}

3.赋值重载

赋值重载,编译器自动生成的赋值重载,只会对子类自己单有的成员进行(浅拷贝)赋值不会对父类的成员进行赋值,需要对父类的赋值,如果想要完成赋值,就要自己写赋值重载,调用父类的赋值重载

//如果你想验证,把B类的赋值重载注释掉即可。

class ParentA{
    public:
    ParentA(int a = 10)
    {
        cout<<"ParentA"<<endl;
    }
    ParentA(const ParentA& pa)
    {
        cout<<"ParentA(const ParentA& pa)"<<endl;
    }
    ParentA operator=(const ParentA& pa)
    {
        cout<<"ParentA operator="<<endl;
        a = pa.a;
        return *this;
    }
    ~ParentA(){
        cout<<"~ParentA"<<endl;
    }
    int a;
    private:
    int A_b;
    // static int count;

    protected:
    // static int count;//静态成员变量属于整个类,子类不谈继承一说,子类也可以正常访问,但是需要protect或者是公有

    int c;
};
class B:public ParentA
{
    public:
    B()
    :ParentA(10)        //类的构造函数里面,应该实现父类会自动调用初始化,如果需要显示调用就要如此写(编译器自动生成的也会如此做
                        //例如父类构造函数不是没有全缺省和无参数构造函数,就必须要显示调用
                        //且父类构造函数总是在之前调用
    {
        cout<<"B"<<endl;
    }
    B& operator=(const B& b)//编译器自动生成的也会去调用父类的赋值重载
    {
        if(this==&b)
        {
            return *this;
        }
        this->ParentA::operator=(b);//这里要主动去调用父类的赋值重载,所以在赋值时,需要调用父类的赋值重载才行
        //已经做深拷贝
        cout<<"B& operator=(const B& b)"<<endl;
        return *this; 
    }
    B(const B& b)
     //:ParentA(b)//拷贝构造如果自己不调用,就调用父类的默认构造函数
    {
        cout<<"B(const B& b)";
    }
    }
    int b;
};

4.析构函数

子类的析构函数,只需要完成对子类那一部分的处理即可,当子类的析构函数调用完,会自动调用父类的析构函数,就不需要自己再调用,否则会出现不可知的错误。

//示例代码,你可以去main函数创建子类,利用调试观察。

class ParentA{
    public:
    ParentA(int a = 10)
    {
        cout<<"ParentA"<<endl;
    }
    ~ParentA(){
        cout<<"~ParentA"<<endl;
    }
    int a;
    private:
    int A_b;
    // static int count;

    protected:
    // static int count;//静态成员变量属于整个类,子类不谈继承一说,子类也可以正常访问,但是需要protect或者是公有

    int c;
};
class B:public ParentA
{
    public:
    B()
    :ParentA(10)        //类的构造函数里面,应该实现父类会自动调用初始化,如果需要显示调用就要如此写(编译器自动生成的也会如此做
                        //例如父类构造函数不是没有全缺省和无参数构造函数,就必须要显示调用
                        //且父类构造函数总是在之前调用
    {
        cout<<"B"<<endl;
    }
    ~B(){
        //析构函数调用时,会优先调用子类的析构
        //ParentA::~ParentA();但是子类会自动调动父亲构造函数,所以就不需要在调用,否则容易出现问题
    }
    int b;
};

虚继承

C++的继承里面,有一种继承方式是虚继承,为了解决菱形继承的缘故。

菱形继承代码。

class A{
    protected:
    int m_a = 0;
};
class B:public A{
};
class C:public A{

};
class D:public B,public C{
    public:
    int m_d = 0;
};

菱形继承示意图。
在这里插入图片描述
上面就是菱形继承,会出现一种十分不妙的情况,1.D里面有两份A的数据分别来自与B和C(B和C从A那里继承来的)2.同时也导致在调用A类里面的m_a成员时,出现二义性情况。
第二种情况实际影响不算太大,可以通过类域去调用。如下:

	D d;
    d.B::m_a = 10;
    d.C::m_a = 10;

但是依然没有解决数据冗余的情况,为此CPP里面搞出了一种虚继承。使得在孙子的衍生类之中,只会有一份祖父基类的成员。
实现方法:实现虚继承,只需在会重复出现的祖父类的子类加上virtual,就可以在后续多继承使用不同类来自同一个父类的时候,孙子类或者更往下不会存在多份数据等情况。

使用示例:

class A{
    public:
    int m_a = 0;
};
class B:virtual public A{
};
class C:virtual public A{

};
class D:public B,public C{
    public:
    int m_d = 0;
};

在这里插入图片描述
VS的实现中,是把属于父类的那一部分成员,给放到了整个类的最底层。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值