C++——继承(单继承、多继承、菱形继承)&&虚继承&&虚基类

首先,我们得知道,面向对象的三大语言:封装 继承 多态

今天我们主要谈谈继承

1.什么是继承

——子类(派生类)可以访问和使用父类(基类)的成员

比如:有两个类,A和B,我们在定义时,使得B 可以访问 A 的成员,我们叫做 B继承了A。
B为子类,(派生类);A为父类(基类)

2.为什么要用继承呢 ?

代码分析:

#include<iostream>
using namespace std;

class A
{
public:
    void fun1(){}
    void fun2(){}
};

class B :public A
{
public:
    void fun3(){}
    void fun4(){}
};

 int main()
{
    B b;
    b.fun1();
    b.fun2();
    b.fun3();
    b.fun4();
    return 0;

}

继承是为了提高程序的复用性。
如上述代码,不需要额外定义fun1( )和fun2(),直接从父类继承即可。

3.三种继承关系

  • 成员访问限定符&继承关系:

这里写图片描述

  • 三种继承关系下基类成员在派生类的访问关系变化:

这里写图片描述

  • 问题1:为什么已经有了私有成员还要有保护成员?

答:基类的私有成员在派生类中是无法被访问的,但如果一些基类成员,不想被基类对象直接访问,需要在派生类中访问,就定义为保护成员。保护成员限定符是为了继承而生的。

  • 问题2:public / protected / private继承之间的区别是什么?
public继承是一个接口继承,保持 is-a 原则,(我是一个你)
每个父类可用的对象对子类也可用,每个子类对象也是一个父类对象。

protected / private 继承是一个实现继承,是 has -a 的关系原则,(我有一个你)
基类的部分成员,并未完全成为子类接口的一部分。
  • 问题3:什么是成员不可见?

    答:不管是哪种继承方式,在派生类内部都可以访问基类的共有成员和保护成员,基类的私有成员存在,但是在派生类中无法访问。(不可见)

  • 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。


4.继承规则

  • A B两个类不相关,不能继承,能发生继承关系的两个类必须具有相关性。
  • 从逻辑上看,B是A 的一种,可以继承(比如:男人是人的一种,男人可以继承人的特性)
  • 从逻辑上看,B是A 的一种,并且 A 的功能和属性对B 有意义,才可以继承。(比如:鸵鸟是鸟的一种,但是鸵鸟不会飞,鸵鸟将这个功能继承过来毫无意义)


5.继承与转换——赋值兼容规则——public继承

  • 子类对象可以赋值给父类对象(切割/切片)
  • 父类对象不可以赋值给子类对象
  • 父类的指针/ 引用可以指向子类对象
  • 子类的指针/ 引用不能指向父类对象(可以通过强制类型转换实现)

代码分析:

class Person
{
public:
    void Show()
    {
        cout<<_name << endl;

    }
protected:
    string _name;
private:
    int _age;

};






//class Student :private Person
//class Student :protected Person

class Student :public Person
{
public:int _num;
};

int main()
{
    Person p;
    Student s;

    p = s;//1.子类对象可以直接赋值给父类对象(切割/切片)

    //s = p;//2.父类对象不能直接赋值给子类对象(空间不足)


          //3.父类的指针/引用,可以指向子类对象
    Person *p1 = &s;

    Person&r = s;//不加const也能引用,——天然支持


          //4.子类的指针/引用不能指向父类的对象(但可以通过强制转换实现)
    Student*p2 = (Student*)&p;
    Student&r2 = (Student&)p;

    //const Student &r2 = p;//编不过:天然不支持





    system("pause");
        return 0;

}

这里写图片描述
这里写图片描述

6.继承体系中的作用域:

  • 在继承体系中,基类和派生类都有独立的作用域
  • 子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问。
    (在子类成员函数中,可使用基类::基类成员 )访问 ——隐藏(重定义)

  • 注意:在实际中最好不要定义同名成员。



7.单继承&&多继承&&菱形继承

  • 单继承:一个子类只有一个直接父类时称这个继承关系为单继承。
  • 多继承:一个子类有两个或两个以上直接父类称这个继承关系为多继承。
    这里写图片描述

  • 菱形继承
    这里写图片描述

菱形继承存在问题:二义性和 数据冗余(虚继承解决)
这里写图片描述

**注:
1.虚基表中存的是一个偏移量(相对地址,而不是实际地址)**
2.同类型对象共享一个虚基表(偏移量相同)
3.虚基表的使用:节省空间



8.虚继承

代码分析:

//不是虚继承时
#include<iostream>
using namespace std;

class A
{
public:
    int _a;
};

class B:public A
{
public:
    int _b;
};

class C :public A
{
public:
    int _c;
};

class D:public B,public C
{
public :
    int _d;
};

int main()
{
    D dd;
    cout << sizeof(dd) << endl;
    dd.B::_a = 1;
    dd._b= 3;
    dd.B::_a = 2;
    dd._c = 4;
    dd._d = 5;

    B bb;
    C cc;
    system("pause");
    return 0;


}

运行结果:
这里写图片描述

结果分析:
D中一共只有四个变量,但此时sizeof 的结果为20,也就是说有5个值,那这五个值是从哪来的呢?
——实际上D继承了两个_a, B继承的_a 和 A继承的_a是不一样的,无法确定哪一个值应该是_a,所以系统默认有两个_a。(有歧义)

在监视窗口上看到,程序的执行步骤为:

  1. B中的_a 变成 1
  2. D中的_b 变成 3
  3. C中的_a 变成 2
  4. D中的_c 变成 4
  5. D中的_d 变成 5


当继承方式为虚继承时:

class B:virtual public A
{
public:
    int _b;
};

class C :virtual public A
{
public:
    int _c;
};

当继承方式为虚继承时,程序的执行过程为:

  1. B中的_a 和 C中的 _a 同时变为1
  2. D中的_b 变成 3
  3. B中的_a 和 C中的 _a 同时变为2
  4. D中的_c 变成 4
  5. D中的_d 变成 5

注:此时A 叫做虚基类,当子类对父类的继承方式为虚继承时,父类就叫做虚基类。

虚基类的作用:当一个基类被声明为虚基类后,即便它被多次继承,最后的派生类中也只有它的一个备份
参照上述代码,A被声明为虚基类,D 既被B继承,又被 C继承 。但最后的派生类D中只有一个_a
——如此便解决了数据冗余和二义性的问题

阅读更多

没有更多推荐了,返回首页