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

【C++进阶】深入了解继承

文章目录

  • 1.继承与友元
  • 2. 继承与静态成员
  • 3. 多继承及其菱形继承问题
    • 3.1 继承模型
    • 3.2 虚继承
    • 3.3 多继承中指针偏移问题
    • 3.4 IO库中的菱形虚拟继承
  • 4. 继承和组合
    • 4.1 继承和组合

点击跳转继承前置知识

1.继承与友元

友元关系不能继承,也就是说基类友元不能访问派生类私有和保护成员。

//编译器从前往后找,所以加上前置声明classStudent;classPerson{public:friendvoidDisplay(constPerson&p,constStudent&s);protected:string _name;// 姓名};classStudent:publicPerson{protected:int_stuNum;// 学号};voidDisplay(constPerson&p,constStudent&s){cout<<p._name<<endl;cout<<s._stuNum<<endl;}intmain(){Person p;Student s;// 编译报错:error C2248: “Student::_stuNum”: 无法访问 protected 成员// 解决方案:Display也变成Student的友元即可Display(p,s);return0;}

2. 继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个派生类,都只有一个static成员实例。

classPerson{public:string _name;staticint_count;};intPerson::_count=0;classStudent:publicPerson{protected:int_stuNum;};intmain(){Person p;Student s;// 这里的运行结果可以看到非静态成员_name的地址是不一样的// 说明派生类继承下来了,父派生类对象各有一份cout<<&p._name<<endl;cout<<&s._name<<endl;// 这里的运行结果可以看到静态成员_count的地址是一样的// 说明派生类和基类共用同一份静态成员cout<<&p._count<<endl;cout<<&s._count<<endl;// 公有的情况下,父派生类指定类域都可以访问静态成员cout<<Person::_count<<endl;cout<<Student::_count<<endl;return0;}

3. 多继承及其菱形继承问题

3.1 继承模型

单继承:一个派生类只有一个直接基类时称这个继承关系为单继承

多继承:一个派生类有两个或以上直接基类时称这个继承关系为多继承,多继承对象在内存中的模型是,先继承的基类在前面,后面继承的基类在后面,派生类成员在放到最后面。

菱形继承:菱形继承是多继承的一种特殊情况。菱形继承的问题,从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题,在Assistant的对象中Person成员会有两份。支持多继承就一定会有菱形继承,像Java就直接不支持多继承,规避掉了这里的问题,所以实践中我们也是不建议设计出菱形继承这样的模型的。

classPerson{public:string _name;// 姓名};classStudent:publicPerson{protected:int_num;//学号};classTeacher:publicPerson{protected:int_id;// 职工编号};classAssistant:publicStudent,publicTeacher{protected:string _majorCourse;// 主修课程};intmain(){// 编译报错:error C2385: 对“_name”的访问不明确Assistant a;a._name="peter";// 需要显示指定访问哪个基类的成员可以解决二义性问题,但是数据冗余问题无法解决a.Student::_name="xxx";a.Teacher::_name="yyy";return0;}

3.2 虚继承

很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂,性能也会有一些损失,所以最好不要设计出菱形继承

classPerson{public:string _name;// 姓名/*int _tel; int _age; string _gender; string _address;*/// ...};// 使用虚继承Person类classStudent:virtualpublicPerson{protected:int_num;//学号};// 使用虚继承Person类classTeacher:virtualpublicPerson{protected:int_id;// 职工编号};// 教授助理classAssistant:publicStudent,publicTeacher{protected:string _majorCourse;// 主修课程};intmain(){// 使用虚继承,可以解决数据冗余和二义性Assistant a;a._name="peter";return0;}

解决办法:


下面这幅图也是菱形继承

classPerson{public:Person(constchar*name):_name(name){}string _name;// 姓名};classStudent:virtualpublicPerson{public:Student(constchar*name,intnum):Person(name),_num(num){}protected:int_num;//学号};classTeacher:virtualpublicPerson{public:Teacher(constchar*name,intid):Person(name),_id(id){}protected:int_id;// 职⼯编号};// 不要去玩菱形继承classAssistant:publicStudent,publicTeacher{public:Assistant(constchar*name1,constchar*name2,constchar*name3):Person(name3),Student(name1,1),Teacher(name2,2){}protected:string _majorCourse;// 主修课程};intmain(){// 思考⼀下这⾥a对象中_name是"张三", "李四", "王五"中的哪⼀个?Assistanta("张三","李四","王五");return0;}

实际上,结果输出王五,因为会按照初始化列表顺序调用,先调用Person类,调用Student和Teacher类的构造函数时,不会再次调用Person类的构造函数,所以结果是王五

3.3 多继承中指针偏移问题

下面说法正确的是( )
A:p1 == p2 == p3 B:p1 < p2 < p3 C:p1 == p3 != p2 D:p1 != p2 != p3

classBase1{public:int_b1;};classBase2{public:int_b2;};classDerive:publicBase1,publicBase2{public:int_d;};intmain(){Derive d;Base1*p1=&d;Base2*p2=&d;Derive*p3=&d;return0;}

选C

3.4 IO库中的菱形虚拟继承

template<classCharT,classTraits=std::char_traits<CharT>>classbasic_ostream:virtualpublicstd::basic_ios<CharT,Traits>{};template<classCharT,classTraits=std::char_traits<CharT>>classbasic_istream:virtualpublicstd::basic_ios<CharT,Traits>{};

4. 继承和组合

4.1 继承和组合

public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。

组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。

继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对派生类可见。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。

对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。

优先使用组合,而不是继承。实际尽量多去用组合,组合的耦合度低,代码维护性好。不过也不太那么绝对,类之间的关系就适合继承(is-a)那就用继承,另外要实现多态,也必须要继承。类之间的关系既适合用继承(is-a)也适合组合(has-a),就用组合。

// Tire(轮胎)和Car(车)更符合has-a的关系classTire{protected:string _brand="Michelin";// 品牌size_t _size=17;// 尺寸};classCar{protected:string _colour="白色";// 颜色string _num="陕ABIT00";// 车牌号Tire _t1;// 轮胎Tire _t2;// 轮胎Tire _t3;// 轮胎Tire _t4;// 轮胎};classBMW:publicCar{public:voidDrive(){cout<<"好开-操控"<<endl;}};// Car和BMW/Benz更符合is-a的关系classBenz:publicCar{public:voidDrive(){cout<<"好坐-舒适"<<endl;}};template<classT>classvector{};// stack和vector的关系,既符合is-a,也符合has-atemplate<classT>classstack:publicvector<T>{};template<classT>classstack{public:vector<T>_v;};intmain(){return0;}

http://www.cnnetsun.cn/news/2501509.html

相关文章:

  • Spring Boot 项目标准化部署打包实战
  • 2026毕业答辩PPT模板实测:三个平台的真实体验与避坑建议
  • 打通本地与云端,LangChain 混合部署环境的兼容性避坑手册
  • 艺术设计论文降AI工具怎么选?创意设计类降AI实用方案
  • 《CVPR2025-DEIM创新改进项目实战:从原理到部署的深度学习优化全攻略》016、DEIM在图像分类任务上的改进——ResNet-DEIM与ViT-DEIM
  • 《CVPR2025-DEIM创新改进项目实战:从原理到部署的深度学习优化全攻略》017、YOLO-DEIM与DETR-DEIM的调试手记
  • 离散几何拓扑数论(终稿·全定义完整版一)
  • CANN 算子调优:榨干昇腾硬件性能
  • BOM(全)
  • Agentic Search能替代GraphRAG吗,结论清晰了
  • 多模态AI应用开发:从理论到实践
  • 谷歌搜索SEO优化需要做什么?4个步骤快速做好站内优化
  • 新手必看,五分钟完成Taotoken的API Key申请与基础配置
  • LLM 认知框架:揭秘时间序列与空间结构,洞悉 AI 未来!
  • 【BUUCTF】【Misc】我有一只马里奥
  • 自研极简C++软交互事件系统:干掉观察者模式、碾压前端事件机制
  • 雷达信号体制识别
  • 超宽自锚式悬索桥模型修正与抗震可靠度分析【附仿真】
  • 独立开发者如何借助Taotoken低成本验证AI应用创意与可行性
  • 论文定稿≠答辩结束?okbiye AI PPT,把你从答辩 PPT 的 “熬夜地狱” 里捞出来
  • 向量数据库选型2026:Qdrant vs Pinecone vs Weaviate vs Chroma深度对比
  • AICoverGen完整指南:零基础打造专业级AI翻唱音乐的终极方案
  • 四轮独立驱动电动汽车操纵稳定性关键状态参数估计及协调控制策略【附代码】
  • 技术人的人际关系:建立良好的职业网络
  • 从物理光学到AI生成:揭秘玻璃折射率n=1.52如何映射为--s 750 + --iw 1.8的底层逻辑
  • 【Midjourney单色调风格终极指南】:20年AI视觉设计专家亲授3大调色公式、7类灰阶映射逻辑与避坑清单
  • Midjourney金属渲染避坑清单(2024Q2最新):6类典型翻车案例+对应反向Prompt修复模板
  • Django 从 0 到 1 打造完整电商平台:登录与登出功能实现
  • 鸿蒙生鲜电商页面构建:商品网格与配送档期模块详解
  • 2026爆火!5款AI论文软件亲测,打破思路枯竭,初稿半天搞定