C++ 中函数覆盖与隐藏的区分及多态实现

蓝鲸
蓝鲸
发布时间:2026-10-16| 5人浏览过
原创

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++ 面向对象编程的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。