C++中重载、重写与隐藏从语法到底层实现

 更新时间:2026年07月09日 09:04:46   作者:小徐不徐说  
本文将从语法规则到底层实现,全面解析三者的差异,并深入探讨虚函数表(vtable)的工作原理,让你彻底掌握面试常问问题,感兴趣的可以了解一下

在 C++ 面向对象编程中,“重载(Overload)”、“重写(Override)” 和 “隐藏(Hide)” 是三个极易混淆的概念。它们虽都涉及函数名的复用,但其语义、作用机制和使用场景却截然不同。本文将从语法规则到底层实现,全面解析三者的差异,并深入探讨虚函数表(vtable)的工作原理。

一、函数重载(Overload)

✅ 定义

同一个作用域中,允许存在多个同名函数,只要它们的参数签名(参数个数、类型或顺序)不同。编译器会根据调用时的参数类型自动匹配最合适的函数版本。

📌 核心特性

  • 作用域:同一类或同一命名空间内
  • 区分依据:参数列表(个数、类型、顺序)必须不同(返回值类型不同不足以构成重载)
  • 绑定方式:编译期静态绑定(编译器在编译时确定调用哪个函数)
  • 与继承无关:仅针对同一作用域内的函数

示例代码

#include <iostream>
using namespace std;
class Calculator {
public:
    // 重载:参数类型不同
    int add(int a, int b) { return a + b; }
    double add(double a, double b) { return a + b; }
    // 重载:参数个数不同
    int add(int a, int b, int c) { return a + b + c; }
};
int main() {
    Calculator calc;
    cout << calc.add(2, 3) << endl;       // 调用 add(int, int)
    cout << calc.add(2.5, 3.5) << endl;   // 调用 add(double, double)
    cout << calc.add(1, 2, 3) << endl;    // 调用 add(int, int, int)
    return 0;
}

总结

重载的本质是 “同名函数的多版本实现”,目的是让函数名更直观(如用 add 统一表示加法操作,无论参数是整数还是小数),其匹配逻辑由编译器在编译期完成。

二、函数重写(Override)

✅ 定义

派生类中重新实现基类中声明为 virtual 的函数,要求函数签名(函数名、参数列表、返回值类型)完全一致。重写是实现多态的核心机制。

📌 核心特性

  • 作用域:派生类重写基类的虚函数
  • 前提条件:基类函数必须用 virtual 声明,派生类函数可加 override 关键字(C++11 起,用于显式表明重写意图,编译器会检查匹配性)
  • 绑定方式:运行期动态绑定(通过虚函数表实现)
  • 目的:实现多态(同一接口,不同实现)

示例代码

#include <iostream>
using namespace std;
class Shape {
public:
    // 基类中声明为虚函数
    virtual void draw() { cout << "绘制图形" << endl; }
};
class Circle : public Shape {
public:
    // 重写基类的 draw 函数,用 override 显式标注
    void draw() override { cout << "绘制圆形" << endl; }
};
class Rectangle : public Shape {
public:
    void draw() override { cout << "绘制矩形" << endl; }
};
int main() {
    Shape* shape1 = new Circle();
    Shape* shape2 = new Rectangle();
    // 运行时根据实际对象类型调用对应函数(多态)
    shape1->draw();  // 输出:绘制圆形
    shape2->draw();  // 输出:绘制矩形
    delete shape1;
    delete shape2;
    return 0;
}

注意事项

  • 若派生类函数签名与基类虚函数不一致,override 会导致编译错误(帮助开发者发现拼写或参数错误)
  • 基类虚函数被重写后,派生类的函数默认也是虚函数(即使不加 virtual
  • 重写的函数返回值类型必须与基类一致(协变返回类型除外,如基类返回 Shape*,派生类返回 Circle*

三、函数隐藏(Hide)

✅ 定义

当派生类中定义了一个与基类同名函数时,若该函数不是重写(即基类函数非虚函数,或参数签名不同),则派生类的函数会 “隐藏” 基类中所有同名函数 —— 即基类的同名函数在派生类中不可见,除非显式调用。

📌 核心特性

  • 作用域:派生类与基类之间
  • 触发条件
    • 基类函数未声明为 virtual,派生类定义同名函数(无论参数是否相同)
    • 基类函数是虚函数,但派生类函数参数签名与基类不同
  • 绑定方式:编译期静态绑定
  • 效果:派生类中无法直接调用被隐藏的基类函数,需用 基类名::函数名 显式调用

示例代码

#include <iostream>
using namespace std;
class Base {
public:
    void func(int x) { cout << "Base::func(int): " << x << endl; }
    void func(string s) { cout << "Base::func(string): " << s << endl; }
};
class Derived : public Base {
public:
    // 派生类定义同名函数,参数类型不同(隐藏基类所有 func 函数)
    void func(double x) { cout << "Derived::func(double): " << x << endl; }
};
int main() {
    Derived d;
    d.func(3.14);    // 调用 Derived::func(double)
    // d.func(10);    // 编译错误:基类的 func(int) 被隐藏
    // d.func("hi");  // 编译错误:基类的 func(string) 被隐藏
    // 显式调用基类被隐藏的函数
    d.Base::func(10);      // 输出:Base::func(int): 10
    d.Base::func("hi");    // 输出:Base::func(string): hi
    return 0;
}

如何避免隐藏?

若希望在派生类中同时保留基类的同名函数,可使用 using 声明引入基类函数:

class Derived : public Base {
public:
    using Base::func;  // 引入基类所有 func 重载函数
    void func(double x) { cout << "Derived::func(double): " << x << endl; }
};
// 此时可正常调用基类的 func 函数
int main() {
    Derived d;
    d.func(10);       // 调用 Base::func(int)
    d.func("hi");     // 调用 Base::func(string)
    d.func(3.14);     // 调用 Derived::func(double)
    return 0;
}

四、虚函数表(vtable):重写的底层实现

重写的核心是多态,而多态的底层依赖于虚函数表(vtable) 和虚指针(vptr) 机制。以下是其工作原理:

1. 虚函数表的本质

  • vtable:每个包含虚函数的类(或其派生类)会在编译期生成一个虚函数表,这是一个存储函数指针的数组,其中存放着该类所有虚函数的地址。
  • vptr:每个类的实例对象会包含一个虚指针(通常是对象的第一个成员),指向该类的 vtable。

2. 实现过程(以之前的Shape类为例)

  • 基类 Shape:

    • 编译期生成 vtable,包含 Shape::draw() 的地址。
    • 每个 Shape 对象有一个 vptr,指向 Shape 的 vtable。
  • 派生类 Circle:

    • 继承 Shape 的 vtable,但会用 Circle::draw() 的地址覆盖 vtable 中 Shape::draw() 的位置。
    • 每个 Circle 对象的 vptr 指向 Circle 的 vtable(而非 Shape 的)。
  • 调用时的动态绑定:

    • 当执行 shape1->draw() 时,编译器无法确定 shape1 指向的是 Shape 还是 Circle 对象。
    • 实际执行时,程序通过 shape1 的 vptr 找到对应的 vtable,再调用表中 draw() 函数的地址 —— 若 shape1 指向 Circle 对象,则调用 Circle::draw()。

3. 虚函数表的特性

  • 每个类只有一个 vtable:所有实例共享同一个 vtable(节省内存)。
  • 派生类 vtable 包含基类虚函数:若派生类未重写基类虚函数,则 vtable 中仍保留基类函数的地址。
  • 动态绑定的开销:相比普通函数调用,虚函数调用需多一次 “通过 vptr 查找 vtable” 的操作,效率略低(但通常可忽略)。

五、三者对比一览表

特性重载(Overload)重写(Override)隐藏(Hide)
作用域同一类 / 命名空间派生类与基类之间派生类与基类之间
函数关系同名不同参同名同参(基类为虚函数)同名(参数不同或基类非虚函数)
与继承的关系必须继承必须继承
虚函数要求无需基类必须声明 virtual基类通常非虚函数
绑定方式编译期静态绑定运行期动态绑定(vtable)编译期静态绑定
核心目的提供多版本函数接口实现多态(同一接口不同实现)屏蔽基类同名函数
调用方式直接调用,编译器自动匹配通过基类指针 / 引用调用派生类对象直接调用派生类函数,基类需显式调用

六、记忆口诀与实战建议

记忆口诀

  • 重载:同域同名,参数不同,编译匹配。
  • 重写:继承虚函,签名相同,运行多态。
  • 隐藏:继承同名,非重写则藏,显式调用。

实战建议

  1. 用 override 标注重写:明确意图,让编译器检查签名是否正确,避免意外隐藏。
  2. 避免同名函数在派生类中随意定义:若无需重写,尽量不要定义与基类同名的函数。
  3. 必要时用 using 引入基类函数:当需要在派生类中保留基类同名函数时,用 using 显式引入。
  4. 理解虚函数表机制:不仅能帮你掌握多态的本质,还能在调试复杂继承关系时快速定位问题。

结语

掌握重载、重写和隐藏的差异,是学好 C++ 面向对象编程的基础。其中,重写与虚函数表机制更是 C++ 多态的核心,理解其底层原理能让你在设计类层次结构时更加得心应手。记住:语法是表象,机制是本质,只有透过现象看本质,才能真正掌握这些概念。

到此这篇关于C++中重载、重写与隐藏从语法到底层实现的文章就介绍到这了,更多相关C++ 重载、重写与隐藏内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 深入解析C++编程中的纯虚函数和抽象类

    深入解析C++编程中的纯虚函数和抽象类

    这篇文章主要介绍了深入解析C++编程中的纯虚函数和抽象类,是C++入门学习中的基础知识,需要的朋友可以参考下
    2015-09-09
  • 使用boost读取XML文件详细介绍

    使用boost读取XML文件详细介绍

    这篇文章主要介绍了使用boost读取XML文件详细介绍的相关资料,需要的朋友可以参考下
    2016-11-11
  • C语言实现贪吃蛇小游戏开发

    C语言实现贪吃蛇小游戏开发

    这篇文章主要为大家详细介绍了C语言实现贪吃蛇小游戏开发,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-08-08
  • 在std::thread中创建并管理QEventLoop的全面解析

    在std::thread中创建并管理QEventLoop的全面解析

    QEventLoop的工作原理可以简单地理解为一个无限循环,它会不断地检查是否有新的事件需要处理,如果有,就将事件从事件队列中取出,然后找到相应的事件处理器进行处理,这篇文章主要介绍了在std::thread中创建并管理QEventLoop的全面指南,需要的朋友可以参考下
    2023-06-06
  • 从入门到精通C++11 <chrono> 库特性

    从入门到精通C++11 <chrono> 库特性

    chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念,感兴趣的朋友一起看看吧
    2025-06-06
  • C++中取余运算的实现

    C++中取余运算的实现

    这篇文章主要介绍了C++中取余运算的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-02-02
  • C 语言restrict 关键字的使用浅谈

    C 语言restrict 关键字的使用浅谈

    C 语言restrict 关键字的使用浅谈,需要的朋友可以参考一下
    2013-04-04
  • 深入理解Java事务的原理与应用

    深入理解Java事务的原理与应用

    下面小编就为大家带来一篇深入理解Java事务的原理与应用。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-06-06
  • Qt中Tab与Tree组件实现分页菜单

    Qt中Tab与Tree组件实现分页菜单

    本文主要介绍tabWidget选择夹组件与TreeWidget树形选择组件的常用方法及灵活运用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-12-12
  • 详解C++中的菱形继承问题

    详解C++中的菱形继承问题

    这篇文章主要介绍了详解C++中的菱形继承问题,在多继承结构中,存在着很多问题,比如从不同基类中继承了同名成员,派生类中也定义了同名成员,这种二义性问题很好解决,加上要访问的基类的类名限制就可以了,需要的朋友可以参考下
    2023-08-08

最新评论