目录

面向对象里这三个词都带个”函数”,意思却差很远:重载是同一作用域里”同名多份”;覆盖是继承里重写虚函数;隐藏是派生类里同名把基类的给挡了。下面分三块捋一遍,最后给张表对照。


一、重载(Overload):同一作用域,同名不同参

说白了:名字一样,参数列表得不一样(类型、个数、顺序里至少变一个)。编译器在编译期按实参帮你选版本,这叫静态多态。

注意:不能只靠返回值不同来重载,返回值不参与重载决议。

要同时满足:

  • 同一作用域:同一个类里,或同一个命名空间里。
  • 同名、参数列表不同。
  • 与返回值无关:只改返回类型不算重载。
  • 编译期定调:看实参匹配谁。
#include <iostream>

// 同一作用域,同名,参数不同
int Add(int a, int b)
{
    std::cout << "call Add(int,int): ";
    return a + b;
}

double Add(double a, double b)
{
    std::cout << "call Add(double,double): ";
    return a + b;
}

int Add(int a, int b, int c)
{
    std::cout << "call Add(int,int,int): ";
    return a + b + c;
}

int main()
{
    std::cout << Add(1, 2) << std::endl;       // int Add(int,int)
    std::cout << Add(1.5, 2.5) << std::endl;   // double Add(double,double)
    std::cout << Add(1, 2, 3) << std::endl;    // int Add(int,int,int)
    return 0;
}

输出示例:

call Add(int,int): 3
call Add(double,double): 4
call Add(int,int,int): 6

二、覆盖(Override):虚函数 + 签名对上

在继承里,派生类重新实现基类的虚函数,而且签名要一致(C++11 起可用 override 写清楚,避免手滑写错签名)。通过基类指针或引用调用时,运行时按对象真实类型走派生类版本,这就是动态多态。

要同时满足:

  • 有继承关系。
  • 基类里该函数是 virtual。
  • 派生类里同名、同参、返回类型一致(协变返回另说,这里不展开)。
  • 访问权限可以不同,但一般别乱改,免得读者懵。
#include <iostream>

class Base {
public:
    Base() = default;
    virtual ~Base() = default;

    virtual void Show()
    {
        std::cout << "Base::Show()\n";
    }
};

class Derived : public Base {
public:
    void Show() override
    {
        std::cout << "Derived::Show()\n";
    }
};

int main()
{
    Base* ptr = new Derived();
    ptr->Show();   // 运行期:走 Derived::Show()
    delete ptr;
    return 0;
}

输出示例:

Derived::Show()

三、隐藏(Hide):同名但没形成覆盖

派生类里起了和基类同名的函数,但没形成覆盖(比如基类不是虚函数,或参数列表不同),那基类里的同名函数在派生类作用域里就被藏起来了:默认用派生类这份;想调基类的,得写 Base::函数名(…)。

要点:

  • 发生在继承里。
  • 同名,但不满足覆盖(非虚,或参数不一致等)。
  • 派生类对象上直接写名字,优先派生类;要调基类版本用 基类::。
  • 一般是编译期就按静态类型解析到派生类那一支(和虚函数那条路径不一样)。
#include <iostream>

class Base {
public:
    Base() = default;
    virtual ~Base() = default;

    void Hide(int x)
    {
        std::cout << "Base::Hide(int): " << x << "\n";
    }

    virtual void VirHide(int x)
    {
        std::cout << "Base::VirHide(int): " << x << "\n";
    }
};

class Derived : public Base {
public:
    // 与基类 Hide(int) 参数不同 → 隐藏基类 Hide(int)
    void Hide(double x)
    {
        std::cout << "Derived::Hide(double): " << x << "\n";
    }

    // 基类虽是虚函数,但参数不同 → 仍不是覆盖,而是隐藏
    void VirHide(double x)
    {
        std::cout << "Derived::VirHide(double): " << x << "\n";
    }
};

int main()
{
    Derived d;
    d.Hide(10);          // Derived::Hide(double)
    d.Hide(10.5);
    d.VirHide(10);
    d.VirHide(10.5);
    d.Base::Hide(10);    // 显式调用基类
    d.Base::VirHide(10);
    return 0;
}

输出示例:

Derived::Hide(double): 10
Derived::Hide(double): 10.5
Derived::VirHide(double): 10
Derived::VirHide(double): 10.5
Base::Hide(int): 10
Base::VirHide(int): 10

四、一眼对比

重载覆盖隐藏
作用域同一作用域(类内 / 命名空间)基类与派生类基类与派生类
函数名相同相同相同
参数列表必须不同必须相同往往不同(否则可能成覆盖)
基类函数不涉及必须是 virtual可有可无,虚不虚都可能被藏
多态编译期(静态绑定)运行期(虚函数)派生类侧优先,不是多态那条故事线
你在记什么同名多版本,参数区分虚函数重写,用 override 省心同名挡了基类,用 :: 救场

抓四个关键词:是不是同一作用域、有没有继承、原型一不一致、基类是不是虚函数,基本就不会把这三个概念搅成一锅粥了。