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

C++中的父继子承(2)多继承菱形继承问题,多继承指针偏移,继承组合分析+高质量习题扫尾继承多态

🎬 胖咕噜的稞达鸭:个人主页

🔥 个人专栏: 《数据结构》《C++初阶高阶》《算法入门》

⛺️技术的杠杆,撬动整个世界!

在这里插入图片描述

在这里插入图片描述

实现一个不能被继承的类

方法一:父类的构造函数私有化,子类的构成必须调用父类的构造函数,但是父类的构成函数私有化之后,子类看不到就不能调用了,子类就无法实例化出对象。
代码演示:把父类foundaTion的构造函数放进private:中,就实现了父类的构造函数私有化。代码会出现问题。

#include<iostream>
using namespace std;class foundaTion
{
public:void func(){ cout << "foundaTion::func" << endl; }
protected:int a = 1;
private:foundaTion(){ }
};class branCh :public foundaTion
{
public:void func() { cout << "branCh::func" << endl; }
};int main()
{branCh b;
}

方法二:用final修改父类,子类就不能被继承了。class foundaTion final

在这里插入图片描述
在这里插入图片描述
友元和继承

友元关系不能继承,也就是说父类友元不能访问子类私有和保护成员

派生类可以继承基类,但是基类中的友元声明关系不会被继承下来

class Student;
class Person
{
public:friend void Display(const Person& p, const Student& s);
protected:string _name;
};
class Student :public Person
{//friend void Display(const Person& p, const Student& s);//注释[^1]
protected:int _stuNum;
};
void Display(const Person& p, const Student& s)
//Display是Person的友元,Person中其他的函数会被继承下来,但是友元的声明关系不会被继承下来
//解决方法:在student中也继承友元即可,如:注释[^1]
{cout << p._name << endl;cout << s._name << endl;//派生类中没有继承基类的友元这里会报错
}int main()
{Person p;Student s;Display(p, s);return 0;
}

继承和静态成员

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

假如说父类Student中成员有一个name,继承下来的子类中也有name,但是这个子类TeachernameStudent是不一样的,如果在父类中定义了static 的静态成员adddress,那么父类和子类中就会有同一个static的成员address,是同一个成员函数。

不管这个学生作为学生还是作为老师,他的住址都是在雄安新区。
在这里插入图片描述

class Student
{
public:int _id;static string _address;
};string Student:: _address = "雄安新区";class Teacher :public Student
{
protected:string title;
};int main()
{Student s;Teacher t;cout << &s._id << endl;cout << &t._id << endl;cout << &s._address << endl;cout << &t._address << endl;cout << Student::_address << endl;cout << Teacher::_address << endl;return 0;
}

继承模型

多继承及其菱形继承问题
是什么:

单继承:一个子类只有一个直接父类时称这个继承关系为单继承;

多继承:一个子类有两个及以上的父类,这个继承关系就是多继承。一个类具有很多个特性,就是多继承,形象一点说,黄瓜既是水果也是蔬菜。那这个关系就是多继承。
菱形继承:
是什么:假设有一个助教,他既是学生也是老师,在继承Person的大类,还分别满足Student和Teacher的特性,而Student和Teacher满足Person的特性。这种会形象构成菱形关系。

怎么样:

  1. 造成二义性问题:如果在主函数Assistant创建一个对象a,去调用_name,那么就会有二义性问题,到底调的是Student还是Teacher_name那么该怎么解决这样的问题?

需要显示指定访问哪个父类的成员可以解决二义性问题,比如:a.Student::_name="小章同学";a.Teacher::_name="小章老师";从学生继承过来或者从老师继承过来指定类域。

  1. 数据冗余,存储了两份数据Assistant存储了StudentTeacher的两份数据,一定程度上造成了内存浪费数据冗余,Person类中创建的数据愈多,内存就越大。

class Person
{
public:string _name;
};
class Student :public Person
{
protected:int _num;
};
class Teacher :public Person
{
protected:int _id;
};class Assistant :public Student, public Teacher
{
protected:string _majorCourse;
};int main()
{Assistant a;//a._name = "Keda";//会报错//改正:指定类域a.Student::_name = "小章同学";a.Teacher::_name = "小章老师";cout << sizeof(a) << endl;//136
}

BUT!!!菱形继承是不可避免的,支持多继承就会有菱形继承。

怎么解决:

虚继承:哪个类(公共基类)产生了数据冗余和二义性,继承时用虚继承。只有构成菱形继承才会加virtual

在这里插入图片描述
所以:菱形继承尽量不要使用,底层要比我们想象的复杂很多,所以不建议使用菱形继承。

多继承中指针偏移问题:

在这里插入图片描述

#include<iostream>
using namespace std;class Base1 { public:int _b1; };
class Base2 { public:int _b2; };
class Derive : public Base2 ,public Base1 { public:int _d; };int main()
{Derive d;Base1* p1 = &d;Base2* p2 = &d;Derive* p3 = &d;return 0;
}

这里引入一道题目来帮助我们理解,

说明p1,p2p3的地址是一样的吗?

多继承中指针的指向规则:

在多继承中,派生类对象的内存会包含多个基类子对象(比如Base1中的_b1,Base2中的_b2)的区域。当用基类指针(Base1* p1Base2* p2)指向派生类对象(d)时,指针会指向对应基类子对象的起始地址;而派生类指针(Derive* p3)会指向派生类对象本身的起始地址。

  1. 分析

在内存中的分布是按照声明的顺序来进行分布的,先继承的就在前面。

对于Base1* p1=&d; p1会指向派生类对象d中Base1子对象的起始地址。

对于Base2* p2=&d;p2指向派生类对象d中Base2子对象的起始地址,由于Base1子对象已经在内存中占用了一定的空间,所以Base1和Base2的起始地址是不同的,因此p1和p2的指向不同。

对于Derive* p3=&dp3指向派生类对象d本身的起始地址,因为Base1是Derive继承的第一个基类,派生类对象的起始地址和第一个基类子对象(Base1子对象)的起始地址是重合的,所以p3和p1指向的地址相同。
所以:p1==p3!=p2

  1. 题目改编:
    在这里插入图片描述

改编将这道题改一下:class Derive: public Base2,public Base1{int _d;} 其他部分不做变化,这个时候p1==p3!=p2这个结果还是对的吗?

不对!!!一定要注意:先继承的在前面, Derive这个类在继承的时候先继承的是Base2,再继承Base1,所以内存分布是先给Base2分配空间,然后再分配地址给Base1,此时p2和p3指向同一块地址,而不等于p1:p2=p3!=p1

继承和组合

public继承是一种is-a的关系。也就是说每个子类对象都是一个父类对象。
组合,是一种has-a的关系,假设stack组合了list,每个stack中都有一个list对象。栈里面有一个链表。

//继承
class stack:public list
{}
//组合
class stack
{list _lt;
}

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

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

黑盒测试:不了解底层实现,从功能角度测试;
白盒测试:了解底层实现,从代码运行逻辑测试。

低耦合,高内聚。

来做一道题:结合继承和多态两种机制:

要做这道题:
要结合博主的两篇博客:

第一篇:
C++中的父继子承:继承方式实现栈及同名隐藏和函数重载的本质区别, 派生类的4个默认成员函数

第二篇:
多态:(附高频面试题)虚函数重写覆盖,基类析构重写,重载重写隐藏对比,多态原理,虚表探究一文大全

说明下面一道题的打印结果是?

class A
{
public:A() :m_iVal(0) { test(); }virtual void func() { std::cout << m_iVal << " "; }void test() { func(); }
public:int m_iVal;
};class B : public A
{
public:B() { test(); }virtual void func(){++m_iVal;std::cout << m_iVal << " ";}
};int main(int argc, char* argv[])
{A* p = new B;p->test();return 0;
}

在这里插入图片描述
这道题打印出来为什么是 0 1 2 呢?

解释:

首先new B的时候要调用父类A的构造函数,因为AB要构成继承关系,只有父类和子类都构造好了才可以执行操作。

A类构造:A类的构造函数中,只有一个成员变量m_iVal被初始化成了0,这个成员变量是Int类型的。然后在构造函数的函数体中执行test()操作。
A类中test()函数体中要执行func()操作,也就是打印出现阶段的m_iVal的值为0;

B类构造:A类构造好了之后,整体就形成了多态的机制(构成虚函数,AB类的函数名返回值类型及参数列表相同,且父类A的指针p调用虚函数)所以调用test()函数,再去执行func(),继承下来在B类中的m_iVal是0,自增打印之后是1;

然后再分析p->test(),此时AB类已经构造好了,而且完全形成多态的机制。由于基类的指针p,调用的是B类对象,所以这个test()应该调用的是B类中的,执行test()操作,执行子类的func(),所以打印出来再次自增的m_iVal的值,二次自增最后是2。

总结构造派生类对象B的时候(new B),会先构造基类,再构造派生类B

执行过程拆解:

  1. 构造基类A:

通过 初始化列表m_iVal(0),将m_iVal初始化为0;构造函数体中调用test(),test()内部调用func(),由于此时再基类的构造阶段,虚函数没有动态绑定,调用基类的func(),打印出0;

  1. 构造派生类B:

B的构造函数要调用test(),此时B已经构造完成了,虚函数动态绑定,调用派生类B中的func(),m_iVal自增为1;

  1. 指针p指向B对象,调用test()时,虚函数动态绑定,再次调用B类的func():m_iVal自增为2。

在这里插入图片描述

http://www.dtcms.com/a/485944.html

相关文章:

  • 做公司网站别人能看到吗6网站源码传到服务器上后怎么做
  • php多语言网站开发网站界面设计图片
  • 树形结构渲染 + 选择(Vue3 + ElementPlus)
  • Redis技术应用
  • hot100练习-8
  • 手机网站设置在哪里找房产信息平台
  • 算法入门:专题二---滑动窗口(长度最小的子数组)更新中
  • 2025年存储市场报告深度解读
  • HTTP 413 状态码详解与前端处理,请求体过大
  • 大数据背景下时序数据库选型指南:国产开源技术的突破与实践
  • asp网站优化云南网站制作需求
  • k8s(六)Pod的资源控制器
  • TypeScript前端架构与开发技巧深度解析:从工程化到性能优化的完整实践
  • 郴州做网站网站建设公司ejiew
  • LeetCode 将数组和减半的最少操作次数
  • OpenHarmony南向开发环境搭建 - 深入理解Ubuntu、DevEco Device Tool与HPM
  • QT-day1
  • Spec-Kit+Copilot打造AI规格驱动开发
  • Linux服务器编程实践30-TCP交互数据流:Nagle算法与延迟确认的作用
  • MATLAB一个基于Attention-LSTM的分类模型,构建Attention-LSTM深度学习模型,训练模型并进行分类预测
  • 杭州网站建设朗诵面朝网站建设策划内容
  • 手机网站开发模板南昌网站设计建设
  • Playwright中page的实现类深度解析-PageImpl 方法作用解析
  • 【完整源码+数据集+部署教程】 【运动的&足球】足球场上球检测系统源码&数据集全套:改进yolo11-DGCST
  • 无用知识研究:如何用decltype里的逗号表达式判断一个类里面有operator <号,并在编译时优雅给出提示,而不是一大堆不相干的模板信息
  • 人类知识体系分类
  • Java 大视界 -- Java 大数据在智能政务数字身份认证与数据安全共享中的应用
  • 《Foundation 图标参考手册》
  • 从 “坑“ 到 “通“:Spring AOP 通知执行顺序深度解密
  • 博途SCL语言仿真七段数码管