你知道C++多少——继承

news2024/11/24 20:40:05

🌈个人主页:小新_-

🎈个人座右铭:“成功者不是从不失败的人,而是从不放弃的人!”🎈

🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝

🏆所属专栏:话说那些与C++的爱恨情仇   欢迎订阅,持续更新中~~~

           

                                    ✨让小新带着你快乐的学习吧~✨

1.继承的概念及定义

1.1继承的概念

继承 (inheritance) 机制是面向对象程序设计 使代码可以复用 的最重要的手段,它允许程序员在
持原有类特性的基础上进行扩展 ,增加功能,这样产生新的类,称派生类。继承 呈现了面向对象
程序设计的层次结构 ,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,
承是类设计层次的复用
class Person
{
public:
    void Print()
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }
protected:
    string _name = "peter"; // 姓名
    int _age = 18;// 年龄
};
// 继承后父类的 Person 的成员(成员函数 + 成员变量)都会变成子类的一部分。这里体现出了
Student Teacher 复用了 Person 的成员。下面我们使用监视窗口查看 Student Teacher 对象,可以看到变量的复用。调用Print 可以看到成员函数的复用。


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;
 }

1.2 继承定义

1.2.1定义格式

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

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 继承下来的成员都只能在派生类的类里
面使用,实际中扩展维护性不强
// 实例演示三种继承关系下基类成员的各类型成员访问关系的变化  
class Person
{
public :
void Print ()
{
cout << _name << endl ;
}
protected :
string _name ; // 姓名
private :
int _age ; // 年龄
};
//class Student : protected Person
//class Student : private Person
class Student : public Person
{
protected :
int _stunum ; // 学号
};

2.基类和派生类对象赋值转换

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

基类对象不能赋值给派生类对象。 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类 的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(RunTime Type Information)的dynamic_cast来进行识别后进行安全转换。(ps:这个我们后面再讲解,这里先了解一下)

3.继承中的作用域

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

2. 子类和父类中有同名成员, 子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,
也叫重定义。 (在子类成员函数中,可以 使用 基类 :: 基类成员 显示访问
3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
4. 注意在实际中在 继承体系里 面最好 不要定义同名的成员

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

6 个默认成员函数, 默认 的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类
中,这几个成员函数是如何生成的呢?
  1.  派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
  2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  3. 派生类的operator=必须要调用基类的operator=完成基类的复制。
  4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能 保证派生类对象先清理派生类成员再清理基类成员的顺序。
  5. 派生类对象初始化先调用基类构造再调派生类构造。
  6. 派生类对象析构清理先调用派生类析构再调基类的析构。
  7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个我们后面会讲 )。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加 virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。

5.继承与友元

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

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 );
}

6.继承与静态成员

基类定义了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 ;
}

7.复杂的菱形继承及菱形虚拟继承

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

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 ;
}

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

下图是菱形虚拟继承的内存对象成员模型:这里可以分析出 D 对象中将 A 放到的了对象组成的最下
面,这个 A 同时属于 B C ,那么 B C 如何去找到公共的 A 呢? 这里是通过了 B C 的两个指针,指
向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量
可以找到下面的 A
// 有童鞋会有疑问为什么 D B C 部分要去找属于自己的 A ?那么大家看看当下面的赋值发生时, d
不是要去找出 B/C 成员中的 A 才能赋值过去?
D d ;
B b = d ;
C c = d ;
下面是上面的 Person 关系菱形虚拟继承的原理解释:

8.继承的总结和反思

1. 很多人说 C++ 语法复杂,其实多继承就是一个体现。有了多继承 ,就存在菱形继承,有了菱
形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设
计出菱形继承。否则在复杂度及性能上都有问题。
2. 多继承可以认为是 C++ 的缺陷之一,很多后来的 OO 语言都没有多继承,如 Java
3. 继承和组合
  • public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
  • 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。 
  • 优先使用对象组合,而不是类继承
  • 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称 为白箱复用(white-box reuse)。术语白箱是相对可视性而言:在继承方式中,基类的 内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很 大的影响。派生类和基类间的依赖关系很强,耦合度高。
  • 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复(black-box reuse),因为对象的内部细节是不可见的。对象只以黑箱的形式出现。
  • 组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
  • 实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有 些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。

 

// Car BMW Car Benz 构成 is-a 的关系
class Car {
protected :
string _colour = " 白色 " ; // 颜色
string _num = " ABIT00" ; // 车牌号
  };
class BMW : public Car {
public :
void Drive () { cout << " 好开 - 操控 " << endl ;}
  };
class Benz : public Car {
public :
void Drive () { cout << " 好坐 - 舒适 " << endl ;}
  };
// Tire Car 构成 has-a 的关系
class Tire {
protected :
     
string _brand = "Michelin" ;   // 品牌
     
size_t _size = 17 ;         // 尺寸
  };
class Car {
protected :
string _colour = " 白色 " ; // 颜色
string _num = " ABIT00" ; // 车牌号
   
Tire _t ; // 轮胎
  };

9.笔试面试题

1. 什么是菱形继承?菱形继承的问题是什么?
2. 什么是菱形虚拟继承?如何解决数据冗余和二义性的
3. 继承和组合的区别?什么时候用继承?什么时候用组合?

 

最后,感谢大家

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

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

相关文章

蓝桥杯模块三:蜂鸣器和继电器的基本控制

模块训练题目&#xff1a; 一、蜂鸣器电路图 1.电路图 2.电路分析 138译码器控制Y5,Y5控制Y5C&#xff0c;Y5C低电平控制芯片开启P0口控制ULN2003继而控制蜂鸣器端口和继电器端口 二、程序代码 1.138译码器控制端口函数 建立初始化函数选择锁存器 2.实现题目功能 在LED代…

24-10-13-读书笔记(二十五)-《一只特立独行的猪》([中] 王小波)用一生来学习艺术

文章目录 《一只特立独行的猪》&#xff08;[中] 王小波&#xff09;目录阅读笔记记录总结 《一只特立独行的猪》&#xff08;[中] 王小波&#xff09; 十月第五篇&#xff0c;放慢脚步&#xff0c;秋季快要过去了&#xff0c;要步入冬季了&#xff0c;心中也是有些跌宕起伏&am…

Guitar Pro怎么制作伴奏谱,吉他谱制作软件guitar pro教程

在诸多教学吉他谱制作软件中Guitar Pro是一款非常优秀的软件&#xff0c;它是专为吉他和其他弦乐器设计&#xff0c;且能提供乐谱编辑、音轨录制和播放、和弦与音阶库等功能的强大软件。Guitar Pro不仅具有强大的乐谱编辑功能&#xff0c;其用户界面也易于上手&#xff0c;更支…

ThingsBoard规则链节点:Script节点详解

引言 脚本节点简介 用法 含义 应用场景 实际项目运用示例 智能楼宇管理系统 工业自动化生产线 结论 引言 ThingsBoard是一个功能强大的物联网平台&#xff0c;它支持设备管理、数据收集与处理以及实时监控。其核心组件之一是规则引擎&#xff0c;允许用户定义复杂的业务…

vue特效,一片动态星空

vue实现漂亮星空&#xff0c;超级简单 1.创建vue项目&#xff1a; vue create demo 2.注册vuecli : npm i element-ui -S 3.加载依赖 &#xff1a;npm i 4.运行项目 :npm run serve <!DOCTYPE html> <html lang"en"> <head><…

JavaWeb 19 AJAX

"我就是希望你好&#xff0c;就像很多人希望我好一样&#xff0c;特别简单&#xff0c;特别真挚。也不为了什么&#xff0c;就是希望你好" —— 24.10.13 一、什么是AJAX AJAX Asynchronous JavaScript and XML(异步的JavaScript和XML) AJAX不是新的编程语言&…

仿新版QQ的聊天小软件

仿新版QQ的聊天小软件 文章说明核心源码效果展示源码下载 文章说明 新开一个聊天组件的项目的想法主要来源于想学习一下消息队列的使用&#xff0c;后来在书写界面和一些功能模块时&#xff0c;又想到可以抽离出来&#xff0c;分别写几篇文章&#xff0c;主要介绍扫码登陆、消息…

MQ快速入门【详细】个人笔记 讲解通俗易懂

1.同步通讯和异步通讯 同步通讯&#xff1a;如果举个例子来说&#xff0c;同步通讯就像是两个人在打电话&#xff0c;一方说的话&#xff0c;能够立马传给另一方&#xff0c;消息的时效性非常高&#xff0c;但是相对的&#xff0c;只能是给一个人通讯&#xff0c;如果这个时候&…

React (三) 创建安装脚手架,类组件与函数式组件;生命周期;父子通信props;插槽;非父子通信Context

文章目录 一、脚手架的创建与安装1. 认识脚手架2. 安装脚手架3. 创建react项目4. 项目结构 二、从0编写三、组件化开发1. 什么是组件化开发2. 类组件3. render函数4. 函数式组件 四、生命周期1. 挂载Mount2. 更新Update3. 卸载Unmount4. 不常用的生命周期 五、父子组件通信1. 父…

数据结构之队列(python)

华子目录 1.队列存储结构1.1队列基本介绍1.2队列的实现方式 2.顺序队列2.1顺序队列的介绍2.2顺序队列的简单实现2.3代码实现 3.链式队列和基本操作3.1链式队列数据入队3.2链式队列数据出队3.3队列的链式表示和实现 1.队列存储结构 1.1队列基本介绍 队列的两端都"开口&qu…

springcloud之服务集群注册与发现 Eureka

前言 1&#xff1a;对于能提供完整领域服务接口功能的RPC而言&#xff0c;例如&#xff1b;gRPC、Thrift、Dubbo等&#xff0c;服务的注册与发现都是核心功能中非常重要的一环&#xff0c;使得微服务得到统一管理。 2&#xff1a;在分布式领域中有个著名的CAP理论&#xff1b;…

Vue3 + Element plus 实现切换el-radio前二次确认

Vue3 Element plus 实现切换el-radio前二次确认 场景&#xff1a;点击切换el-radio之前判断当前内容是否有改变&#xff0c;如有改变弹窗提示切换el-radio将销毁操作&#xff0c;弹窗二次确认是否切换 问题&#xff1a; el-radio 没有提供类似于beforeUpdate这样的钩子去处理这…

探索极简计算的新边界:从Uxn虚拟机看未来编程生态

越来越多的开发者追求复杂度和功能性的极致,然而,有一个小众的编程社区选择了截然不同的道路——极简主义。Uxn虚拟机便是这一思潮的代表之一。它通过简洁的指令集和有限的硬件资源模拟,试图打造一种可以在多种设备上运行的便携性编程环境。 与主流的重型操作系统和复杂…

HTB:Legacy[WriteUP]

目录 连接至HTB服务器并启动靶机 1.How many TCP ports are open on Legacy? 2.What is the 2008 CVE ID for a vulnerability in SMB that allows for remote code execution? 3.What is the name of the Metasploit module that exploits CVE-2008-4250? 4.When expl…

VS+QT 自定义插件变成动态库加载及使用

一、前言 有个界面需要重复使用某个自定义的控件&#xff0c;希望自定义控件能够像动态库文件那样&#xff0c;添加引用lib就能使用&#xff0c;经过多次太坑后&#xff0c;总结如下 二、实现方式 ① 新建项目&#xff0c;选择"Qt Designer Custom Widget" 创建自定…

Springboot从入门到起飞-【day01】

个人主页→VON 收录专栏→Springboot从入门到起飞 一、前言 经过了近两个月的沉淀开始了新专栏的学习&#xff0c;经过深思熟虑还是决定重新学习java&#xff0c;因为基础部分东西太多太乱就不进行逐一的更新了&#xff0c;等到学完了一同进行更新。 二、Springboot简要概述 …

kafka消息队列核心内容及常见问题

目录 1. 使用消息队列的目的&#xff08;优点与缺点&#xff09; 2. 常见各消息队列对比 3. kafka介绍 3.1 kafka简介 3.2 kafka特点 3.3 kafka系统架构 3.4 设置数据可靠性 3.4.1 Topic 分区副本 3.4.2 消息确认机制 4. 常见问题&#xff08;面试题&#xff09; 4.…

Springboot 接入 WebSocket 实战

Springboot 接入 WebSocket 实战 前言&#xff1a; WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。 简单理解&#xff1a; 1&#xff0c;常见开发过程中我们知道 Http协议&#xff0c;客户端…

LED显示器闪烁故障原因及解决方法

随着电子显示屏在各行各业的广泛应用&#xff0c;LED显示器因其高亮度、节能和灵活的宣传功能&#xff0c;成为了宣传推广的重要工具。然而&#xff0c;LED显示器在使用过程中有时会出现闪烁的现象&#xff0c;这不仅影响了显示效果&#xff0c;还可能影响用户体验。针对这一问…

【layui】多文件上传组件实现

插件预览效果&#xff1a; 需要引入layui的脚本文件layui.js和样式文件layui.css html代码&#xff1a; <div class"layui-input-block"><div class"layui-upload-list"><table class"layui-table"><colgroup><col…