C++ —— 继承

server/2024/9/24 2:51:25/

什么是继承?

继承是指一种代码可以被复用的机制,在一个类的基础上进行扩展,产生的新类叫做派生类,被继承的类叫基类。(也可称为子类和父类)

继承的写法:

class B : 继承方式 A              (类B以public/private/protected的方式继承类A)

当需要继承多个类时:

 class C : 继承方式 A B              (类C以public/private/protected的方式继承类A、B)

注意:谁在前就先继承谁,如上则是先继承A再继承B

继承究竟做了什么?

继承继承,顾名思义就是继承了父类的成员(准确的说是会继承除去析构、构造以外所有的成员)再加上子类的扩展形成新类。

如下图:(请先忽略构造函数部分)子类student继承了父类people的_name和_age,所以这个子类中也有_name和_age成员变量。

继承的特性

继承方式

(父类XX成员以XX继承后,在子类是XX成员)

类成员/继承方式publicprotectedprivate
父类的public成员子类的public成员子类的protected成员子类的private成员
父类的protected成员子类的protected成员子类的protected成员子类的private成员
父类的private成员在子类中不可见在子类中不可见在子类中不可见

 总结:在继承方式和原本的成员权限中选权限更小的,如在父类中为public以protected继承则继承后的权限为protected(权限大小:public<protected<private)

虽然继承方式有三种,但常用的却只有public。

切片

在继承中,往往可以进行用子类对象直接调用父类函数的情况。像前者这种情况往往发生在相似类型身上,因为相似所以可以转换。而父类有的,子类都有,所以程序执行时就会把子类中与父类相同的部分拿出来去调用函数,这种情况就叫切片。

 

例如:将Student的对象s拿去给People的对象p去调用拷贝构造就是使用了切片的特性

子类构造与析构

虽说父类有的子类都有,理论上说构造自己搞定进行,但规定上在构造子类对象时要先调用父类构造函数,再构造子类部分。(继承是一种复用代码的机制,这里也是复用的父类构造,减少了工作量)

这就是为什么Student的构造函数中的初始化列表要调用  People(string name,int age)  这个构造(这个过程同样会发生切片)

构造要手动调用,那析构也要吗? 

手动调用时结果父类部分析构了两次。实际上在子类析构结束后会自动调用父类析构去释放子类中与父类一致的部分,不需要去手动调用。

隐藏(重定义)

当父类与子类出现同名的成员变量或成员函数时,子类成员将屏蔽对父类成员的直接访问,这时通过子类对象去访问这个同名成员时优先访问子类的。

class People
{
public:void printf(){cout << _name << " " << _age << endl;}People(string name, int age):_name(name),_age(age){}People(People& p):_name(p._name),_age(p._age){}~People(){cout << "~People()" << endl;}string _name;int _age;
};class Student : public People 
{
public:void printf(){cout << _name << " " << _age << " "<< _Sno<<endl;}Student(string name, int age, int sno):People(name, age),_Sno(sno){}~Student(){cout << "~Student()" << endl;}//学号int _Sno;
};int main()
{Student s("swi",20,34);s.printf();return 0;
}

 之所以会出现隐藏这种特性与他的搜索逻辑有关,当我们通过子类对象访问成员时,会先去子类中寻找这个成员,如果没找到再去父类中寻找。


以上所讲都是继承的通用的特性,样例均为单继承

多继承

多继承顾名思义就是继承多个类。

class A
{
public:void printf(){cout << _a << endl;}int _a = 1;
};class B
{
public:void printf(){cout << _b << endl;}int _b = 2;
};//C同时继承了类A和类B(先继承的A,后继承的B)
class C:public A,B
{
public:void printf(){cout << _c << endl;}int _c = 3;
};int main()
{C* c = new C;return 0;
}

查看内存后我们发现,缺省值为1的_a在缺省值为2的_b 的前面,class C:public A,B的顺序能够决定谁先被继承。

 A,B反过来后:

多继承的问题

 假设他们的成员变量分别为_a,_b,_c,_d,那么他们在内存中的存储会如下图:

此时就会出现两个问题:

  • 二义性:当我访问_a时,到底访问哪一个_a。
  • 数据冗余:出现重复数据,占用不必要空间 

解决方法

在继承方式前加上关键字virtual构成虚继承(注意:想要解决菱形继承的问题,放virtual关键字的类是出现重复部分的若干个子类,被virtual修饰的子类的父类称作虚基类

如下图的菱形问题(菱形问题是这类问题的总称,并不一定是菱形):

不处理时:

 使用virual处理后:

虚基类的成员将会在下方找到一片公共空间去存储,这样一来就解决了二义性和数据冗余。

但是原来两条红线存储的又是什么?

将这四行数字重新组合排序可以得到两个地址,去查找这个地址,发现他分别存储了一个十六进制的28,一个十六进制的18。实际上这个地址是虚基表的地址,而虚基表存储的是地址偏移量,该类的部分的起始地址加上偏移量就能找到虚基类的成员变量_a。

 

多态

对不同的对象会有不同的实现方法,即为多种形态。

多态的条件:

  1. 虚函数的重写(父子类虚函数需要三同,三同指函数名、参数、返回值)
  2. 父类的指针或引用去调用

 但是有三种例外:

  1. 协变(基类与派生类的虚函数返回值不同)
  2. 析构函数的重写(看似不符合函数名相同的条件,实际上编译器对其进行了特殊处理,编译后析构函数的名字统一处理成destructor)
  3. 派生类虚函数重写可以不加virtual(但建议写上)

虚函数

virtual修饰的函数叫做虚函数,虚函数只能是类中非静态的成员函数。

虚函数的重写

子类和父类中的虚函数拥有相同的名字,返回值,参数列表,那么称子类中的虚函数重写了父类的虚函数,或者叫做覆盖。(虚函数重写,重写的是函数体)

//成人
class People
{
public:virtual void fun(){cout << "全票" << endl;}
};//儿童
class Child : public People 
{
public:virtual void fun(){cout <<"半票" << endl;}
};void buyTicket(People& p)
{p.fun();
}int main()
{People p;Child c;buyTicket(p);buyTicket(c);return 0;
}

多态的原理

为什么会访问到不同的虚函数? 

使用父类的指针或引用去调用(用子类来调用就会产生切片),根据其指针或引用可以找到其对应的虚函数表,进而找到虚函数地址然后去调用不同的虚函数。


http://www.ppmy.cn/server/13459.html

相关文章

业务复习知识点Oracle查询

业务数据查询-1 单表查询 数据准备 自来水收费系统建表语句.sql 简单条件查询 精确查询 需求 &#xff1a;查询水表编号为 30408 的业主记录 查询语句 &#xff1a; select * from t_owners where watermeter 30408; 查询结果 &#xff1a; 模糊查询 需求 &#xff1a;查询业…

RK3588 Android13 鼠标风格自定义动态切换

前言 电视产品,客户提供了三套鼠标图标过来,要求替换系统中原有丑陋风格且要支持动态切换, 并且在 TvSetting 中要有菜单,客户说啥就是啥呗,开整。 效果图 test framework 部分修改文件清单 png 为鼠标风格资源图片,这里就不提供了,可自由找一个替换一下就行 framew…

QA测试开发工程师面试题满分问答20: 软件的安全性应从哪几个方面去测试?

软件的安全性测试应从多个方面进行&#xff0c;并确保覆盖以下关键方面&#xff1a; 当回答问题时&#xff0c;可以根据自己的经验和知识&#xff0c;从上述要点中选择适合的方面进行详细说明。强调测试的综合性、全面性和持续性&#xff0c;并强调测试的重要性以及如何与开发团…

Playwright UI 自动化测试实战

随着软件开发的日益复杂和用户期望的不断提高&#xff0c;UI&#xff08;用户界面&#xff09;自动化测试变得越来越重要。Playwright是一个开源的自动化测试工具&#xff0c;可以用于测试Web应用程序&#xff0c;支持多种浏览器&#xff0c;并提供强大的自动化测试功能。本文将…

.NET 基于Socket中转WebSocket

前言 针对IOS App Proxy Server无法直连WebSocket&#xff0c;建立 Socket中转端。 WebSocket 端&#xff1a; WebSocket 端用于实现实时通信功能。 WebSocket 端通过 WebSocket 协议与中转端通信&#xff0c;中转端可以通过 WebSocket 或其他传输协议与 WebSocket 端建立连…

class089 贪心经典题目专题1【左程云算法】

class089 贪心经典题目专题1【左程云算法】 前言版权推荐class089 贪心经典题目专题1最后 前言 2024-1-3 18:45:02 以下内容源自《【左程云算法】》 仅供学习交流使用 版权 禁止其他平台发布时删除以下此话 本文首次发布于CSDN平台 作者是CSDN日星月云 博客主页是https://j…

制作一个RISC-V的操作系统十三-抢占式多任务和兼容协作式多任务

文章目录 强占式多任务流程代码具体流程兼容协作式多任务&#xff08;软中断&#xff09;寄存器 msip流程代码结果 强占式多任务 流程 抢占式多任务由计时器中断触发&#xff0c;最后在处理程序中切换到下一个进程 代码具体流程 上下文中增加pc寄存器 寄存器保留上下文和切…

【C++】日期计算机

个人主页&#xff1a;救赎小恶魔 欢迎大家来到小恶魔频道 好久不见&#xff0c;甚是想念 今天我们要讲述的是一个日期类计算机的代码实现 引言&#xff1a; 我们日常生活中可能会有一个烦恼。 今天几月几号&#xff1f;过n天后又是几月几号&#xff1f;某年某月某天和x年…