1. 引言
在 c++ 面向对象编程中,重载(overloading)、重写(overriding)和隐藏(hiding)是三个容易混淆但至关重要的概念。它们都涉及同名函数,但发生场景、作用机制和结果截然不同。理解这些区别对于编写正确、高效且易于维护的 c++ 代码至关重要。
本文将结合详细的 c++ 代码示例,深入剖析这三者的定义、规则、区别以及典型应用场景。
2. 函数重载(overloading)
2.1 定义与规则
函数重载发生在同一个作用域内(例如同一个类中,或同一个命名空间下)。它允许定义多个同名函数,但这些函数的参数列表(参数的类型、个数或顺序)必须不同。函数的返回类型可以相同也可以不同,但仅返回类型不同不足以构成重载。
编译器在编译时根据调用时提供的实参类型和数量来决定调用哪个重载函数,这个过程称为静态绑定(早绑定)。
2.2 代码示例
#include <iostream>
using namespace std;
class calculator {
public:
// 重载函数:参数个数不同
int add(int a, int b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
// 重载函数:参数类型不同
double add(double a, double b) {
return a + b;
}
// 错误示例:仅返回类型不同,不是有效的重载
// double add(int a, int b) { return (double)(a + b); } // 编译错误
};
int main() {
calculator calc;
cout << "calc.add(1, 2) = " << calc.add(1, 2) << endl; // 调用 int add(int, int)
cout << "calc.add(1, 2, 3) = " << calc.add(1, 2, 3) << endl; // 调用 int add(int, int, int)
cout << "calc.add(1.5, 2.5) = " << calc.add(1.5, 2.5) << endl; // 调用 double add(double, double)
return 0;
}3. 函数重写(overriding)
3.1 定义与规则
函数重写发生在继承层次结构中。它是指派生类(子类)重新定义基类(父类)中已有的虚函数(virtual function),以实现多态性。
重写必须满足以下严格条件:
- 基类函数必须声明为
virtual(c++11 后可使用override关键字显式标记)。 - 函数名、参数列表(类型、个数、顺序)和返回类型必须完全相同(协变返回类型除外)。
- 访问权限可以不同(但通常建议保持一致)。
重写是实现运行时多态的关键。通过基类指针或引用调用虚函数时,实际调用的是指针或引用所指向的对象类型的版本,这个过程称为动态绑定(晚绑定)。
3.2 代码示例
#include <iostream>
using namespace std;
class animal {
public:
// 基类虚函数
virtual void speak() {
cout << "animal makes a sound." << endl;
}
virtual ~animal() {} // 虚析构函数,良好实践
};
class dog : public animal {
public:
// 重写基类虚函数
void speak() override { // c++11 使用 override 确保正确重写
cout << "dog barks: woof! woof!" << endl;
}
};
class cat : public animal {
public:
// 重写基类虚函数
void speak() override {
cout << "cat meows: meow~" << endl;
}
};
int main() {
animal* animal1 = new dog();
animal* animal2 = new cat();
animal1->speak(); // 输出: dog barks: woof! woof!
animal2->speak(); // 输出: cat meows: meow~
delete animal1;
delete animal2;
return 0;
}4. 函数隐藏(hiding)
4.1 定义与规则
函数隐藏也发生在继承层次结构中。当派生类定义了与基类同名的函数(无论参数列表是否相同,也无论基类函数是否为虚函数)时,基类的同名函数在派生类的作用域内就会被隐藏。
关键点:
- 隐藏与重载不同:重载要求在同一作用域,而隐藏是跨作用域的。
- 隐藏与重写不同:重写要求基类函数是虚函数且签名严格匹配,而隐藏不要求。
- 一旦发生隐藏,通过派生类对象直接调用该函数名时,只能看到派生类的版本。若要调用被隐藏的基类函数,需要使用作用域解析运算符
::。
4.2 代码示例
#include <iostream>
using namespace std;
class base {
public:
void func() {
cout << "base::func()" << endl;
}
void func(int x) { // 重载版本
cout << "base::func(int): " << x << endl;
}
};
class derived : public base {
public:
// 定义了同名函数,隐藏了基类中的所有 func 函数
void func() {
cout << "derived::func()" << endl;
}
// 注意:这里并没有重载 base::func(int),而是隐藏了它。
};
int main() {
derived d;
d.func(); // 正确:调用 derived::func()
// d.func(10); // 错误!base::func(int) 被隐藏了,无法直接调用。
// 使用作用域解析运算符调用被隐藏的基类函数
d.base::func(); // 正确:调用 base::func()
d.base::func(100); // 正确:调用 base::func(int)
base* ptr = &d;
ptr->func(); // 正确:调用 base::func() (非虚函数,静态绑定)
ptr->func(200); // 正确:调用 base::func(int)
return 0;
}5. 核心区别对比总结
| 特性 | 重载 (overloading) | 重写 (overriding) | 隐藏 (hiding) |
|---|---|---|---|
| 作用域 | 同一作用域(如类内) | 不同作用域(继承关系) | 不同作用域(继承关系) |
| 函数关系 | 同名不同参 | 同名同参(虚函数) | 同名(参数可同可不同) |
| virtual 关键字 | 不需要 | 基类函数必须为 virtual | 不需要 |
| 绑定方式 | 静态绑定(编译时) | 动态绑定(运行时) | 静态绑定(编译时) |
| 目的 | 提供同一操作的不同版本 | 实现多态性 | 派生类覆盖基类实现(非多态) |
| 调用基类版本 | 根据参数自动选择 | 通过基类指针/引用调用子类版本 | 需使用 baseclass::function() |
6. 常见误区与最佳实践
6.1 误区:误将隐藏当作重载
在继承中,如果派生类定义了与基类同名的函数,即使参数列表不同,它也会隐藏基类中所有同名函数(包括重载版本),而不是在派生类作用域内形成重载。
class base {
public:
void show(int) { /* ... */ }
};
class derived : public base {
public:
void show(double) { /* ... */ } // 隐藏了 base::show(int),并未重载
};
// derived d; d.show(10); // 错误!需要类型转换,因为 base::show(int) 被隐藏了。
解决方案: 如果希望在派生类中也能使用基类的重载函数,可以使用 using 声明将其引入派生类作用域。
class derived : public base {
public:
using base::show; // 引入 base 中的所有 show 函数
void show(double) { /* ... */ } // 现在与引入的 show(int) 形成重载
};
// derived d; d.show(10); // 正确:调用 base::show(int)
6.2 最佳实践
- 重写时使用 override 关键字(c++11及以上):让编译器检查是否真正重写了虚函数,避免因签名错误导致的意外隐藏。
- 为多态基类声明虚析构函数:确保通过基类指针删除派生类对象时行为正确。
- 理解隐藏机制:明确区分“想重写”和“想隐藏”的意图,避免因隐藏导致基类功能意外不可用。
- 优先使用重写实现多态:隐藏通常不是设计首选,它破坏了通过基类接口操作派生类对象的统一性。
7. 总结
重载、重写和隐藏是 c++ 中处理同名函数的三种不同机制:
- 重载是横向的,在同一层次提供功能变体,编译时决定。
- 重写是纵向的,在继承层次中实现多态,运行时决定,是面向对象的核心。
- 隐藏也是纵向的,但它是名字遮盖,会屏蔽基类的同名函数,通常需要谨慎使用。
掌握它们的区别,有助于你更精准地控制函数的行为,编写出符合预期、易于理解和维护的 c++ 代码。
到此这篇关于c++ 中重载、重写与隐藏的区别详解(附代码示例)的文章就介绍到这了,更多相关c++ 重载、重写与隐藏区别内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论