类和对象-继承

时间:2023-02-22 11:00:57

继承是面向对象三大特性之一 定义类时,下级别的成员除了拥有上一级的共性,还有自己的特性,就可以考虑使用继承的技术,减少代码的重复

继承的基本语法

语法:class 子类 : 继承方式 父类

  • 子类也被成为派生类
  • 父类也被称为基类
class A
{
public:
    string name;
};
class B :public A
{
public:
    int age;
};
int main()
{
    B b;
    b.name = "张三";
    b.age = 10;
    cout << b.name << b.age << endl;
    return 0;
}

继承方式

继承方式一共有三种:

  • 公共继承
    • 访问权限不变
  • 保护继承
    • 除私有内容外,都变为保护权限
  • 私有继承
    • 除私有内容外,都变为私有权限

父类中的私有内容,三种继承方法都无法访问

class A
{
public:
    int a;
protected:
    int b;
private:
    int c;
};
class B :public A//公共继承
{

};
class C :protected A//保护继承
{

};
class D :private A//私有继承
{

};

继承中的对象模型

父类中所有非静态成员属性都会被子类继承下去 父类中私有的成员属性,是被编译器给隐藏了,因此是访问不到,但是确实被继承下去了 利用开发人员命令提示工具查看对象模型:

  1. 跳转盘符:盘符:
  2. 跳转文件路径:cd 具体路径下
  3. 查看命名:dir
  4. 报告单个类的布局:cl /d1 reportSingleClassLayout类名 文件名

文件名可按Tap建自动补齐

class A
{
public:
    int a;
protected:
    int b;
private:
    int c;
};
class B :public A//公共继承
{
    int c;
};
class B size(16):
        +---
 0      | +--- (base class A)
 0      | | a
 4      | | b
 8      | | c
        | +---
12      | c
        +---

继承中构造和析构顺序

先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反

继承同名成员处理方式

子类对象可以直接访问到子类中的同名成员 子类对象加作用域可以访问到父类同名成员 当子类与父类拥有同名的成员函数,子类会隐藏父类中所有同名成员函数,加作用域可以访问到父类中同名函数

class A
{
public:
    void test()
    {
        cout << "A" << endl;
    }
};
class B :public A//公共继承
{
public:
    void test()
    {
        cout << "B" << endl;
    }
};
int main()
{
    B b;
    b.test();
    b.A::test();
    return 0;
}

继承同名静态成员处理方式

静态成员跟非静态成员出现同名,处理方法一致,只不过有两种处理方法:

  • 通过对象.
  • 通过类名::
class A
{
public:
    static string a;
};
class B :public A//公共继承
{
public:
    static string a;
};
//类内声明,类外初始化
string B::a = "B";
string B::A::a = "A";
int main()
{
    //通过对象访问
    B b;
    cout << b.a << endl;
    cout << b.A::a << endl;
    //通过类名访问
    cout << B::a << endl;
    //第一个::表示通过类名方式访问,第二个::代表访问父类作用域下
    cout << B::A::a << endl;
    return 0;
}

多继承语法

C++允许一个类继承多个类 语法:class 子类 : 继承方式 父类1 , 继承方式 父类2... 多继承可能会引发父类中有同名成员出现,需要加作用域区分 C++实际开发中不建议用多继承

class A
{
public:
    int a;
};
class B
{
public:
    int a;
};
class C :public A, public B
{
};
int main()
{
    C c;
    c.A::a = 10;
    c.B::a = 20;
    cout << c.A::a << endl;
    cout << c.B::a << endl;
    return 0;
}

菱形继承

菱形继承概念:

  • 两个派生类继承同一个基类
  • 又有某个类同时继承这两个派生类
  • 这种继承被称为菱形继承,也被称为钻石继承

典型的菱形继承问题: 类和对象-继承 菱形继承问题:子类继承两份相同的数据,导致资源浪费以及毫无意义

  • 羊继承了动物的数据,驼也继承了动物的数据,当羊驼使用数据时,就会产生二义性
  • 羊驼继承自动物的数据继承了两份,只需要一份就可以

利用虚继承,解决菱形继承的问题:

  • 继承之前,加上关键字virtual变为虚继承
  • 公共的父类被称为虚基类
class A
{
public:
    int a;
};
//A为虚基类
class B :virtual public A{};
class C :virtual public A{};
class D:public B,public C{};
int main()
{
    D d;
    d.a = 10;
    cout << d.a << endl;
    return 0;
}

vbptr虚基类指针:

  • v-virtual
  • b-base
  • ptr-pointer

虚基类指针指向vbtable虚基类表

  • 实际继承了两个指针,通过偏移量,找到那份唯一的数据
class D size(24):
        +---
 0      | +--- (base class B)
 0      | | {vbptr}
        | | <alignment member> (size=4)
        | +---
 8      | +--- (base class C)
 8      | | {vbptr}
        | | <alignment member> (size=4)
        | +---
        | <alignment member> (size=4)
        +---
        +--- (virtual base A)
16      | a
        +---

D::$vbtable@B@:
 0      | 0
 1      | 16 (Dd(B+0)A)

D::$vbtable@C@:
 0      | 0
 1      | 8 (Dd(C+0)A)
vbi:       class  offset o.vbptr  o.vbte fVtorDisp
               A      16       0       4 0