c++编程(20)——类与对象(6)继承

欢迎来到博主的专栏——c++编程
博主ID:代码小豪


文章目录

    • 继承
      • 继承与权限访问
    • 基类和派生类
      • 基类和派生类的赋值兼容转换
      • 基类与派生类的类作用域
      • 派生类与基类的构造函数
        • 基类与派生类拷贝构造函数
      • 继承与静态成员
      • final关键字

面向对象编程的核心思想是封装、继承和多态,通过封装,我们可以将类的接口与实现分离,封装在前面已经了解过了,现在来讲讲继承这一特性

继承

通过继承联系在一起的类构成一种层次关系。在层次关系的根部的类称为基类,其他类从基类继承而来,称为派生类。基类负责的定义在层次关系中所有类共同拥有的成员,而每个派生类定义各自特有的成员。

所以在面对对象编程中,设计一个合理的基类以及派生类是一个非常重要的步骤。我举一个例子。

比如,如果我们想要设计出一款游戏,那么首先我们可以考虑设计一个基类entity(实体),这个entity指的是游戏内可以交互的物体,以英雄联盟举例,英雄是一个实体,小兵和野怪是一个实体,防御塔也是一个实体,包括地形也是一个实体。

OK,根据上面所说关于继承的层次关系,基类应该设计成拥有层次中所有类共同拥有的成员(即成员函数和成员变量)。当前构思中设想的派生类有:英雄、小兵、防疫塔等。那么这些派生类拥有的统一特征是什么?

英雄和小兵可以移动,但是防御塔不能,因此移动不是基类的成员,英雄和防御塔都能进行攻击,但是地形不能进行攻击,因此攻击也不是基类的成员

博主这里想到了3个共同特征,(1)坐标,实体会有在地图中的坐标(2)碰撞体积,所有实体具有碰撞体积(3)实体会在游戏当中显示出位置

因此基类entity被博主设计成这样

class entity//基类enity
{
public: void display();
protected: int _x;//x坐标int _y;//y坐标int _Hitbox;//碰撞体积
};

继承与权限访问

现在我们来设计enity的派生类,先来设计玩家操作的英雄类player吧。什么是player具有,但是其他派生类未必具有的操作呢?首先,player会有名字,第二、player会攻击,第三、player会移动,因此,我们可以把player设计成如下情况。

class entity
{public: void display();protected: int _x;//x坐标int _y;//y坐标int _Hitbox;//碰撞体积
};class player :public entity
{
public:void attack();//攻击void move();//移动
private:string _name;
};

前面滔滔不绝讲了这么多,但是博主竟然遗漏的最重要的内容,那就是派生类如何继承基类。以及基类的成员在派生类中的访问权限是什么。

首先,我们要为派生类指出,该派生类是由哪些基类继承而来的。方法如下:
我们在派生类player后面加上一个冒号(:),然后加上继承方式public,然后指出继承的基类是entity。

继承方式分为三种(1)public继承,(2)protected继承,(3)private继承,现在程序员在设计派生类时基本只采用public继承,因此博主只讲public继承的规则

基类的成员权限分为3种,分别是public,protected、private。在public继承下,这三种权限的成员在派生类中的访问权限如下:

基类中的成员权限派生类继承的基类权限
public可以在类域外访问,可以在类域内访问
protected不可在类域外访问,可以在类域内访问
private不可在类域外访问,不可在类域内访问

例子如下:

class A
{
public:void printA() {cout<<_x<<_y;};
protected:int _x;
private:int _y;
};class B :public A
{
public:void printB(){printA();//可以访问基类的public成员cout << _x;//可以访问基类的protected成员cout << _y;//error不可访问基类的private成员}
};int main()
{B b;b.printA();//可以在类外访问b._x;//error,不可在类外访问b._y;//error,不可在类外访问return 0;
}

由于B public 继承了A,因此在B类域内,可以访问A的public成员和protected成员,不可访问private成员_y。在B类域外,可以访问A的public成员printA(),不可访问A的protected和private成员。

此外,·派生类可以继承多个基类。

class a{};
class b{};
class c :public a, public b {};//c同时继承了a和b

基类和派生类

派生类拥有从基类继承而来的成员,也就是说,基类拥有的成员是所有派生类的共同成员,还记得前面举得关于游戏设计的例子吗?它们的层次关系为
在这里插入图片描述
实际上博主觉得这种图更能体现继承的关系
在这里插入图片描述
乍一看这个图和数学中的关于集合文恩图有点类似,但实际文恩图越小的圈表示包含的内容越少,而继承则是越小层次的派生类,包含的功能会越多。

基类和派生类的赋值兼容转换

以player和entity为例,entity的内存形式如下:
在这里插入图片描述
而player的内存形式如下
在这里插入图片描述
一个派生类对象包含两部分,一部分是从基类继承下来的数据,另一部分是派生类自己定义的数据。

基类对象可以用派生类对象进行赋值操作,其效果是得到派生类中基类部分的值。(反之不行)

以enity和player为例,博主为player增加了移动的功能

class entity
{
public: void display() { cout << _x << _y; };
protected: int _x=0;//x坐标int _y=0;//y坐标int _Hitbox = 0;//碰撞体积
};class player :public entity
{
public://void attack();//攻击void move()//移动{_x += 1;_y += 1;}
private:string _name;
};

player调用move1次,x轴和y轴上的坐标就相应的增加1(大概就是往斜上方移动)。

entity entity1;//enity1坐标{0,0}
player player1;//player1坐标{0,0}
player1.move();//enity{0,0},player{1,1}
entity1 = player1;//entity{1,1},player1{1,1}
entity1.display();//1 1

player1和entity并非相同类型,也不符合类之间隐式转换赋值的规则(即赋值对象与赋值重载函数的参数类型一致。),那么为什么派生类可以赋值给基类呢?

c++允许派生类的对象赋值给基类的对象,其基类的值会变成派生类中有关基类部分的值。
在这里插入图片描述

基类的指针或引用可以指向派生类。这个过程可以成为切片或切割。意思就是将派生类中有关基类的值进行操作。

为了方便展示接下来的示例,博主将entity中的_x,_y,_Hitbox的权限修改为public,其余代码不变。

class entity{public: void display() { cout << _x << _y; }; int _x=0;//x坐标int _y=0;//y坐标int _Hitbox = 0;//碰撞体积};class player :public entity{public://void attack();//攻击void move()//移动{_x += 1;_y += 1;}private:string _name;};
player player1;//player1坐标{0,0}entity* eptr = &player1;//基类的指针
entity& eref = player1;//基类的引用eptr->_x = 5;//将player1中的_x赋值为5
eref._y = 5;//将player1中的_y赋值为5
eptr->_name="LY";//error,不能操作派生类中除了基类以外的数据
eref->_name="LY";//error,同上
player1.display();//(5,5)

派生类的对象不能用基类赋值,派生类的指针和引用也不能指向基类。

基类与派生类的类作用域

基类与派生类拥有各自的类域,当派生类继承基类时,会在派生类中嵌套在基类的作用域(类似于局部域嵌套在全局域,即优先使用局部域中的标识符)。

如果一个标识符在派生类的作用域中无法被查找到,编译器就会在基类的类域中寻找该标识符的定义。

比如我们定义基类X

class X
{
public:void printX() { cout << _x << _y << _z; }int _x=0;
protected:int _y=0;
private:int _z=0;
};

标识符_x,_y,_z都存在基类X的类域当中,因此定义在基类类域的成员函数print可以访问这些成员。

现在我们用Y来继承基类X。即Y成为X的派生类

class Y:public X
{
public:void printY() { cout << _x << _y << _z; }//error,_z没有访问权限
};

在派生类Y的类域内使用了_x,_y,_z三个标识符,编译器会首先在派生类Y的类域中寻找这三个变量的定义,如果Y的类域中不存在这些标识符的定义,就会到基类的类域中寻找这些变量,而_x,_y,_z定义在基类X的类域当中,因此会访问基类类域中的变量。

由于_x,_y的访问权限分别是publc和protected,因此,我们可以在派生类Y的类域中访问到_x,_y,而_z的权限是private,不能再派生类Y中访问。

因此我们调用Y的printY时,会打印基类部分_x,_y的值(_z不能访问)。

void printY() { cout << _x <<_y; }//打印结果是0 0

由于派生类域和基类类域属于不同的作用域,C++中允许在不同的作用域中声明相同的标识符,比如局部域中的标识符可以和全局与的标识符重名,这个规则在类域当中也适用。

因此我们可以在派生类中定义和基类一致的标识符。

class X
{
public:void printX() { cout << _x << _y << _z; }int _x=0;
protected:int _y=0;
private:int _z=0;
};class Y:public X
{
public:void printY() { cout << _x << _y <<_z; }
private:int _x = 1;int _y = 2;int _z = 3;
};

由于编译器会优先在派生类中查找定义,因此当访问派生类和基类同名的标识符时,会优先访问到派生类的成员,而基类中的同名成员则无法被访问,我们称这一特性为隐藏

X x;
Y y;
y.printY();//打印结果为1,2,3
x.printX();//打印结果为0,0,0

如果想要在派生类的类域中访问到被隐藏的基类的成员,就要用到类限定符(::)。

class Y:public X
{
public:void printY() { cout << X::_x << X::_y <<_z; }//没有访问X::_Z的权限
private:int _x = 1;int _y = 2;int _z = 3;
};

这样访问的就是基类X的_x,_y成员。

Y y;
y.printY();//打印结果为0,0,3

如果使用相同的成员函数,也会触发隐藏机制,比如:

class X
{
public:void print() { cout << _x << _y << _z; }//同名函数printint _x=0;
protected:int _y=0;
private:int _z=0;
};class Y:public X
{
public:void print() { cout << _x << _y <<_z; }//同名函数print
private:int _x = 1;int _y = 2;int _z = 3;
};

此时如果y对象调用print,输出结果是1,2,3。如果想要调用X类域中的print,就要用到域限定符。

Y y;
y.print();//打印结果为1,2,3
y.X::print();//打印结果为0,0,0

派生类与基类的构造函数

派生类拥有从基类当中继承而来的成员,但是我们并非可以在派生类中初始化所有的基类成员,因为基类当中的private成员在派生类的类域当中无法访问,这也就导致无法在派生类的构造函数中通过直接赋值的方式完成从初始化。

于是c++允许在派生类的构造函数的初始化列表中调用基类的构造函数。以此来完成构造函数的初始化。

class entity
{
public:entity(int x, int y, int Hitbox):_x(x),_y(y),_Hitbox(Hitbox){}
protected: int _x=0;//x坐标int _y=0;//y坐标int _Hitbox = 0;//碰撞体积
};class player :public entity
{
public:player(int x,int y,int Hitbox,string name):entity(x,y,Hitbox),_name(name){}
private:string _name;
};

当构造player时,将x,y,Hitbox实参传递给entity的构造函数,由entity的构造函数负责初始化player的基类部分,即(_x,_y,_Hitbox)成员。

c++规定派生类进行初始化时,会优先初始化基类,再初始化派生类定义的成员,即无论如何,当派生类调用构造函数时,一定会优先构造派生类的基类部分,表现为会优先调用基类相应的构造函数。

基类与派生类拷贝构造函数

以基类entity及其派生类player为例,我们先来看看这两个类的拷贝构造函数是怎样的。

entity(const entity& rhs)
{_x = rhs._x;_y = rhs._y;_Hitbox = rhs._Hitbox;
}
player(const player& player):entity(player),_name(player._name){}

派生类的拷贝构造函数依然可以调用基类的拷贝构造函数完成拷贝,但是有一个奇怪的点,那就是基类的拷贝构造的函数形参是entity类型,但是在派生类player中却传递了一个player类型的参数,为什么这种传参方式是可行呢?

其实这就是前面提到的基类与派生类之间的赋值兼容转换,即基类的引用、指针可以指向派生类。

派生类的赋值重载函数也是同理。

entity& operator=(const entity& entity)
{_x = entity._x;_y = entity._y;_Hitbox = entity._Hitbox;
}	
player& operator=(const player& player)
{entity::operator=(player);//调用entity的赋值重载函数_name = player._name;
}

继承与静态成员

如果基类存在一个静态成员,那么对于整个继承体系来说,都存在唯一的一个静态成员,即无论从基类中派生出多少派生类,该静态成员都是唯一的实例。

比如在基类entity中声明一个静态成员变量_num,

class entity
{
protected: int _x=0;//x坐标int _y=0;//y坐标int _Hitbox = 0;//碰撞体积static int _num;
};

在其派生类player中,与entity共用同一个_num。

如果在基类中存在static成员函数,效果也是同理。

class entity
{
public:static void statement() { cout << _num; }
protected: int _x=0;//x坐标int _y=0;//y坐标int _Hitbox = 0;//碰撞体积static int _num;
};

我们可以通过基类访问这个函数,可以通过派生类访问这个函数。

entity::statement();//通过基类访问
entity(1, 1, 1).statement();
player::statement();//通过派生类访问
player(1, 1, 1, "ly").statement();

final关键字

如果我们定义某个类无法被继承,就在类名后面加上关键字final。

class entity final
{
};

此时player将无法作为entity的派生类,发生报错

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

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

相关文章

计算机网络408考研 2021

2021 计算机网络408考研2021年真题解析_哔哩哔哩_bilibili 1 1 11 1 1 11

解决No module named ‘tensorflow‘

import tensorflow as tf ModuleNotFoundError: No module named tensorflow 安装合适的tensorflow版本 先查看自己的python版本 或者输入指令&#xff1b;python --version 安装兼容的tensorflow版本&#xff0c;安装指定版本的tensorflow pip install tensorflow-gpu2.3.0…

Qt | QSQLite内存数据库增删改查

点击上方"蓝字"关注我们 01、演示 参数随便设置 查询 修改 右键菜单是重点 手动提交,点击Submit All

【Docker】基础篇

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了云计算学习的&#xff0c;整理期间苛求每个知识点&#xff0c;平衡理解简易度与深入程度。 &#x1f970;来源&#xff1a;材料主要源于–Docker视频教程从入门到进阶&#xff0c;docker视频教程详解–…

【云原生】高可用集群KEEPALIVED(理论篇)

一、高可用集群 1.1 集群类型 LB:Load Balance 负载均衡 LVS/HAProxy/nginx(http/upstream, stream/upstream)HA:High Availability 高可用集群数据库、RedisSPoF: Single Point of Failure&#xff0c;解决单点故障HPC: High Performance computing 高性能集群 1.2 系统可用…

车身域测试学习、CANoe工具实操学习、UDS诊断测试、功能安全测试、DTC故障注入测试、DBC数据库、CDD数据库、CAN一致性测试、ECU刷写测试

每日直播时间&#xff1a;&#xff08;直播方式&#xff1a;腾讯会议&#xff09;周一到周五&#xff1a;20&#xff1a;00-23&#xff1a;00周六与周日&#xff1a;9&#xff1a;00-17&#xff1a;00 进腾讯会议学习的&#xff0c;可以关注我并后台留言 直播内容&#xff1a;&…

OKnews加密货币资讯:现货比特币ETF市场动荡,价格大幅下跌

OKnews加密货币资讯网8月13日讯&#xff1a;现货比特币ETF 市场近期的动荡给加密货币行业带来了冲击&#xff0c;导致比特币 (BTC) 未能维持其在关键的60,000美元大关之上的地位。该数字货币在过去24 小时内下跌了 3.65%&#xff0c;跌至58,515 美元。市场波动加剧以及对美国经…

人脸操作:从检测到识别的全景指南

人脸操作&#xff1a;从检测到识别的全景指南 在现代计算机视觉技术中&#xff0c;人脸操作是一个非常重要的领域。人脸操作不仅包括检测图像中的人脸&#xff0c;还涉及到人脸识别、表情分析、面部特征提取等任务。这些技术在各种应用中发挥着关键作用&#xff0c;从社交媒体…

中国科技统计年鉴,数据覆盖1991-2022年多年份

基本信息. 数据名称: 中国科技统计年鉴 数据格式: excel 数据时间: 1991-2022年 数据几何类型: xlsx 数据坐标系: WGS84 数据来源&#xff1a;国家统计局 数据预览&#xff1a; 数据可视化.

(kali关怀版)kali调整字体图标显示大小

字体大小调整(图标字体) 字体在左上角搜apprence 图标大小调整 图标在桌面右键点apprence 任务栏大小调整 任务栏在上面右键&#xff0c;选择panel preference 终端字体大小调整 终端字体用ctrl和加号减号进行缩放 属于是kali关怀版了:) 还可指定锁屏和休眠时间&#…

waveInAddBuffer死锁的大雷解决

项目场景&#xff1a; 从来没有一个bug让我这么抓狂&#xff0c;足足查了3天3夜&#xff0c;官方文档翻了一遍说的基本无用。具体项目就是使用waveIn系列函数获取windows系统麦克风数据&#xff0c;虽然windows上有好几种方法获取麦克风数据&#xff0c;我最终还是选择了它。 …

Springboot整合hutool验证码

在 Spring Boot 中&#xff0c;你可以将 Hutool 生成验证码的功能集成到 RESTful API 接口中。 依赖 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.14</version> <!-- 使用最新版…

pytorch,用lenet5识别cifar10数据集(训练+测试+单张图片识别)

目录 LeNet-5 LeNet-5 结构 CIFAR-10 pytorch实现 lenet模型 训练模型 1.导入数据 2.训练模型 3.测试模型 测试单张图片 代码 运行结果 LeNet-5 LeNet-5 是由 Yann LeCun 等人在 1998 年提出的一种经典卷积神经网络&#xff08;CNN&#xff09;模型&#xff0c;主要…

Solana真假繁荣调查:机器人横行占7成交易,Meme数据下滑严重

随着Solana链上数据全面赶超以太坊&#xff0c;关于Solana将超越以太坊的讨论逐渐升温。然而&#xff0c;与此同时&#xff0c;关于Solana真实繁荣度的争议也引发了广泛关注。本文将深入探讨Solana生态中机器人泛滥、交易量数据虚高、MEV问题、财务亏损问题&#xff0c;以及SOL…

从零搭建xxl-job(四):xxljob进行一些性能优化

之前的代码这部分并没有补充完毕&#xff0c;假如调度中心如果判断有定时任务要执行了&#xff0c;该怎么远程通知给执行定时任务的程序呢&#xff1f;当定时任务要把自己的信息发送给调度中心时&#xff0c;是通过一个RegistryParam对象发送的。该对象内部封装了定时任务相关的…

【C#】explicit、implicit与operator

字面解释 explicit&#xff1a;清楚明白的;易于理解的;(说话)清晰的&#xff0c;明确的;直言的;坦率的;直截了当的;不隐晦的;不含糊的。 implicit&#xff1a;含蓄的;不直接言明的;成为一部分的;内含的;完全的;无疑问的。 operator&#xff1a;操作人员;技工;电话员;接线员;…

HarmonyOS应用开发者高级认证(一)

1、依次点击A、B、C、D四个按钮&#xff0c;其中不会触发UI刷新的是&#xff1a; 答案&#xff1a; Button("C").onClick(() > {this.nameList[0].name "Jim"})分析&#xff1a;直接更新非一级数据不会触发UI刷新 2、如果要实现Row组件内的子元素均匀…

基于JSP的个性化影片推荐系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;JSP 数据库&#xff1a;MySQL 技术&#xff1a;JSP技术 工具&#xff1a;MyEclipse、Tomcat、MySQL 系统展示 首页 管理员功能模块 用户功能模块 …

Rancher的RKE和RKE2部署K8s集群kube-proxy开启strictARP

kube-proxy配置strictARPtrue 1、非RKE部署的K8s集群&#xff1a;配置首先&#xff0c;需要为kube-proxy启动strictARP&#xff0c;以便Kubernetes集群中的所有网卡停止响应其他网卡的ARP请求&#xff0c;而由OpenELB来处理ARP请求。 $ kubectl edit configmap kube-proxy -n…

C# 在Word中插入或删除分节符

在Word中&#xff0c;分节符是一种强大的工具&#xff0c;用于将文档分成不同的部分&#xff0c;每个部分可以有独立的页面设置&#xff0c;如页边距、纸张方向、页眉和页脚等。正确使用分节符可以极大地提升文档的组织性和专业性&#xff0c;特别是在长文档中&#xff0c;需要…