C++继承概念——权限,对象模型,菱形虚拟继承

什么是继承?

继承是面向对象设计的重要部分,允许在原有类特性上进行扩展和代码复用!
表示 is a 的概念。鸵鸟是一种鸟,鸵鸟就可以继承鸟这个类,但是鸟会飞,鸵鸟不会飞,这时候派生类继承了自己没有的属性是不合理的。更严谨——当B是A的一种,且有A的全部属性,这时候才适合继承。
然而,使用场景更广泛的是 has a ,也就是组合的形式。头由眼耳口鼻组成,而不是说头是眼耳口鼻的一种。

访问权限和继承权限

访问权限是指对于本类或者对象的成员来说的,继承权限是在访问权限的基础上谈的,这一点很容易理不清。

访问权限

private: 只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问.

protected: 可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问

public: 可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问

其中protected的作用是不想让成员在类外被使用,但是可以让子类使用,因此可以说在继承中才能体现其作用。

继承权限

因为派生类完全继承了基类的成员变量,所以现在讨论继承过来的成员的权限问题。

public:
public和protected成员权限在派生类没有改变,private在派生类不可见
protected:
public降级为protected, protected不变,private不可见
private:
public降级为private,protected降级为private,private不可见

如何证明子类继承了一个protected权限的成员?

分别在子类内部访问成员,在类外用对象访问成员。

其中public继承最为常用,每个父类成员对子类也可用, 子类 IS-A 父类。

class和struct的默认继承权限分别是private和public。

派生类默认的成员函数

与一般类一样,在需要时编译器合成相应的六个成员函数。

  1. 基类没有定义构造函数或者有缺省构造,则派生类不需要显式定义。
class Date {
public:
    //缺省构造
    Date()
    {
        _day = 1;
    }
    Date(int)
    {
        _day = 2;
    }
private:
    int _day;
};

class Time :public Date
{
public:
    //假如不写,编译器也会合成默认构造
    Time()
    :Date()//隐式调用
    {
    }
private:
    int _time;
};
  1. 如果基类只有带参构造函数,则派生类必须在构造函数参数列表调用基类构造函数。
class Date {
public:
    Date(int)
    {
        _day = 2;
    }
private:
    int _day;
};

class Time :public Date
{
public:
    Time()
        :Date(1)
    {
    }
private:
    int _time;
};

构造函数的调用顺序

  1. 基类构造函数
  2. 成员类对象构造函数(多个按声明顺序)
  3. 派生类构造函数

赋值兼容规则

由于构造函数是先父后子,自然在内存中的模型就是先放父成员后放子成员

复制兼容原则是大的可以赋给小的,小权限指针可以指向大权限指针。

翻译一下就是派生类可以切割给基类对象赋值,父类指针可以限制的指向子类。

基类那些成员被派生继承了?友元可以继承吗?

除了基类的构造,析构,赋值函数没有被继承,其他成员被继承。

派生类中访问基类的私有成员只能通过基类public或protect函数访问。

因此在写派生类需要注意
1. 一条铁则:编译器总是要构造父类对象的,若父类有默认构造,则调用默认构造。若没有默认构造,则需要在派生类初始化列表显示调用父类构造。
2. 基类虚析构(多态导致派生类不能正常析构)
3. 赋值函数里调用父类赋值函数初始化父类成员(不能操作父类私有,也没有父类构造)

static成员因为在静态区,父类的static变量和函数在派生类中依然可用,但是受访问性控制。

友元关系:父亲的朋友不是儿子的朋友。

菱形继承和虚拟继承

菱形继承的模型:基类A,派生类B1,B2都继承A,C继承B1和B2.

缺陷:C类里有两份A类成员,分别从B1B2继承来的,存在二义性,必须用类限定符访问

class B {
public:
    int _b;
};
class C1 :virtual public B {
public:
    int _c1;
};
class C2 :virtual public B {
public:
    int _c2;
};
class D :public C1,public C2 {
public:
    int _d;
};

int main()
{
    cout << sizeof(C1) << endl;
    cout << sizeof(D) << endl;
    C1 c;
    c._b = 5;
    c._c1 = 6;
    D d;
    d._b = 1;
    d._c1 = 2;
    d._c2 = 3;
    d._d = 4;
    system("pause");
}

虚拟继承——声明virtual

0x004FFD54  30 7b 93 00  
0x004FFD58  06 00 00 00  
0x004FFD5C  05 00 00 00

通过C1的对象模型可以看到前4字节存放一个指针,然后先放派生类成员再放基类成员。

虚拟菱形继承

0x004FFD34  e0 7b 93 00  ?{?.
0x004FFD38  02 00 00 00  ....
0x004FFD3C  e8 7b 93 00  ?{?.
0x004FFD40  03 00 00 00  ....
0x004FFD44  04 00 00 00  ....
0x004FFD48  01 00 00 00

虚拟菱形继承D的模型,先按继承顺序存放C1,C2,再放本类,最后放基类成员。

虚拟菱形继承实现了基类成员仅存放一份,消除二义性。

怎么实现的?

虚拟继承的派生类对象前四字节存放指针,指针指出了派生类相对基类的偏移量,从而两个派生类相对于基类成员有了不同的偏移,可以保证访问到同一个基类对象。这里的偏移量是在构造函数里填充的,编译器自动合成默认构造。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值