一文带你深入了解隐藏的相关知识
文章目录
- 一文带你深入了解隐藏的相关知识
- 一、什么是隐藏?
- 最简单的例子
- 二、隐藏的三种情况
- 1. 成员变量的隐藏
- 2. 成员函数的隐藏
- 3. 重载函数的隐藏
- 三、解决隐藏的方法
- 方法1:使用using声明
- 方法2:显式调用基类版本
- 四、隐藏 vs 重写(覆盖)的区别
- 详细解释
- 代码运行结果
- 五、隐藏的实际应用场景
- 场景1:加强访问控制
- 场景2:修改函数行为但保留接口
- 场景3:静态多态(CRTP模式中的隐藏)
- 六、常见陷阱和注意事项
- 陷阱1:意外隐藏
- 陷阱2:隐藏与重载的混淆
- 最佳实践建议
- 总结
- 总结
好的,我来详细讲解C++继承中的**隐藏(Hiding)**机制。这是一个非常重要但也容易混淆的概念。
一、什么是隐藏?
隐藏是指:在派生类中定义了与基类同名的成员(函数或变量)时,基类的同名成员在派生类中变得"不可见"(被隐藏了)。
最简单的例子
#include<iostream>usingnamespacestd;classBase{public:voidfunc(){cout<<"Base::func()"<<endl;}};classDerived:publicBase{public:voidfunc(){// 隐藏了基类的funccout<<"Derived::func()"<<endl;}};intmain(){Derived d;d.func();// 输出:Derived::func()(基类的被隐藏)// 如果想调用基类的,必须指定作用域d.Base::func();// 输出:Base::func()return0;}二、隐藏的三种情况
1. 成员变量的隐藏
classBase{public:intvalue=100;intsameName=10;};classDerived:publicBase{public:intvalue=200;// 隐藏了基类的valueintdifferent=20;voidprint(){cout<<"Derived value: "<<value<<endl;// 200(派生类的)cout<<"Base value: "<<Base::value<<endl;// 100(需要指定)cout<<"sameName: "<<sameName<<endl;// 10(基类的,没被隐藏)}};intmain(){Derived d;d.print();cout<<d.value<<endl;// 200(派生类的)cout<<d.Base::value<<endl;// 100(基类的)return0;}2. 成员函数的隐藏
这是最容易混淆的地方,特别注意:隐藏不看参数,只看名字!
classBase{public:voidfunc(){cout<<"Base::func()"<<endl;}voidfunc(intx){cout<<"Base::func(int) x="<<x<<endl;}voidfunc(doublex){cout<<"Base::func(double) x="<<x<<endl;}};classDerived:publicBase{public:voidfunc(){// 隐藏了基类中所有名为func的函数cout<<"Derived::func()"<<endl;}};intmain(){Derived d;d.func();// ✅ 调用Derived::func()// d.func(10); // ❌ 错误!基类的func(int)被隐藏了// d.func(3.14); // ❌ 错误!基类的func(double)也被隐藏了// 必须指定基类作用域才能调用d.Base::func(10);// ✅ 这样才可以d.Base::func(3.14);// ✅ 这样才可以return0;}3. 重载函数的隐藏
重要规则:派生类中定义同名函数会隐藏基类中所有同名函数(不管参数列表)
classBase{public:voidshow(){cout<<"Base::show()"<<endl;}voidshow(int){cout<<"Base::show(int)"<<endl;}voidshow(double){cout<<"Base::show(double)"<<endl;}};classDerived:publicBase{public:voidshow(){// 隐藏了基类所有的showcout<<"Derived::show()"<<endl;}};intmain(){Derived d;d.show();// ✅ 调用Derived的// d.show(10); // ❌ 被隐藏了// d.show(3.14); // ❌ 被隐藏了// 解决方案1:使用using声明return0;}三、解决隐藏的方法
方法1:使用using声明
classBase{public:voidfunc(){cout<<"Base::func()"<<endl;}voidfunc(int){cout<<"Base::func(int)"<<endl;}};classDerived:publicBase{public:usingBase::func;// 把基类所有func函数引入派生类作用域voidfunc(){// 现在重载而不是隐藏cout<<"Derived::func()"<<endl;}};intmain(){Derived d;d.func();// Derived::func()d.func(10);// Base::func(int)(因为using声明)return0;}方法2:显式调用基类版本
classDerived:publicBase{public:voidfunc(){cout<<"Derived::func()"<<endl;}voidcallBaseFunc(){Base::func();// 显式调用基类版本}voidcallBaseFunc(intx){Base::func(x);// 显式调用基类版本}};四、隐藏 vs 重写(覆盖)的区别
这是初学者最容易混淆的,一定要分清楚:
| 特性 | 隐藏 | 重写(覆盖) |
|---|---|---|
| 关键字 | 无 | virtual |
| 函数名 | 相同 | 相同 |
| 参数 | 可不同 | 必须相同 |
| 返回值 | 可不同 | 必须相同(协变除外) |
| 绑定方式 | 静态绑定 | 动态绑定 |
| 多态性 | 无 | 有 |
classBase{public:voidhide(){cout<<"Base::hide"<<endl;}// 会被隐藏virtualvoidoverride(){cout<<"Base::override"<<endl;}// 会被重写};classDerived:publicBase{public:voidhide(){cout<<"Derived::hide"<<endl;}// 隐藏voidoverride()override{cout<<"Derived::override"<<endl;}// 重写};intmain(){Base*ptr=newDerived();ptr->hide();// 静态绑定:Base::hide(隐藏不看指针实际类型)ptr->override();// 动态绑定:Derived::overridedeleteptr;return0;}根据你提供的代码,ptr->hide();调用的是基类的hide()函数。
虽然ptr指向的是一个Derived对象,但由于hide()不是虚函数,编译器根据指针的静态类型(即Base*)来决定调用哪个函数,这就是静态绑定。
详细解释
- 指针类型:
ptr被声明为Base*类型,所以它的静态类型是Base*。 - 函数类型:
hide()在基类中没有被声明为virtual,因此它是一个非虚函数。 - 绑定机制:对于非虚函数,编译器在编译时就根据指针的静态类型(
Base*)确定了要调用的函数地址,所以它会调用Base::hide()。 - 结果:因此,输出是
"Base::hide"。
而override()是虚函数,所以调用它时会根据指针指向的对象的实际类型(Derived)进行动态绑定,输出"Derived::override"。
代码运行结果
Base::hide // 来自静态绑定 Derived::override // 来自动态绑定总结:
- 隐藏(hide):同名非虚函数,由指针/引用的静态类型决定调用哪个版本。
- 重写(override):同名虚函数,由对象的实际类型决定调用哪个版本。
五、隐藏的实际应用场景
场景1:加强访问控制
classBase{public:voidpublicAPI(){cout<<"Base public API"<<endl;}};classDerived:privateBase{public:voidpublicAPI(){// 隐藏基类的publicAPIcout<<"Derived's own API"<<endl;Base::publicAPI();// 可以在内部使用基类的}};intmain(){Derived d;d.publicAPI();// 调用Derived的版本// d.Base::publicAPI(); // ❌ 不能直接调用基类的(private继承)return0;}场景2:修改函数行为但保留接口
classLogger{public:voidlog(conststring&msg){cout<<"[LOG] "<<msg<<endl;}};classTimedLogger:publicLogger{public:voidlog(conststring&msg){// 隐藏基类的logstring timedMsg="["+getCurrentTime()+"] "+msg;Logger::log(timedMsg);// 调用基类版本完成实际工作}private:stringgetCurrentTime(){return"12:00:00";// 简化示例}};场景3:静态多态(CRTP模式中的隐藏)
template<typenameDerived>classBase{public:voidinterface(){static_cast<Derived*>(this)->implementation();// 调用派生类版本}voidimplementation(){// 可能被隐藏cout<<"Base implementation"<<endl;}};classDerived:publicBase<Derived>{public:voidimplementation(){// 隐藏基类的implementationcout<<"Derived implementation"<<endl;}};六、常见陷阱和注意事项
陷阱1:意外隐藏
classBase{public:virtualvoidprocess(intx){cout<<"Base::process(int)"<<endl;}};classDerived:publicBase{public:// 本意是想重写,但参数不同变成了隐藏virtualvoidprocess(doublex){// ❌ 隐藏了基类的process(int)cout<<"Derived::process(double)"<<endl;}};intmain(){Derived d;d.process(10);// 调用Derived::process(double)(int转double)// d.process(10, 20); // 如果基类有其他重载,也都被隐藏return0;}陷阱2:隐藏与重载的混淆
classBase{public:voidf(int){cout<<"Base::f(int)"<<endl;}};classDerived:publicBase{public:voidf(double){cout<<"Derived::f(double)"<<endl;}// 隐藏,不是重载};intmain(){Derived d;d.f(10);// 调用Derived::f(double)!10被转为double// 想要调用Base::f(int)?只能用Base::f(10)return0;}最佳实践建议
- 使用override关键字(C++11)明确表示重写意图
classDerived:publicBase{voidfunc()override;// 明确表示要重写};- 使用using声明引入基类同名函数,避免意外隐藏
classDerived:publicBase{usingBase::func;// 引入基类所有funcvoidfunc();// 现在是重载集的一部分};避免在派生类中使用与基类相同的名字,除非你有明确意图
理解并善用作用域解析
Base::member来访问被隐藏的成员
总结
| 要点 | 说明 |
|---|---|
| 隐藏的本质 | 派生类的名字屏蔽了基类的同名成员 |
| 判断依据 | 只看名字,不看参数列表 |
| 影响范围 | 隐藏一个函数会隐藏所有同名函数 |
| 与重写的区别 | 隐藏是静态绑定,重写是动态绑定 |
| 解决方案 | using声明或显式作用域 |
| 常见用途 | 加强封装、修改行为、CRTP模式 |
理解隐藏机制对于正确使用C++继承至关重要,特别是在设计类层次结构和处理多态时。
总结
这篇文章是作者搜集大量面经和资料这里出来的。感谢你的支持
作者wkm是一名中国矿业大学(北京) 大一的新生,希望得到你的关注
如果可以的话,记得一键三联!