C++继承(详解)

一、继承的概念

1.1、继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。

class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter"; // 姓名int _age = 18; // 年龄
};
class Student : public Person
{protected:int _stuid; // 学号
};
class Teacher : public Person
{
protected:int _jobid; // 工号
};
int main()
{Student s;Teacher t;s.Print();t.Print();return 0;
}

 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了
Student和Teacher复用了Person的成员。下面我们使用监视窗口查看Student和Teacher对象,可以看到变量的复用。调用Print可以看到成员函数的复用。

1.2、继承的定义

1.2.1、定义格式

下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类
 

class Student:public Person
{
public:int _stuid; //学号int _major; //专业
}

1.2.2继承关系和访问限定符

1.2.3继承基类成员访问方式的变化

总结:

1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。


2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。


3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected
> private。


4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过
最好显示的写出继承方式。


5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡
使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里
面使用,实际中扩展维护性不强

二、基类和派生类对象赋值转换

1、派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。


2、基类对象不能赋值给派生类对象。


3、基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(RunTime Type Information)的dynamic_cast 来进行识别后进行安全转换。

                       

class Person
{
protected :string _name; // 姓名string _sex; // 性别int _age; // 年龄
};
class Student : public Person
{
public :int _No ; // 学号
};
void Test ()
{Student sobj ;// 1.子类对象可以赋值给父类对象/指针/引用Person pobj = sobj ;Person* pp = &sobj;Person& rp = sobj;//2.基类对象不能赋值给派生类对象sobj = pobj;// 3.基类的指针可以通过强制类型转换赋值给派生类的指针pp = &sobjStudent* ps1 = (Student*)pp; // 这种情况转换时可以的。ps1->_No = 10;pp = &pobj;Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问
题ps2->_No = 10;
}

三、继承中的作用域

1. 在继承体系中基类和派生类都有独立的作用域。


2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问


3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。


4. 注意在实际中在继承体系里面最好不要定义同名的成员。

// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
class Person
{
protected :string _name = "小李子"; // 姓名int _num = 111; // 身份证号
};
class Student : public Person
{
public:
void Print()
{cout<<" 姓名:"<<_name<< endl;cout<<" 身份证号:"<<Person::_num<< endl;cout<<" 学号:"<<_num<<endl;
}
protected:int _num = 999; // 学号
};
void Test()
{Student s1;s1.Print();
};
// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A
{
public:void fun(){cout << "func()" << endl;}
};
class B : public A
{
public:void fun(int i){A::fun();cout << "func(int i)->" <<i<<endl;}
};
void Test()
{B b;b.fun(10);
};

四、派生类的默认成员函数

派生类中默认成员函数是如何生成的

1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。


2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。


3. 派生类的operator=必须要调用基类的operator=完成基类的复制。


4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。


5. 派生类对象初始化先调用基类构造再调派生类构造。


6. 派生类对象析构清理先调用派生类析构再调基类的析构。


7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系

class Person
{
public :Person(const char* name = "peter"): _name(name ){cout<<"Person()" <<endl;}Person(const Person& p): _name(p._name){cout<<"Person(const Person& p)" <<endl;}Person& operator=(const Person& p ){cout<<"Person operator=(const Person& p)"<< endl;if (this != &p)_name = p ._name;return *this ;}~Person(){cout<<"~Person()" <<endl;}
protected :string _name ; // 姓名
};class Student : public Person
{
public :Student(const char* name, int num): Person(name ), _num(num ){cout<<"Student()" <<endl;}Student(const Student& s): Person(s), _num(s ._num){cout<<"Student(const Student& s)" <<endl ;}Student& operator = (const Student& s ){cout<<"Student& operator= (const Student& s)"<< endl;if (this != &s){Person::operator =(s);_num = s ._num;}return *this ;}~Student(){cout<<"~Student()" <<endl;}
protected :int _num ; //学号
};
void Test ()
{Student s1 ("jack", 18);Student s2 (s1);Student s3 ("rose", 17);s1 = s3 ;
}

五、继承与友元

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

class Student;
class Person
{
public:friend void Display(const Person& p, const Student& s);
protected:string _name; // 姓名
};class Student : public Person
{
protected:int _stuNum; // 学号
};void Display(const Person& p, const Student& s)
{cout << p._name << endl;cout << s._stuNum << endl;
}
void main()
{Person p;Student s;Display(p, s);
}

六、继承与静态成员

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

class Person
{
public :Person () {++ _count ;}
protected :string _name ; // 姓名
public :static int _count; // 统计人的个数。
};int Person :: _count = 0;
class Student : public Person
{protected :int _stuNum ; // 学号
};
class Graduate : public Student
{
protected :string _seminarCourse ; // 研究科目
};void TestPerson()
{Student s1 ;Student s2 ;Student s3 ;Graduate s4 ;cout <<" 人数 :"<< Person ::_count << endl;Student ::_count = 0;cout <<" 人数 :"<< Person ::_count << endl;
}

七、菱形继承与菱形虚继承

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

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

                  

菱形继承:菱形继承是多继承的一种特殊情况

菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余二义性的问题。
在Assistant的对象中Person成员会有两份

二义性问题,可以通过指定作用域限定操作符::指定作用域来避免

virtual关键字声明虚继承来避免数据冗余问题。

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 ; // 主修课程
};void Test ()
{
// 这样会有二义性无法明确知道访问的是哪一个Assistant a ;a._name = "peter";
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决a.Student::_name = "xxx";a.Teacher::_name = "yyy";
}

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和
Teacher的继承Person时使用虚拟继承,即可解决问题。

class Person
{
public :string _name ; // 姓名
};
class Student : virtual public Person
{
protected :int _num ; //学号
};
class Teacher : virtual public Person
{
protected :int _id ; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected :string _majorCourse ; // 主修课程
};
void Test ()
{Assistant a ;a._name = "peter";
}

八、虚拟继承原理

class A
{
public:int _a;
};
// class B : public A
class B : virtual public A
{
public:int _b;
};
// class C : public A
class C : virtual public A
{
public:int _c;
};
class D : public B, public C
{
public:int _d;
};
int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}

下图是菱形继承的内存对象成员模型:这里可以看到数据冗余

            

我们可以发现,B和C的公共部分A被提取到了最下方的位置,这个A同属于B和C,也可以单独使用d._a1来访问,在存储B和C的成员变量的同时存储了两个指针变量,这两个指针成为虚基指针,两个虚基指针各指向一张存储了B对象和C对象相对与A的偏移量的表,这张表称为虚基表,通过A相对于B、C存储位置的偏移量,来找到A。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/196043.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【JavaEE】单例模式

作者主页&#xff1a;paper jie_博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文于《JavaEE》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精力)打造&…

Python容器——字典

Key——Value 键值对

科技云报道:AI+PaaS,中国云计算市场迎来新“变量”?

科技云报道原创。 没有小的市场&#xff0c;只有还没有被发现的大生意。 随着企业数字化转型的逐级深入&#xff0c;市场需求进一步向PaaS和SaaS层进发&#xff0c;使之成为公有云服务市场增长的主要动力。 根据IDC最新发布的报告显示&#xff0c;2022-2027五年间中国公有云…

初识计算机网络

网络通信基础 1. IP地址2.端口号3.认识协议3.1协议分层 4. 网络数据传输的基本流程4.1 五元组4.2封装和分用 1. IP地址 IP地址主要用于表示网络主机,其他网络设备的网络地址,IP地址用于定位主机的网络地址 比如:发送快递的时候,需要知道对象的收货地址,才能将包裹送到目的地. …

APISpace 实名认证(身份证二要素)接口案例代码

1.实名认证&#xff08;身份证二要素&#xff09;API APISpace 的 实名认证&#xff08;身份证二要素API&#xff09;&#xff0c;核验身份证二要素&#xff08;姓名和身份证号码&#xff09;信息是否一致。 2.实名认证&#xff08;身份证二要素&#xff09;接口详情 2.1 接口…

外汇天眼:CySEC宣布与Titanedge Securities 达成90,000欧元的和解

塞浦路斯证券交易委员会&#xff08;CySEC&#xff09;12月1日宣布已经与塞浦路斯投资公司Titanedge Securities Ltd 达成了一项和解。 此次和解涉及可能违反了2017年《投资服务和活动以及受监管市场法》的情况。更具体地说&#xff0c;达成和解的调查涉及评估该公司在2017/565…

自动化测试的4大注意事项

自动化测试能够提高测试效率、覆盖率&#xff0c;降低测试成本和工作量&#xff0c;是软件开发中不可或缺的一部分。但前提是要确保自动化测试的有效性和可靠性&#xff0c;否则无效或错误的自动化测试&#xff0c;往往会对项目造成负面影响&#xff0c;如维护成本高、假阳性和…

高等职业学校新媒体营销实训室解决方案

背景 随着数字化时代的来临&#xff0c;新媒体营销成为企业推广和品牌建设的关键手段。为了培养高职学生在新媒体领域的实际操作能力&#xff0c;建立一套全面、系统的实训室方案至关重要。 目标 搭建高职新媒体营销实训室&#xff0c;旨在培养学生的实际操作能力&#xff0…

这些B端产品设计规范,你都知道吗?

设计规范虽然有其通用性&#xff0c;但因应对不同的业务环境和企业形态&#xff0c;其具体的运用可能会有所差异。对于新入行的B端设计师&#xff0c;各种B端组件可能会让他们感到困惑&#xff0c;不知在何种场景下应选择何种组件。这主要是因为我们在日常中学到的B端知识点多是…

人工智能与供应链行业融合:开启智能化供应链的新时代

随着人工智能技术的快速发展&#xff0c;供应链行业正迎来革命性变革。本文将探索人工智能在供应链管理中的应用领域&#xff0c;并分析其带来的益处和挑战&#xff0c;展望人工智能与供应链融合的未来发展趋势。 引言 供应链管理是企业运营中不可或缺的重要组成部分。它涵盖了…

用友NC word.docx接口存在任意文件读取漏洞

声明 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 一、产品介绍 用友 NC Cloud&#xff0c;大型企业数字化平台&#xff…

如何在Linux上搭建本地Docker Registry镜像仓库并实现公网访问

Linux 本地 Docker Registry本地镜像仓库远程连接 文章目录 Linux 本地 Docker Registry本地镜像仓库远程连接1. 部署Docker Registry2. 本地测试推送镜像3. Linux 安装cpolar4. 配置Docker Registry公网访问地址5. 公网远程推送Docker Registry6. 固定Docker Registry公网地址…

地方公派|商学院老师对口加拿大古德曼商学院访学交流

L老师荣幸地入选某省中青年教师国外访学进修计划&#xff0c;但因DIY申请职位无果&#xff0c;求助于我们。最终我们克服干扰因素&#xff0c;为其对口落实了加拿大最具声望和影响力的商学院之一布鲁克大学-古德曼商学院&#xff08;Goodman School of Business&#xff09;。 …

本地存储与复杂数据类型转换

1. 本地存储介绍 2.1 本地存储分类 - localStorage // 存储一个名字localStorage.setItem(uname, abc)// 获取名字console.log(localStorage.getItem(uname));// 删除本地存储 只删名字// localStorage.removeItem(uname)// 改localStorage.setItem(uname, aaa)// 存一个年龄 …

怎么翻译英文医学文献资料

文献翻译是一项要求严谨、精确且地道的工作&#xff0c;对于医学文献翻译更是如此。那么&#xff0c;怎么翻译英文医学文献资料&#xff0c;医学英文文献翻译公司哪个好&#xff1f; 专业人士指出&#xff0c;在翻译医学文献时&#xff0c;理解原文的语境是至关重要的。这不仅需…

【改进YOLOV8】融合动态蛇形卷积&DCNV2的草莓分级分割分割系统

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义 随着计算机视觉技术的不断发展&#xff0c;图像分割成为了一个重要的研究领域。图像分割可以将图像中的不同对象或区域进行分离&#xff0c;从而更好地理解图像内…

【数据结构(六)】排序算法介绍和算法的复杂度计算(1)

文章目录 1. 排序算法的介绍1.1. 排序的分类 2. 算法的时间复杂度2.1. 度量一个程序(算法)执行时间的两种方法2.2. 时间频度2.2.1. 忽略常数项2.2.2. 忽略低次项2.2.2. 忽略系数 2.3. 时间复杂度2.4. 常见的时间复杂度2.5. 平均时间复杂度和最坏时间复杂度 3. 算法的空间复杂度…

测试用例:微信发红包测试用例(最新版)

测试核心&#xff08;重点&#xff09;&#xff1a; 功能界面安全性易用性兼容性性能 一、功能测试 1、一对一红包&#xff1a; 一对一发出去的红包自己不能领取。 一对一红包金额&#xff1a;最多200。 2、群发红包&#xff1a; &#xff08;1&#xff09;拼手气红包&#xff…

(03)vite 处理 css

文章目录 系列全集vite 处理css流程vite如何解决协同开发&#xff0c;样式重复覆盖的问题&#xff1f;使用less通过配置&#xff0c;更改vite的css默认行为 系列全集 &#xff08;01&#xff09;vite 从启动服务器开始 &#xff08;02&#xff09;vite环境变量配置 &#xff…

微服务调用组件Feign

JAVA 项目中如何实现接口调用&#xff1f; 1&#xff09;Httpclient HttpClient 是 Apache Jakarta Common 下的子项目&#xff0c;用来提供高效的、最新的、功能丰富 的支持 Http 协议的客户端编程工具包&#xff0c;并且它支持 HTTP 协议最新版本和建议。HttpClient 相比传…