大鹏万里头像
关注

C++ : 类 继承

C++ 继承(Inheritance)面试与原理指南

继承是 C++ 面向对象体系中的核心机制之一。很多人知道“继承就是子类复用父类代码”,但这个理解远远不够。面试真正容易问的是:派生类对象在内存中到底是什么、访问权限经过继承后如何变化、构造析构顺序是什么、基类指针为什么能指向派生类对象、向上/向下转换有什么风险,以及多继承、菱形继承、虚继承到底解决什么问题。

理解继承时,可以始终抓住一条主线:

Base
  ↓
Inheritance
  ↓
Derived
  ↓
Derived object contains a Base subobject
  ↓
construction / destruction / conversion / layout

1. 什么是继承

继承允许一个类基于已有类建立新的类。

已有的类称为基类(Base Class),新的类称为派生类(Derived Class)

class Animal {
   
   
public:
    void eat() {
   
   
        std::cout << "eat\n";
    }
};

class Dog : public Animal {
   
   
public:
    void bark() {
   
   
        std::cout << "bark\n";
    }
};

这里:

Animal
  ↑
  │ public inheritance
  │
 Dog

因此:

Dog d;

d.eat();   // 来自 Animal
d.bark();  // Dog 自己

从语义上看:

Dog 是一种 Animal

也就是典型的 is-a relationship

Dog is an Animal
Cat is an Animal

而:

Dog has a Toy
Car has an Engine

属于 has-a relationship,这通常更适合使用组合(composition),而不是继承。


2. 继承并不是简单的“复制代码”

这是理解 C++ 继承最重要的第一个概念。

很多初学者会下意识认为:

class Dog : public Animal {
   
   };

相当于把 Animal 的代码复制到 Dog 里面。

实际上不是。

更准确的理解是:

派生类对象中包含一个基类子对象(base class subobject)。

例如:

class Animal {
   
   
public:
    int age;
};

class Dog : public Animal {
   
   
public:
    int weight;
};

一个 Dog 对象可以概念上理解成:

Dog object
┌─────────────────────┐
│ Animal subobject    │
│     age             │
├─────────────────────┤
│ Dog members         │
│     weight          │
└─────────────────────┘

所以:

Dog d;
d.age = 3;
d.weight = 20;

age 属于内部的 Animal 子对象,weight 属于 Dog 自己。

这个模型非常重要,因为后面的:

  • 构造析构
  • 基类指针
  • 类型转换
  • 多继承
  • 虚继承
  • 对象布局

本质上都可以从“派生类对象包含基类子对象”这个概念继续推导。


3. 继承后的对象内存布局

假设:

class Base {
   
   
public:
    int x;
};

class Derived : public Base {
   
   
public:
    int y;
};

一个 Derived 对象通常可以理解为:

Derived object
┌──────────────┐
│ Base::x      │
├──────────────┤
│ Derived::y   │
└──────────────┘

于是通常:

sizeof(Derived)

至少需要容纳:

Base 部分
+
Derived 自己的成员
+
可能的 padding

但这里需要注意:

上面的布局是典型实现下的直观模型,不应该把它当成 C++ 标准规定的唯一物理布局。

C++ 标准规定的是语义和对象模型,具体内存布局由 ABI、编译器等共同决定。

对于普通单继承,没有虚继承等复杂机制时,这个模型通常非常接近实际情况。


4. 派生类能继承什么

“继承”这个词很容易被理解得过于绝对。

更准确地说:

基类的成员会成为派生类对象的一部分,但能否直接访问,取决于成员本身的访问权限以及继承方式。

例如:

class Base {
   
   
public:
    int a;

protected:
    int b;

private:
    int c;
};

派生类:

class Derived : public Base {
   
   
public:
    void test() {
   
   
        a = 1;  // 可以
        b = 2;  // 可以
        // c = 3;  // 不可以
    }
};

可以简单记忆:

Base::public
    ↓
Derived 可以直接访问

Base::protected
    ↓
Derived 可以直接访问

Base::private
    ↓
Derived 不能直接访问

但这并不意味着 c 不存在。

c 仍然是 Base 子对象的一部分。

只是:

Derived 不能直接访问 Base 的 private 成员。


5. Base 的 private 成员到底算不算被继承

这是一个经典面试陷阱。

假设:

class Base {
   
   
private:
    int x;
};

class Derived : public Base {
   
   };

问:

x 被继承了吗?

这个问题如果只回答“没有”并不严谨。

更准确的理解是:

  • Derived 对象中仍然存在 Base 子对象;
  • Base::x 仍然属于这个 Base 子对象;
  • Derived 不能直接访问 x

可以理解成:

Derived object
┌──────────────────┐
│ Base subobject    │
│   private x       │  ← 存在
├──────────────────┤
│ Derived members   │
└──────────────────┘

所以:

“不能访问”不等于“不存在”。

这是理解 C++ 访问权限和继承关系时非常重要的区别。


6. 三种继承方式

C++ 有:

public inheritance
protected inheritance
private inheritance

写法:

class Derived : public Base {
   
   };
class Derived : protected Base {
   
   };
class Derived : private Base {
   
   };

它们最重要的作用是:

改变基类 public/protected 成员在派生类中的访问级别。

这里必须注意:

基类 private 成员不参与这种访问级别转换。


7. public 继承

例如:

class Base {
   
   
public:
    int a;

protected:
    int b;

private:
    int c;
};

class Derived : public Base {
   
   };

结果:

Base          Derived 中可见权限
--------------------------------
public    →   public
protected →   protected
private   →   不可直接访问

也就是:

public
  ↓
public

protected
  ↓
protected

这是最常见的继承方式,也是面向对象意义上最典型的继承。

通常表达:

Derived is-a Base

例如:

class Animal {
   
   };
class Dog : public Animal {
   
   };

8. protected 继承

class Derived : protected Base {
   
   };

权限变化:

Base          Derived 中可见权限
--------------------------------
public    →   protected
protected →   protected
private   →   不可直接访问

可以理解为:

public
  ↓
protected

protected
  ↓
protected

外部代码因此不能通过 Derived 对象直接访问原来 Base 的 public 成员。


9. private 继承

class Derived : private Base {
   
   };

权限变化:

Base          Derived 中可见权限
--------------------------------
public    →   private
protected →   private
private   →   不可直接访问

即:

public
  ↓
private

protected
  ↓
private

这种继承更接近一种实现层面的复用关系,而不是向外表达“is-a”。


10. 三种继承方式一张表记住

这是非常值得直接记忆的一张表:

基类成员 public 继承 protected 继承 private 继承
public public protected private
protected protected protected private
private 不可直接访问 不可直接访问 不可直接访问

记住一句即可:

继承方式改变的是 Base 的 public/protected 成员在 Derived 中的访问权限,Base 的 private 成员不因此变成可访问。


11. classstruct 对继承还有一个默认区别

前面讲过:

class B : A {
   
   };

默认是:

private

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/MaFei5200/article/details/164330728

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--