欢迎光临
我们一直在努力

C/C++ 基础笔记(十三)继承

本篇核心知识:继承与派生、继承方式、成员权限变化、构造 / 析构顺序、同名成员处理、多继承、菱形继承、虚继承、对象赋值兼容规则


一、继承与派生(概念)

概念

继承是从已有类(父类 / 基类)快速创建新类(子类 / 派生类)的机制,子类会获得基类成员,并可扩展新成员。

特性

目的:代码复用、快速扩展、层次化设计。

子类 = 基类成员 + 新增成员。

不能被继承:构造函数、析构函数、赋值运算符重载、友元。

语法

class 子类 : 继承方式 基类1, 继承方式 基类2…
{
  新增成员;
};

三种继承分类

  • 单继承:一个子类只有一个直接基类。

  • 多继承:一个子类有多个直接基类。

  • 多级继承:子类再派生子类(爷→父→子)。


  • 二、继承方式与成员权限

    概念

    继承方式决定基类成员在子类中的最终访问权限。

    三种继承方式

  • public(公有继承)

    基类 public → 子类 public

    基类 protected → 子类 protected

    基类 private → 子类不可访问

  • protected(保护继承)

    基类 public → 子类 protected

    基类 protected → 子类 protected

    基类 private → 子类不可访问

  • private(私有继承)

    基类 public → 子类 private

    基类 protected → 子类 private

    基类 private → 子类不可访问

  • 核心结论

    基类private成员无论如何继承,子类都无法直接访问。

    公有继承最常用,不改变基类权限结构。

    权限只限制外部访问,基类私有成员依然会被继承下来(占内存)。


    三、继承后的内存布局

    概念

    子类对象内存 = 基类部分内存 + 子类新增成员内存。

    特性

    基类所有数据成员都会被复制到子类中(包括 private)。

    内存大小 = sizeof (基类) + sizeof (子类新增)。

    内存对齐规则不变。

    示例

    class Base { int a; };
    class Son : public Base { int b; };
    // sizeof(Son) = 4 + 4 = 8


    四、子类构造与析构顺序(重点)

    概念

    创建子类对象时,必须先构造基类部分;销毁时先析构自身,再析构基类。

    构造顺序

  • 虚基类优先(按声明顺序)

  • 非虚基类(按继承声明顺序,从左到右)

  • 对象成员(按类内声明顺序)

  • 子类自身构造

  • 析构顺序

    与构造完全相反。

    代码示例

    class A { public: A(){cout<<"A构造";} ~A(){cout<<"~A";} };
    class B { public: B(){cout<<"B构造";} ~B(){cout<<"~B";} };
    class C : public A, public B { public: C(){cout<<"C构造";} ~C(){cout<<"~C";} };

    int main(){
      C c;
      // 输出:A构造 B构造 C构造 ~C ~B ~A
    }

    子类初始化基类成员

    必须在初始化列表调用基类构造:

    // 基类
    class Base {
    public:
      int a;
      // 基类构造函数
      Base(int x) : a(x) {}
    };

    // 子类
    class Sub : public Base {
    public:
      int b;
      // 子类构造:基类没有无参构造函数就必须在 初始化列表 调用基类构造
      // 子对象的初始化也必须在初始化列表中进行
      Sub(int x, int y) : Base(x), b(y) {
          // 不能初始化基类成员,基类成员的初始化、给值只能在基类的构造函数中
      }
    };

    int main()
    {
    Sub obj(100,10);
    // obj.a = 100; obj.b = 10;
    }

    拓展

    一、必须在初始化列表调用基类构造的情况

    只要 基类没有无参构造函数(默认构造)就 必须在初始化列表手动调用基类构造!

    必须写的 3 种场景

  • 基类只有带参构造,没有无参构造

  • 基类写了构造函数,但都是带参的

  • 基类构造需要传参数才能初始化

  • 只要满足上面任意一条 → 必须在初始化列表调用基类构造

    必须写的代码样例

    class Base {
    public:
      // 只有带参构造 → 子类必须手动调用
      Base(int x) {}
    };

    class Son : public Base {
    public:
      // 必须写:Base(x)
      Son(int x) : Base(x) { }
    };


    二、不用在初始化列表写基类构造的情况

    只要 基类有无参构造函数(默认构造)就 不用写,编译器会自动调用!

    不用写的 2 种场景

  • 基类完全没写构造函数(编译器自动生成无参)

  • 基类自己写了无参构造

  • 满足任意一条 → 子类不用写初始化列表调用基类

    不用写的代码样例

    class Base {
    public:
      // 有无参构造 → 子类不用手动调用
      Base() {}
    };

    class Son : public Base {
    public:
      // 不用写 Base(),编译器自动加
      Son() { }
    };


    五、赋值兼容规则(对象 / 指针 / 引用)

    概念

    公有继承下,子类对象可以当作基类对象使用(安全向上转换)。

    规则

  • 子类对象 → 赋值给 基类对象(切片,只复制基类部分,自己的部分被浪费)

  • 子类对象 → 初始化 基类引用(无切片,无拷贝,仅仅是别名,安全)

  • 子类地址 → 赋值给 基类指针(存储子类首地址)

  • 反向不允许

    基类不能自动转为子类(不安全,会越界)。

    示例

    Father objFa;
    Father *pFa = &objFa;
    Self objSelf;
    Self *pSe = &objSelf;

    pFa = &objSelf;     // 合法
    pSe = &objFa;       // 非法

    objFa = objSelf;     // 合法
    objSelf = objFa;     // 非法


    六、同名成员处理

    概念

    子类与基类成员同名时,优先访问子类成员,基类成员被隐藏。

    访问基类同名成员

    必须加作用域::。

    代码示例

    class Base {
    public:
      int num = 10;
    };
    class Son : public Base {
    public:
      int num = 20;
      void show() {
          cout << num;       // 子类 20
          cout << Base::num; // 基类 10
      }
    };

    多继承同名冲突

    多个基类有同名成员 → 必须用基类名::明确指定。


    七、多继承

    概念

    一个子类继承多个基类。

    语法

    class 子类 : 继承方式 基类1, 继承方式 基类2…
    {};

    问题

    可能出现同名成员歧义。

    可能出现菱形继承(重复继承祖父类)。


    八、菱形继承与虚继承(虚基类)

    概念

    多继承中出现:A 派 B、A 派 C,B 和 C 派 D → D 中会有两份 A 成员,造成冗余与歧义。

    问题

    内存浪费

    成员歧义

    数据不一致

    解决方案:虚继承 virtual

    让多个子类共享同一份基类成员。

    语法

    class B : virtual public A {};
    class C : public virtual A {}; // virtual前后两种写法都可以
    class D : public B, public C {};

    原理

    虚继承会给子类加一个虚基类指针(占用一个指针的内存)。

    最终派生类中只保留一份虚基类成员。

    虚继承构造规则

    虚基类构造优先于同一层所有非虚基类。

    最终子类必须直接初始化虚基类。


    九、重点

  • 继承是为了复用 + 扩展。

  • 三种继承方式只改变权限,不改变内存。

  • 基类私有成员会被继承,但子类不能访问。

  • 构造顺序:虚基类 → 基类 → 对象成员 → 自己。

  • 析构与构造相反。

  • 子类可自动转基类,反之不行。

  • 同名成员用::区分。

  • 多继承容易歧义,菱形继承用虚继承解决。

  • 赞(0)
    未经允许不得转载:171主机测评 » C/C++ 基础笔记(十三)继承
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址