当前位置: 首页 > news >正文

【c++面向对象编程】第13篇:继承(三):同名隐藏与作用域覆盖

一、一个让人意外的现象

先看这段代码,猜猜输出什么?

cpp

#include <iostream> using namespace std; class Base { public: void func() { cout << "Base::func()" << endl; } void func(int x) { cout << "Base::func(int)" << endl; } }; class Derived : public Base { public: void func() { cout << "Derived::func()" << endl; } }; int main() { Derived d; d.func(); // 输出什么? d.func(10); // 能编译吗? return 0; }

答案:

  • d.func()输出Derived::func()— 这很合理,派生类覆盖了基类的版本

  • d.func(10)编译错误!编译器说没有匹配的函数

为什么会这样?基类明明有一个func(int),为什么说找不到?

这就是“同名隐藏”规则:一旦派生类定义了同名函数(不管参数是否相同),基类的所有同名函数都会被隐藏。编译器只看到派生类的func(),不会去基类里找其他重载版本。


二、同名隐藏的规则

核心规则

当派生类中声明了与基类同名的成员(变量或函数),基类的同名成员会被“隐藏”——即使在派生类中不可见。

  • 对于函数:隐藏的是名字,不是单个函数。基类中所有同名函数(无论参数)都会被隐藏。

  • 对于变量:派生类的同名变量会隐藏基类的同名变量。

函数隐藏示例

cpp

class Base { public: void print() { cout << "Base::print()" << endl; } void print(int x) { cout << "Base::print(int)" << endl; } void show() { cout << "Base::show()" << endl; } }; class Derived : public Base { public: void print() { cout << "Derived::print()" << endl; } // 同名函数 // 注意:没有重写show() }; int main() { Derived d; d.print(); // ✅ Derived::print() // d.print(10); // ❌ 错误!Base::print(int) 被隐藏了 d.show(); // ✅ 可以,show没有被隐藏(没有同名) }

变量隐藏示例

cpp

class Base { public: int value = 10; }; class Derived : public Base { public: int value = 20; // 同名变量,隐藏基类的value }; int main() { Derived d; cout << d.value << endl; // 输出20(派生类的) // cout << d.Base::value; // 输出10(用作用域运算符访问被隐藏的) }

三、隐藏 vs 重载 vs 重写

这是新手最容易混淆的三个概念,必须区分清楚:

概念发生条件作用域效果
重载同一作用域,函数名相同参数不同同一个类内部增加多个版本,编译器根据参数选择
重写派生类重写基类的虚函数继承体系,函数签名完全相同实现多态,运行时动态绑定
隐藏派生类定义了同名成员(不要求签名相同)继承体系基类同名成员被屏蔽

关键区别图

cpp

class Base { public: void f(int); // Base::f(int) virtual void g(); // Base::g() }; class Derived : public Base { public: void f(double); // 隐藏!不是重载(不同作用域),不是重写(参数不同) void g() override; // 重写!虚函数,签名相同 };

一句话记住

  • 重载:同一个类,同一个名字,不同参数

  • 重写:不同类(继承),虚函数,相同签名

  • 隐藏:不同类,同名即隐藏(不看参数)


四、如何访问被隐藏的基类成员?

方法1:作用域运算符::

cpp

class Base { public: void func() { cout << "Base::func()" << endl; } void func(int x) { cout << "Base::func(int) " << x << endl; } }; class Derived : public Base { public: void func() { cout << "Derived::func()" << endl; } void callBaseFunc() { Base::func(); // 调用基类无参版本 Base::func(100); // 调用基类带参版本 } }; int main() { Derived d; d.func(); // Derived版本 d.Base::func(); // 基类无参版本 d.Base::func(42); // 基类带参版本 }

方法2:使用using声明(推荐,将基类版本引入派生类作用域)

cpp

class Derived : public Base { public: using Base::func; // 把基类的所有func重载引入派生类作用域 void func() { cout << "Derived::func()" << endl; } }; int main() { Derived d; d.func(); // Derived版本(派生类自己的优先级更高) d.func(10); // ✅ 现在可以了!调用Base::func(int) }

using声明的效果:基类的同名函数变成了派生类的重载集的一部分。编译器会同时考虑派生类和基类的版本进行重载解析。


五、为什么会有隐藏规则?

这看起来像一个“缺陷”,但背后有设计考量:

1. 避免意外的继承

假设基类后来添加了一个新的重载版本:

cpp

// 原有代码 class Base { public: void process(int x) { ... } }; class Derived : public Base { public: void process(double d) { ... } // 原本只处理double };

如果C++没有隐藏规则,基类新增的process(string)会突然出现在派生类中,可能导致意想不到的重载决议。隐藏规则让派生类“主动选择”哪些基类成员可见。

2. 维护封装性

派生类可以完全“替换”基类的某个名字,而不受基类未来添加新重载的影响。


六、完整例子:图形系统中的隐藏问题

cpp

#include <iostream> #include <string> using namespace std; // 基类:形状 class Shape { public: void draw() { cout << "绘制通用形状" << endl; } void draw(string color) { cout << "用" << color << "颜色绘制形状" << endl; } virtual double getArea() { return 0; } }; // 派生类:圆形 class Circle : public Shape { private: double radius; public: Circle(double r) : radius(r) {} // 重写虚函数 double getArea() override { return 3.14159 * radius * radius; } // 隐藏!定义了同名函数 void draw() { cout << "绘制圆形,半径=" << radius << endl; } // 如何让基类的draw(string)也能用? // 方法1:手动转发 void drawWithColor(string color) { Shape::draw(color); } // 方法2:using声明(推荐) using Shape::draw; // 把Shape的所有draw引入作用域 }; int main() { Circle c(5.0); cout << "=== 演示隐藏 ===" << endl; c.draw(); // Circle::draw() // c.draw("红色"); // 如果不加using,这行会编译错误 cout << "\n=== 加上using后 ===" << endl; c.draw("红色"); // ✅ 现在可以了,调用Shape::draw(string) cout << "\n=== 通过作用域运算符访问 ===" << endl; c.Shape::draw(); // 基类无参版本 c.Shape::draw("蓝色"); // 基类带参版本 cout << "\n=== 面积计算 ===" << endl; cout << "圆形面积: " << c.getArea() << endl; return 0; }

输出:

text

=== 演示隐藏 === 绘制圆形,半径=5 === 加上using后 === 用红色颜色绘制形状 === 通过作用域运算符访问 === 绘制通用形状 用蓝色颜色绘制形状 === 面积计算 === 圆形面积: 78.5397

七、隐藏的“例外”:虚函数重写

如果派生类的函数与基类的虚函数签名完全相同,这不是隐藏,而是重写(override),是实现多态的基础。

cpp

class Base { public: virtual void draw() { cout << "Base::draw" << endl; } virtual void draw(int x) { cout << "Base::draw(int)" << endl; } }; class Derived : public Base { public: void draw() override { cout << "Derived::draw" << endl; } // 重写Base::draw() // Base::draw(int) 没有被重写,但会被隐藏! }; int main() { Derived d; d.draw(); // Derived::draw(重写的版本) // d.draw(10); // ❌ 隐藏了!即使基类版本是虚函数 }

注意:虚函数重写只针对完全相同签名的函数。同名但参数不同的版本仍然会被隐藏。


八、最佳实践建议

1. 避免不必要的同名成员

除非你有明确意图(如重写虚函数),否则不要给派生类的成员取和基类相同的名字。

2. 需要基类重载时使用using

cpp

class Derived : public Base { public: using Base::func; // 把基类的所有func带过来 void func() { ... } // 添加自己的版本 };

3. 重写虚函数时永远使用override关键字

cpp

void draw() override { ... } // 明确表达意图,编译检查签名是否正确

4. 访问被隐藏成员时显式使用作用域运算符

cpp

d.Base::func(); // 代码阅读者一眼就知道你在调用基类版本

九、三个常见错误

1. 以为派生类会重载基类函数

cpp

class Base { public: void process(int x) { cout << "int" << endl; } }; class Derived : public Base { public: void process(double d) { cout << "double" << endl; } }; Derived d; d.process(10); // 输出 "double"!不是 "int"

因为process(double)隐藏了基类的process(int)10被隐式转换为double调用派生类版本。

2. 忘记using导致编译错误

cpp

class Derived : public Base { void func() { ... } // 隐藏了Base::func(int) }; // 外部想调用d.func(10) → 编译错误

3. 搞混隐藏和重写

cpp

class Base { public: virtual void show() { cout << "Base" << endl; } }; class Derived : public Base { public: void show(int x) { cout << "Derived" << endl; } // 这是隐藏,不是重写! // 因为参数不同,show(int)隐藏了Base::show() };

十、这一篇的收获

你现在应该理解:

  • 同名隐藏:派生类定义了与基类同名的成员,基类的同名成员(所有重载版本)被隐藏

  • 隐藏发生在名字级别,不看参数、不看是否为虚函数

  • 重载是同一作用域,重写是虚函数相同签名,隐藏是派生类屏蔽基类

  • Base::memberusing Base::member可以访问被隐藏的基类成员

💡 小作业:设计Animal基类,有speak()speak(string language)两个版本。派生类Dog重写speak()(打印"汪汪"),但不重写带参数版本。测试Dog对象能否调用speak("english")。如果不能,用两种方法修复(作用域运算符和using声明)。


下一篇预告:第14篇《多态(一):虚函数——实现“一个接口,多种方法”》——终于到了面向对象最核心的概念:多态。基类指针指向派生类对象,调用同一个函数,执行不同的行为。这是怎么做到的?虚函数是答案。

http://www.jsqmd.com/news/817972/

相关文章:

  • PyQt5开发一个简单的HTTP请求测试工具
  • Figma中文插件终极指南:3分钟让英文界面变中文的简单方案
  • 视频去水印软件怎么选?2026 免费去水印工具对比|电脑手机都能用 - 科技热点发布
  • 容器内 ping 不通外网但宿主机能 ping 通,怎么排查 Docker 网络配置?
  • VMware Fusion 26H1 发布 - 领先的免费桌面虚拟化软件
  • Windows系统优化终极指南:Chris Titus Tech WinUtil一键管理神器
  • AMD Ryzen调试神器SMU Debug Tool:5步快速掌握CPU性能调优
  • 3个颠覆性脚本,让Adobe Illustrator工作效率提升500%
  • 常德招聘网站推荐:秒聘网择业利器 - 17329971652
  • 如何用3分钟彻底解决Windows开发者的API测试困境:Postman便携版完整指南
  • WSL网络连接问题
  • VMware Workstation Pro 26H1 for Windows Linux - 领先的免费桌面虚拟化软件
  • Python CosyVoice项目遭遇 Windows TxF WinError 6714 的深度排查与修复指南
  • 乙烯基甲苯市场深度洞察:年复合增长率(CAGR)为5.7%(2026-2032)
  • 2026年照片去水印免费软件app有哪些?手机无广告去水印工具推荐 - 科技热点发布
  • ESP-Drone:如何用300元预算打造你的第一架智能无人机?
  • 2026届必备的六大AI写作神器推荐榜单
  • 拆弹实验——反汇编实战:从汇编指令到算法还原
  • 常德招聘软件推荐:秒聘网优选佳选 - 17322238651
  • 2026 成都靠谱 GEO 优化公司排行榜|全维度评测,这 5 家头部服务商值得选 - GEO优化
  • 2026年图片去水印软件哪个好用?5款工具实测对比与推荐指南 - 科技热点发布
  • PYTHON+AI LLM DAY FOURTY-FOUR
  • 2026漳州市黄金回收白银回收铂金回收店铺哪家好 靠谱门店推荐及联系方式_转自TXT - 盛世金银回收
  • Linux系统上运行Photoshop CC 2022:打破平台壁垒的完整指南
  • 构建多模型对比评测系统时利用Taotoken简化API管理与调用
  • 72.人工智能实战:RAG 多路召回怎么做?从单一向量检索召回不足到 BM25、向量、标签与重排融合
  • TextRL:简化文本生成强化学习,统一接口加速RLHF实验
  • 常德招聘平台哪个好:秒聘网领跑首位 - 13425704091
  • 9.2%年复合增长!2032年全球电子束曝光系统市场冲刺36.13亿美元
  • 2026 抖音视频怎么在线去水印?去水印操作方法与平台工具实测对比 - 科技热点发布