函数隐藏是C++继承中容易引起误解的知识,函数隐藏又和函数覆盖(或函数重写)又有些类似,但又不一样。
简单记录于此,方便自己查阅。
正文
函数隐藏
函数隐藏是指当派生类中定义了与基类同名的成员函数时,无论参数列表是否相同、返回值类型是否一致,基类中所有同名函数在派生类作用域内均变为不可见。
静态绑定!
条件
同名即隐藏:只要函数名相同,基类函数即被屏蔽,与参数类型、个数、顺序及返回值无关。
跨作用域特性:隐藏发生在基类与派生类这两个不同的作用域之间,区别于同一作用域内的函数重载。
静态绑定:隐藏是编译期行为,不涉及虚函数表与运行时多态机制。
派生类中定义一个同名函数,会隐藏基类中该函数的所有重载版本。
例子
例子1
class Base {
public:
void func() {}
void func(int x) { }
void func(double x) {}
};
class Derived : public Base {
public:
void func(float x) {} // 仅定义一个同名函数
};
//使用
Derived d;
d.func(); // 编译错误:Base::func() 被隐藏
d.func(10); // 编译错误:Base::func(int) 被隐藏
d.func(3.14); // 编译错误:Base::func(double) 被隐藏
d.func(1.0f); // 正确:调用 Derived::func(float)知识点:编译器在派生类作用域中找到
func名称后,立即停止向基类作用域查找,因此基类中所有func重载均不参与后续的重载决议。
例子2
当基类函数被隐藏后,仍可通过以下方式访问被隐藏函数的方法。
using 声明
(推荐):在派生类中显式引入基类函数,恢复重载集。
class Derived : public Base { public: using Base::func; // 引入基类所有func重载 void func(float x) { } }; // 此时 d.func()、d.func(10) 均可正常调用
作用域解析符
临时显式调用特定基类版本
d.Base::func(10); // 仅调用 Base::func(int),不恢复重载集
函数覆盖
虚函数,使得通过基类指针或引用调用该函数时,实际执行的是派生类的版本。这与编译期绑定的函数隐藏有着本质区别。
动态绑定!
条件
基类函数为虚函数:基类中必须使用
virtual关键字声明。函数签名完全一致:函数名、参数列表(类型、个数、顺序)必须完全相同。
返回值类型兼容:返回值类型必须相同,或满足协变返回类型规则(即派生类返回类型是基类返回类型的指针或引用的派生类)。
访问权限非私有:基类虚函数不能是
private,否则派生类无法访问,无法构成覆盖。
例子
例子1
class Base {
public:
virtual void func(int x) { }
virtual void func() { }
};
class Derived : public Base {
public:
void func(int x) override {} // 正确:覆盖基类虚函数
void func() override { } // 正确:覆盖基类虚函数
void func(double x) override { } // 编译错误!这不是覆盖,是隐藏
};override 的三大作用:
防止误写为隐藏:如果派生类函数签名与基类虚函数不匹配,编译器直接报错,避免意外隐藏。
防止基类函数被删除:如果基类虚函数被删除或重命名,派生类的
override函数会编译失败,及时暴露问题。提高代码可读性:明确表达"此函数意图覆盖基类虚函数",便于维护。
override是 C++11 引入的关键字,用于显式标记覆盖函数,提供编译期安全检查
例子2
class Base {
public:
virtual Base* clone() { return new Base(); }
};
class Derived : public Base {
public:
Derived* clone() override { return new Derived(); } // 协变返回类型,合法覆盖
};注意:协变返回类型仅适用于指针和引用,不适用于值返回。值返回时类型必须完全一致。
陷阱
忘记
virtual:基类函数未声明为虚函数,派生类同名同参函数仅构成隐藏,无法多态。签名不一致:参数类型、const 限定符、引用限定符等细微差异导致隐藏而非覆盖。
private虚函数:基类虚函数为private时,派生类仍可覆盖(因为覆盖发生在类定义阶段,而非调用阶段),但无法通过基类指针在类外调用。析构函数未虚:基类析构函数非虚时,通过基类指针删除派生类对象会导致未定义行为。
隐藏 vs 覆盖
| 对比维度 | 函数隐藏 | 函数覆盖(函数重写) |
|---|---|---|
| 基类函数要求 | 无特殊要求(虚/非虚均可) | 必须为 virtual 虚函数 |
| 函数签名要求 | 仅函数名相同即可 | 函数名、参数列表、返回值必须完全一致 |
| 绑定方式 | 编译期静态绑定 | 运行期动态绑定(多态) |
| 作用域关系 | 跨作用域屏蔽 | 跨作用域替换 |
| 访问方式 | 需 Base::func() 显式调用 | 通过基类指针/引用自动调用派生类版本 |
核心记忆点:覆盖是多态,隐藏是屏蔽。覆盖看签名,隐藏只看名字。
参考文章
AI

