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. class 和 struct 对继承还有一个默认区别
前面讲过:
class B : A {
};
默认是:
private
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/MaFei5200/article/details/164330728



