C++继承与多态知识点及其高频面试问题

本文整理自学习笔记,涵盖继承和多态的核心知识点、代码示例及面试高频问题

一、继承

1.1 基本概念

  • 定义:派生类(子类)继承基类(父类)的成员(数据 + 函数),实现代码复用和 is‑a 关系

  • 访问控制

    • public 继承:基类的 public → 派生类的 publicprotectedprotectedprivate 不可见

    • protected 继承:基类的 public/protected → 派生类的 protected

    • private 继承:基类的 public/protected → 派生类的 private(常用于实现"用组合代替继承"的变体)

    • 转化为类外类内可不可用的问题

  • 默认方式class 默认 private 继承,struct 默认 public 继承

1.2 构造函数与析构函数

  • 派生类构造函数必须调用基类构造函数(显式或隐式)

  • 构造顺序:先基类 → 后派生类

  • 析构顺序:先派生类 → 后基类

  • 基类析构函数通常声明为 virtual(见多态部分)

cpp 复制代码
class Base {
public:
    Base(int x) : _x(x) {}
    virtual ~Base() {}      // 虚析构
private:
    int _x;
};

class Derived : public Base {
public:
    Derived(int x, int y) : Base(x), _y(y) {}
private:
    int _y;
};

1.3 同名隐藏

  • 派生类中定义与基类同名的函数(不管参数列表是否相同),会隐藏基类的所有同名函数(不构成重载)

  • 若需要基类版本,使用 using Base::func; 引入

  • 也可再调用时 进行显示调用

cpp 复制代码
class Base {
public:
    void f(int) {}
    void f(double) {}
};
class Derived : public Base {
public:
    void f(int) {}   // 隐藏 Base::f(double)
    // using Base::f; // 解除隐藏
};

1.4 继承方式对成员的访问权限

基类成员权限 public 继承 protected 继承 private 继承
public public protected private
protected protected protected private
private 不可访问 不可访问 不可访问

private继承一般少见 实践中很少使用

1.5 继承与组合的选择--复用

  • is‑a → 继承(例如 Dog is a Animal)

  • has‑a → 组合(例如 queue的底层实现)

1.6 面试常见考点(继承)

  1. 派生类构造函数如何初始化基类成员?

    在初始化列表中显式调用基类构造函数

  2. 什么是同名隐藏?如何访问被隐藏的基类成员?

    派生类同名函数隐藏基类所有同名函数。用 Base::func() 调用,或者再类外显示调用

  3. private 继承的意义是什么?

    实现"用组合代替继承"的语法糖,通常用于适配器模式(std::stack 默认继承 deque 但使用私有继承)

  4. 析构函数是否必须为虚?

    见多态部分

二、多态

2.1 静态多态 vs 动态多态

  • 静态多态:函数重载、模板(编译期确定)

  • 动态多态:通过虚函数和基类指针/引用,运行时确定调用哪个函数

2.2 虚函数-Virtual Function

  • 在基类中声明为 virtual,派生类可重写(override--检查重写是否正确)

  • 通过基类指针或引用调用虚函数时,会调用实际对象类型的版本

  • 虚函数表(vtable)实现细节

cpp 复制代码
class Animal {
public:
    virtual void speak() const { cout << "???" << endl; }
    virtual ~Animal() {}
};
class Dog : public Animal {
public:
    void speak() const override { cout << "Woof!" << endl; }
};
void makeSound(const Animal& a) { a.speak(); }
Dog d;
makeSound(d);   // 输出 "Woof!"

2.3 overridefinal 说明符(C++11)

  • override:显式声明重写基类虚函数,如果签名不匹配则编译错误

  • final:禁止派生类重写该虚函数,或禁止类被继承

cpp 复制代码
class Base {
    virtual void f() final;
    virtual void g();
};
class Derived : public Base {
    void g() override;   // OK
    // void f() override; // 错误,f 是 final
};

2.4 虚析构函数

  • 基类析构函数应为 virtual,否则 delete 基类指针 时不会调用派生类析构函数,导致内存泄漏
cpp 复制代码
Base* p = new Derived();
delete p;   // 若 ~Base() 非虚,则不会调用 ~Derived()

2.5 纯虚函数与抽象类

  • 纯虚函数:virtual void func() = 0;

  • 包含纯虚函数的类称为抽象类,不能实例化

  • 派生类必须实现所有纯虚函数才能成为非抽象类

  • 设置抽象类的一个目的是强制派生类 否则无法实例化对象

2.6 虚函数表(vtable)与内存布局(理解即可)

  • 每个多态类有一个虚函数表,对象中有一个虚指针(_vptr)指向该表

  • 派生类虚表先复制基类虚表,再替换被重写的函数指针

2.7 面试常见考点(多态)

  1. 虚函数是如何实现动态绑定的?

    每个对象有一个虚指针(vptr),指向虚函数表(vtable)调用虚函数时通过 vptr 找到对应的函数地址

  2. 为什么基类析构函数需要是虚的?

    避免派生类资源未释放。如果基类析构非虚,delete base_ptr 只会调用基类析构,派生类部分未析构

  3. 构造函数可以是虚的吗?

    不能。虚函数表在构造函数执行完后才建立

  4. 静态函数可以是虚的吗?

    不能。虚函数依赖对象,静态函数属于类

  5. overridefinal 的作用?

    override 让编译器检查重写是否正确;final 禁止进一步重写或继承--具体情况依照位置

  6. 什么是纯虚函数?抽象类的作用?

    纯虚函数强制派生类提供实现,抽象类用于定义接口,不能创建对象

  7. 能否在构造函数或析构函数中调用虚函数?

    可以,但不会发生多态(调用的是当前类版本的函数,因为派生类部分尚未构造或已经析构)

  8. 虚函数表在内存中位于哪个区?

    通常位于只读数据段(.rodata)或代码段(.rdata),虚指针位于对象内存布局中

三、总结要点

  • 继承用于 is‑a 关系,注意访问控制和构造函数调用顺序

  • 多态通过虚函数实现,基类析构函数需为虚

  • 区分重载、隐藏、重写(覆盖)

  • 抽象类用于定义接口,纯虚函数强制派生类实现

  • 面试常问虚函数实现原理、虚表位置、构造函数中能否调用虚函数等

我的gitee仓库

https://gitee.com/jiangmingpeng0716/c-learning-process

相关推荐
June`1 小时前
如何组织一个并行程序
开发语言·cuda
dtq04241 小时前
C语言刷题函数1-判断素数(分支语句,函数两种方法)
c语言·开发语言·学习
乘浪初心1 小时前
python调用API接口,免费API调取,学习如何调取API接口并反馈你输入的内容
开发语言·python·api·免费
AI玫瑰助手1 小时前
Python模块:import导入模块与模块的搜索路径
android·开发语言·python
Tairitsu_H1 小时前
[LC优选算法#4] 滑动窗口 | 串联所有单词的⼦串 | 最⼩覆盖⼦串
c++·算法·滑动窗口
傻啦嘿哟1 小时前
一篇文章讲清楚Python的变量作用域
开发语言·python
devilnumber1 小时前
Java 二分查找(二分算法)详解 + 实战运用 + 核心坑点
java·开发语言·算法
ch.ju1 小时前
Java程序设计(第3版)第四章——重载和覆盖的区别
java·开发语言
嵌入式ZYXC1 小时前
第9篇:《面试题:ADC前端为什么要加运放跟随器?什么情况下可以不加?》
stm32·单片机·嵌入式硬件·面试·职场和发展