面向对象里这三个词都带个”函数”,意思却差很远:重载是同一作用域里”同名多份”;覆盖是继承里重写虚函数;隐藏是派生类里同名把基类的给挡了。下面分三块捋一遍,最后给张表对照。
一、重载(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 省心 | 同名挡了基类,用 :: 救场 |
抓四个关键词:是不是同一作用域、有没有继承、原型一不一致、基类是不是虚函数,基本就不会把这三个概念搅成一锅粥了。