从零开始的c++之旅——继承

1. 继承

1.继承概念及定义

        继承是面向对象编程的三大特点之一,它使得我们可以在原有类特性的基础之上,增加方法
        和属性,这样产生的新的类,称为派生类。

        继承 呈现了⾯向对象程序设计的层次结构,以前我们接触的函数层次的 复⽤,继承是类设计
        层次的复⽤。

        例如我们在实现老师的类teacher和学生的类student时,他们都有姓名/地址/ 电话/年龄等成员
        变量,都有identity⾝份认证的成员函数,设计到两个类⾥⾯就是冗余的。当然他们 也有⼀些
        不同的成员变量和函数,⽐如⽼师独有成员变量是职称,学⽣的独有成员变量是学号;学⽣
        的独有成员函数是学习,⽼师的独有成员函数是授课。

class Student
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 void identity(){// ...}// 学习 void study(){// ...}
protected:string _name = "peter"; // 姓名 string _address; // 地址 string _tel; // 电话 int _age = 18; // 年龄 int _stuid; // 学号 
};class Teacher
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 void identity(){// ...}// 授课 void teaching(){//...}
protected:string _name = "张三"; // 姓名 int _age = 18; // 年龄 string _address; // 地址 string _tel; // 电话 string _title; // 职称 
};

        我们可以将他们两个类中冗余的部分提取出来,实现一个新的类Person,就可以复用这些成
        员,不需要重新定义,省时省力。

class Person
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 void identity(){cout << "void identity()" << _name << endl;}
protected:string _name = "张三"; // 姓名 string _address; // 地址 string _tel; // 电话 int _age = 18; // 年龄 
};
class Student : public Person
{
public:// 学习 void study(){cout << Person::_name << endl;}
protected:int _stuid;// 学号string _name = "李四";
};
class Teacher : public Person
{
public:// 授课 void teaching(){//...}
protected:string title; // 职称 string _name = "王五";};
int main()
{Student s;Teacher t;s.identity();t.identity();return 0;
}

1.2 继承定义

1.2.1 格式

如下图所示,Person是基类,也称作⽗类。Student是派⽣类,也称作⼦类。

1.2.2 继承方式及其特点

         1.如果我们不想让基类的对象被派生类访问,就将基类中的对象定义为private,这样无论是
            哪种继承方式,无论是在类内部还是外部都无法访问。

         2.如果想让基类的对象继承之后可以在派生类中被访问,但是不能在类外被访问,就定义成
            protect,这也是protect和private的区别,所以如果不涉及继承他们两个的作用是一致的

         3.有上表格我们可以总结出继承的规律:
            基类的私有成员在派⽣类都是不可⻅。
            基类的其他成员 在派⽣类的访问⽅式==Min(成员在基类的访问限定符,继承⽅式),
            public > protected > private。

         4.继承方式最好显示写出来,即使我们知道class默认private,struct默认public。

         5. 当然虽然c++中的继承方式相对复杂,但是我们实际当中基本都是使用public继承。
             也不提倡使用protetced/private继承,因为这两者继承下来的成员都只能在派⽣类的类⾥⾯
             使⽤,实 际中扩展维护性不强。

1.3 继承类模板

        在继承类模板的时候需要注意,当我们的基类是一个类模板的时候,我们使用其中的方法需
        要指定类域,不然编译器找不到对应的方法,因为模板的实例化是按需实例化,只有用了对
        饮的方法才对去实例化对应的方法,

	template<class T>class stack : public std::vector<T>{public:void push(const T& x){ vector<T>::push_back(x);//push_back(x);}void pop(){vector<T>::pop_back();}const T& top(){return vector<T>::back();}bool empty(){return vector<T>::empty();}};

2. 基类和派生类之间的转换

         public继承的 派生类对象 可以赋值给 基类的指针/基类的引用 。我们形象的将其称为切片。
        意思就是将派生类中的基类那部分切出来,基类指针只会指向派生类对象中基类有的那部分
        对象,下面为示例

class Person
{
protected :string _name; // 姓名 string _sex; // 性别 int _age; // 年龄 
};
class Student : public Person
{
public :int _No ; // 学号 
};
int main()
{Student sobj ;// 1.派⽣类对象可以赋值给基类的指针/引⽤ Person* pp = &sobj;Person& rp = sobj;// ⽣类对象可以赋值给基类的对象是通过调⽤后⾯会讲解的基类的拷⻉构造完成的 Person pobj = sobj;//2.基类对象不能赋值给派⽣类对象,这⾥会编译报错 sobj = pobj;return 0;

 需要注意的是 Person& rp = sobj 这条语句在执行过程中并没有产生临时对象,而是直接赋值给了rp。

3. 继承中的作用域

3.1 隐藏规则:

        1. 在继承体系中基类和派生类都有独自的作用域。
        2. 若基类和派生类有同名成员,则派生类成员将会屏蔽基类对同名成员的直接访问,这种现
            象叫做隐藏。
        3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
        4. 住哟i在实际继承体系中最好不要定义同名成员。

3.2 注意事项

        函数重载和隐藏都要求同名函数,但是函数重载的行为是要求同一作用域,而隐藏的要求是
        两个类为派生类和基类中有函数同名

        如果我们要在派生类当中调用基类被隐藏的同名函数,就需要指定类域。

4. 派生类的默认成员函数

4个默认成员函数

        类有6个默认成员函数,我们主要讨论其中较为重要的4个。

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

	Student(const char* name, int num ,const string& address): Person(name)//父类成员调用父类的构造函数,没有就在子类初始化列表显示写, _num(num), _address(address){cout << "Student()" << endl;}

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

	Student(const Student& s): Person(s)//把基类看作一个整体, _num(s._num){cout << "Student(const Student& s)" << endl;}

    3. 派生类的operator=必须调用基类的operator完成基类的复制。需要注意的是派生类的
        operator=会隐藏基类的operator等于,使用显示调用基类operrtor=需要指定类域

	Student& operator = (const Student& s){if (this != &s){// 构成隐藏,所以需要显⽰调⽤ Person::operator =(s);//将基类对象看作整体_num = s._num;}return *this;}

     4. 派生类的析构函数会在被调用后自动取调用基类的析构函数,因为这样才能保证派 ⽣类对象
         先清理派⽣类成员再清理基类成员的顺序。因此我们显示写派生类的析构函数时候需要注意
         不用显示写基类的析构函数

	~Student(){//不用写基类的析构函数}

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

     6. 因为多态中⼀些场景析构函数需要构成重写,重写的条件之⼀是函数名相同(后续的多态章节
         会讲解)。那么编译器会对析构函数名进⾏特殊处理,处理成destructor(),所以基类析构函数
         不加 virtual的情况下,派⽣类析构函数和基类析构函数构成隐藏关系。

要点总结


         不写,编译器默认生成的行为是什么?
         默认生成不符合我们需求,自己写,得怎么写?
 
         特点:子类中继承下来的父类成员当做一个整体对象


         构造:
                 默认:子类成员 内置类型(有缺省值就用,没有不确定)和自定义类型(默认构造) + 父类
                 成员(必须调用父类默认构造) 


         拷贝构造:
                子类成员 内置类型(值拷贝)和自定义类型(这个类型拷贝构造) + 父类成员(必须调用父类
                拷贝构造)

         赋值重载:
                类似拷贝构造

         析构:
                子类成员 内置类型(不处理)和自定义类型(调用他的析构) + 父类成员(调用他的析构)
                自己实现的话,注意不需要显示调用父类析构,子类析构函数结束后,会自动调用父类
                析构

4. 继承和友元

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

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;
}
int main()
{Person p;Student s;// 编译报错:error C2248: “Student::_stuNum”: ⽆法访问 protected 成员 // 解决⽅案:Display也变成Student 的友元即可 Display(p, s);return 0;
}

5. 继承和静态成员

基类定义了static静态成员,则整个继承体系⾥⾯只有⼀个这样的成员。⽆论派⽣出多少个派⽣类,都 只有⼀个static成员实例。

class Person
{
public:string _name;static int _count;
};
int Person::_count = 0;
class Student : public Person
{
protected:int _stuNum;
};
int main()
{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;return 0;
}

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

6.1 继承模型

        单继承: 一个派生类只有个一个直接的基类

        多继承: ⼀个派⽣类有两个或以上直接基类,多继承对象在内存中的模型 是,先继承的基类
                        在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后⾯。

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

从上图得出,菱形继承数据冗余和二义性问题,在Assistant的对象中Person成员会有两份。⽀持多继承就 ⼀定会有菱形继承,所以实践中我们也是不建议 设计出菱形继承这样的模型的。

                        ​​​​​​​        ​​​​​​​        

6.1 虚继承

        在具有二义性的类前面加上关键字virtual,可以实现虚继承,解决菱形继承的问题,但是对于
        计算机来说会造成多余的新能损失

class Person
{
public:string _name; // 姓名 /*int _tel;int _age;string _gender;string _address;*/// ...
};// 使⽤虚继承Person类 
class Student : virtual public Person
{
protected:int _num; //学号 
};// 使⽤虚继承Person类 
class Teacher : virtual public Person
{
protected:int _id; // 职⼯编号 
};// 教授助理 
class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程 
};int main()
{// 使⽤虚继承,可以解决数据冗余和⼆义性 Assistant a;a._name = "peter";return 0;
}

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

        我们可以设计出多继承,但是不建议设计出菱形继承,因为菱形虚拟继承以后,⽆论是使⽤
        还是底层 都会复杂很多。

        虽然菱形继承会造成一些不必要的麻烦,但是一些底层的实现还是需要用上的。但对于小萌
        新来说还是只可远观不可亵玩

8. 继承和组合 

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

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

        继承允许你根据基类的实现来定义派⽣类的实现。这种通过⽣成派⽣类的复⽤通常被称为⽩
        箱复⽤。术语“⽩箱”是相对可视性⽽⾔:在继承⽅式中,基类的内部细节对派⽣类可⻅。继承
        ⼀定程度破坏了基类的封装,基类的改变,对派⽣类有很⼤的影响。派⽣类和基类间的依 赖
        关系很强,耦合度⾼。

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

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

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

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

相关文章

6.1、实验一:静态路由

源文件获取&#xff1a;6.1_实验一&#xff1a;静态路由.pkt: https://url02.ctfile.com/f/61945102-1420248902-c5a99e?p2707 (访问密码: 2707) 一、目的 理解路由表的概念 会使用基础命令 根据需求正确配置静态路由 二、准备实验 1.实验要求 让PC0、PC1、PC2三台电脑…

logback日志级别动态切换四种方案

生产环境中经常有需要动态修改日志级别。 现在就介绍几种方案 方案一&#xff1a;开启logback的自动扫描更新 配置如下 <?xml version"1.0" encoding"UTF-8"?> <configuration scan"true" scanPeriod"60 seconds" debug…

Qt字符编码

目前字符编码有以下几种&#xff1a; 1、UTF-8 UTF-8编码是Unicode字符集的一种编码方式(CEF)&#xff0c;其特点是使用变长字节数(即变长码元序列、变宽码元序列)来编码。一般是1到4个字节&#xff0c;当然&#xff0c;也可以更长。 2、UTF-16 UTF-16是Unicode字符编码五层次…

postman 获取登录接口中的返回token并设置为环境变量的方法 postman script

postman是一个比较方便的API开发调试工具&#xff0c; 我们在访问API时一般都需要设置一个token来对服务进行认证&#xff0c; 这个token一般都是通过登录接口来获取。 这个postman脚本放到登录接口的sctipt--> post-response里面即可将登陆接口中返回的token值设置到postma…

使用Django REST framework构建RESTful API

使用Django REST framework构建RESTful API Django REST framework简介 安装Django REST framework 创建Django项目 创建Django应用 配置Django项目 创建模型 迁移数据库 创建序列化器 创建视图 配置URL 配置全局URL 配置认证和权限 测试API 使用Postman测试API 分页 过滤和排序…

消息队列面试——打破沙锅问到底

消息队列的面试连环炮 前言 你用过消息队列么&#xff1f;说说你们项目里是怎么用消息队列的&#xff1f; 我们有一个订单系统&#xff0c;订单系统会每次下一个新订单的时候&#xff0c;就会发送一条消息到ActiveMQ里面去&#xff0c;后台有一个库存系统&#xff0c;负责获取…

Rust 力扣 - 1493. 删掉一个元素以后全为 1 的最长子数组

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 本题我们能转换成求只包含一个0的子数组的最长长度 如果数组中不存在0&#xff0c;则结果为数组长度减一 我们使用一个left指针指向子数组的左边界&#xff0c;然后我们遍历子数组的右边界&#xff0c;我们记录…

在 Windows 系统上,在两台机器上测试 MySQL 集群实现实时备份的基本步骤:

在两台机器上测试 MySQL 集群实现实时备份的基本步骤&#xff1a; 一、环境准备 机器配置 确保两台机器&#xff08;假设为服务器 A 和服务器 B&#xff09;能够互相通信&#xff0c;例如它们在同一个局域网内&#xff0c;并且开放了 MySQL 通信所需的端口&#xff08;默认是 3…

常见的开源软件许可证及其应用案例

目录 引言 开源的定义 开源许可证的种类 常见的开源许可证及其应用案例 结论 引言 开源软件在过去几十年中迅速发展&#xff0c;已经成为软件开发的重要组成部分。开源不仅仅是一种技术模式&#xff0c;更是一种文化和社区精神。本文将详细介绍开源的定义、开源许可证的种…

校招回顾 | “青春不散场,梦想正起航”,极限科技(INFINI Labs)亮相湖北工业大学 2025 秋季校园招聘会

10 月 31 日&#xff0c;极限科技&#xff08;INFINI Labs&#xff09; 受邀参加 湖北工业大学 2025 届秋季校园招聘会&#xff0c;这不仅是一次与满怀激情的青年学子们的深度碰撞&#xff0c;更是一场关于青春与未来的美好邂逅。让我们一起回顾校招现场的精彩瞬间&#xff0c;…

基于python的机器学习(一)—— 基础知识(Scikit-learn安装)

目录 一、机器学习基础 1.1 机器学习概述 1.2 监督学习、无监督学习和强化学习 1.3 聚类、分类、回归、标注 1.3.1 聚类 1.3.2 分类 1.3.3 回归 1.3.4 标注 1.4 机器学习、人工智能和数据挖掘 1.5 机器学习的三个要素 二、Scikit-learn 机器学习库 2.1 Scikit-lea…

第五篇: 使用Python和BigQuery进行电商数据分析与可视化

使用Python和BigQuery进行电商数据分析与可视化 大数据分析对于电商业务的洞察至关重要。在这篇文章中&#xff0c;我们将使用Python结合Google BigQuery来分析电商数据集&#xff0c;以最畅销商品和平均订单价格最高的前10位客户为主题&#xff0c;展示如何通过数据可视化提供…

基于SpringBoot+Vue的快递物流信息查询系统设计与实现【前后端分离】

基于SpringBootVue的快递物流信息查询系统设计与实现 摘要 随着电子商务的快速发展&#xff0c;快递物流系统的重要性愈发突出。针对用户对快递信息的实时查询需求&#xff0c;本系统结合Spring Boot和Vue技术&#xff0c;设计并实现了一个高效、易用的快递物流信息查询系统。系…

开源免费的API网关介绍与选型

api网关的主要作用 API网关在现代微服务架构中扮演着至关重要的角色&#xff0c;它作为内外部系统通信的桥梁&#xff0c;不仅简化了服务调用过程&#xff0c;还增强了系统的安全性与可管理性。例如&#xff0c;当企业希望将内部的服务开放给外部合作伙伴使用时&#xff0c;直…

WPF+MVVM案例实战(十七)- 自定义字体图标按钮的封装与实现(ABC类)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1、案例效果1、按钮分类2、ABC类按钮实现1、文件创建2、字体图标资源3、自定义依赖属性4、按钮特效样式实现 3、按钮案例演示1、页面实现与文件创建2、依赖注入3 运…

使用MongoDB Atlas构建无服务器数据库

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用MongoDB Atlas构建无服务器数据库 MongoDB Atlas 简介 注册账户 创建集群 配置网络 设置数据库用户 连接数据库 设计文档模式…

【大语言模型】ACL2024论文-07 BitDistiller: 释放亚4比特大型语言模型的潜力通过自蒸馏

【大语言模型】ACL2024论文-07 BitDistiller: 释放亚4比特大型语言模型的潜力通过自蒸馏 目录 文章目录 【大语言模型】ACL2024论文-07 BitDistiller: 释放亚4比特大型语言模型的潜力通过自蒸馏目录摘要研究背景问题与挑战如何解决创新点算法模型实验效果代码推荐阅读指数&…

P9220 「TAOI-1」椎名真昼

P9220 「TAOI-1」椎名真昼 考点&#xff1a;博弈论、拓扑、强连通分量。 难度&#xff1a; 提高/省选- 。 题意&#xff1a; ​ Alice 和 Bob 玩游戏&#xff0c;给定一个有向图&#xff0c;每个点有初始颜色&#xff08;黑/白&#xff09;。 ​ 双方轮番操作一次&#xf…

计算机网络:网络层 —— 多播路由选择协议

文章目录 多播路由选择协议多播转发树构建多播转发树基于源树的多播路由选择建立广播转发树建立多播转发树 组共享树的多播路由选择基于核心的生成树的建立过程 因特网的多播路由选择协议 多播路由选择协议 仅使用 IGMP 并不能在因特网上进行IP多播。连接在局域网上的多播路由…

例行性工作

1、单一执行------at-----仅处理执行一次就结束了 1.1工作过程 /etc/at.allow&#xff0c;写在该文件的人可以使用at命令/etc/at.deny&#xff0c;黑名单两个文件如果都不存在&#xff0c;只有root能使用 1.2命令详解------命令格式&#xff1a;at [参数] [时间] 2、循环执行…