C++ 继承下(二篇文章学习继承所有知识点)

5.继承与友元

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员

//验证友元不能继承
class B
{
    friend void Print();
public:
    B(int b)
        : _b(b)
    {
        cout << "B()" << endl;
    }

protected:
    int _b;
};


class D : public B
{
public:
    D(int b, int d)
        : B(b), _d(_d)
    {
        cout << "D(int,int)" << endl;
    }

protected:
    int _d;
};

void Print()
{
    B b(1);
    cout << b._b << endl;

    //结论:友元关系不能被继承
    //创建一个子类对象,如果子类对象D中的_d不能被访问,说明友元不能被继承
    D d(1, 2);
    //cout << d._d << endl;   编译失败
}

6. 继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例

// 静态成员可以被子类继承,并且在整个继承体系中只存在一份
class B
{
public:
    static int _b;
};

int B::_b = 10;

class D : public B
{
protected:
    int _d;
};

class DD : public B
{
protected:
    int _d;
};

int main()
{
    cout << D::_b << endl;
    cout << DD::_b << endl;

    cout << &B::_b << " " << &D::_b <<" "<<&DD::_b << endl;
    return 0;
}

地址一样,成功继承。说明静态成员可以被子类继承,并且在整个继承体系中只存在一份

7.不同继承方式下子类的对象模型

对象模型:对象中的成员在内存中的存储方式

单继承

一个子类只有一个直接父类时称这个继承关系为单继承

多继承

一个子类有两个或以上直接父类时称这个继承关系为多继承

注意:如果有多个基类,每个基类前必须要添加继承权限,否则就是默认的继承权限

菱形继承(钻石继承)

菱形继承是多继承的一种特殊情况

class B
{
public:
     int _b;
};

class C1 : public B
{
public:
    int _c1;
};

class C2 : public B
{
public:
    int _c2;
};

class D : public C1,public C2
{
public:
    int _d;
};
int main()
{
    cout << sizeof(D) << endl;

    D d;
    return 0;
}

对上面代码进行如下操作:

菱形继承继承的二义性问题:

d._b = 1; // 编译失败:error C2385: 对“_b”的访问不明确

d.func(); // 编译失败:error C2385: 对“_b”的访问不明确

错误分析:

* D从C1中继承下来一个_b,从C2中也继承下来一个_b,导致D的模型中实际有2个_b

* 如果通过D的对象直接访问_b,编译器就不知道到底要访问从C1中继承下来的_b还是要访问

* 从C2中继承下来的_b

* 因此存在二义性

* 即:菱形继承的缺陷 因此:一般情况下尽量避免设计出菱形继承方式

菱形继承二义性问题解决方式:

1. 让访问明确化: 在成员前加类名以及作用域限定符

d.C1::_b = 1;

d._c1 = 2;

d.C2::_b = 3;

d._c2 = 4;

d._d = 5;

上述解决方式只是让代码通过编译了,但是最顶层基类中的成员在子类中仍旧是存在多份 /

不足:最顶层基类中的成员在子类中仍旧是存在多份,浪费空间,而且二义性并没有真正的解决

如果要真正解决,让最顶层类中的成员在子类(类型继承中最底下的类)只存储一份,在C++中引入了菱形虚拟继承解决上述问题

虚拟继承

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。需要注意的是,虚拟继承不要在其他地方去使用

注意:平时在写继承的代码时,不会涉及虚拟继承的方式

虚拟继承只有一个作用:解决菱形继承二义性的问题

在继承权限前加上虚拟关键字即可

class B
{
public:
    int _b;
};

class D : virtual public B
{
public:
    int _d;
};

int main()
{
    cout << sizeof(D) << endl;   // 12, 普通继承是8

    D d;
    d._b = 1;
    d._d = 2;   // 直接一条mov指令搞定
    return 0;
}

利用菱形虚拟继承解决菱形继承中二义性问题

class A
{
    public:
     int _a;
};
// class B : public A
class B : virtual public A
{
    public:
     int _b;
};
// class C : public A
class C : virtual public A
{
    public:
     int _c;
};
class D : public B, public C
{
public:
 int _d;
};
int main()
{
     D d;
     d.B::_a = 1;
     d.C::_a = 2;
     d._b = 3;
     d._c = 4;
     d._d = 5;
     return 0;
}

D对象中将A放到的了对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A

8.继承的总结和反思

1. C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱

形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设

计出菱形继承。否则在复杂度及性能上都有问题。

2. 多继承可以认为是C++的缺陷之一,很多后来的OO语言都没有多继承,如Java。

3. 继承和组合

public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。

组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象

猜你喜欢

转载自blog.csdn.net/weixin_59215611/article/details/129545101