C++ 中函数覆盖与隐藏的区分及多态实现
C++ 继承中,当子类出现与父类同名的函数时,会触发“覆盖”或“隐藏”机制。核心区别在于父类函数是否包含 virtual 关键字以及函数签名的一致性。理解这一机制对于掌握 C++ 多态性及正确调用父类成员至关重要。
1. 基本原理:同名的副作用
在 C++ 中,只要派生类(子类)定义了与基类(父类)同名的函数,基类中所有重载版本的该函数都会被隐式隐藏。这意味着在子类作用域内,无法直接通过对象名调用基类的同名函数,必须显式指定作用域。
以下代码展示了普通非虚函数的情况:
#include <iostream>
using namespace std;
class A {
public:
void fun1() { cout << "A::fun1()" << endl; }
void fun1(int) { cout << "A::fun1(int)" << endl; }
};
class B : public A {
public:
void fun1() { cout << "B::fun1()" << endl; }
};
int main() {
B b;
b.fun1(); // 输出: B::fun1()
// b.fun1(3); // 错误: no matching function for call to 'B::fun1(int)'
b.A::fun1(); // 调用基类无参版本: A::fun1()
b.A::fun1(3); // 调用基类有参版本: A::fun1(int)
return 0;
}
如上所示,B 类定义了 fun1(),导致 A 类中的 fun1(int) 也被隐藏。此时若要调用父类中被隐藏的重载函数,需使用 b.A::fun1(...) 的方式。
2. 虚函数与非虚函数的关键差异
区分“覆盖”与“隐藏”的关键在于函数声明中是否使用了 virtual 关键字,以及函数签名的匹配程度。
第1步:验证虚函数覆盖行为
当基类函数带有 virtual 关键字,且派生类函数具有相同的签名时,发生的是**覆盖(Overriding)**,这是实现运行时多态的基础。但如果派生类新增了不同参数的同名函数(即使基类是虚函数),同样会隐藏基类其他版本的重载函数。
#include <iostream>
using namespace std;
class A {
public:
virtual void fun1() { cout << "A::fun1()" << endl; }
void fun1(int) { cout << "A::fun1(int)" << endl; }
};
class B : public A {
public:
virtual void fun1() { cout << "B::fun1()" << endl; } // 覆盖 A::fun1()
};
int main() {
B b;
b.fun1(); // 输出: B::fun1()
// b.fun1(3); // 依然报错: 隐藏了 A::fun1(int)
b.A::fun1(); // 可显式调用 A::fun1()
b.A::fun1(3); // 可显式调用 A::fun1(int)
return 0;
}
即便存在虚函数覆盖,派生类定义的 void fun1() 依然会隐藏基类 A 中所有名为 fun1 的函数(包括 fun1(int))。因此,访问基类被隐藏的其他重载函数仍需使用域限定符 ::。
第2步:区分覆盖与隐藏的规则
- 覆盖 (Override):
- 基类函数必须加
virtual关键字。 - 派生类函数名、参数列表、返回值类型必须与基类严格一致(协变返回类型除外)。
- 结果:通过基类指针或引用调用时,会根据实际指向的对象类型动态绑定到派生类函数。
- 基类函数必须加
- 隐藏 (Hide):
- 基类函数没有
virtual关键字。 - 或者基类有
virtual,但派生类函数签名不匹配(如参数不同)。 - 结果:派生类对象只能看到自己的函数,基类同名函数完全不可见(除非显式指定作用域)。此过程不涉及多态,属于静态绑定。
- 基类函数没有
3. 多态的实现:基类指针的妙用
为了直观对比隐藏与覆盖的效果,我们可以引入基类指针。观察基类指针如何根据指向的对象类型决定调用哪个函数。
#include <iostream>
using namespace std;
class Base {
public:
void func1() { cout << "Base::func1 (Non-virtual)" << endl; }
virtual void func2() { cout << "Base::func2 (Virtual)" << endl; }
};
class Derived : public Base {
public:
void func1() { cout << "Derived::func1 (Hidden)" << endl; } // 隐藏
void func2() override { cout << "Derived::func2 (Override)" << endl; } // 覆盖
};
int main() {
Base* ptr;
Derived d;
ptr = &d;
// 1. 隐藏示例:虽然ptr指向Derived,但由于func1非虚,按基类静态类型解析
ptr->func1(); // 输出: Base::func1 (Non-virtual)
// 2. 覆盖示例:func2是虚函数,发生动态绑定
ptr->func2(); // 输出: Derived::func2 (Override)
return 0;
}
运行结果清晰展示了两种机制的本质差异:对于被隐藏的 func1,编译器仅查看指针类型 Base*,故调用基类版本;对于被覆盖的 func2,由于使用了虚函数表机制,程序检测到实际对象为 Derived,从而调用派生类版本。这就是 C++ 多态的核心所在。
4. 总结
在 C++ 开发中,避免意外的函数隐藏是编写健壮继承体系的关键。若期望派生类替换基类行为以实现多态,务必确保使用 virtual 且签名一致。若仅想扩展功能而非替代,请注意新定义的函数可能会屏蔽掉基类原有的重载函数,必要时请使用 using 声明或作用域操作符显式访问。
以上就是 C++ 函数覆盖与隐藏的详细解析,更多关于 C++ 面向对象编程的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









