C++ 访问控制——公有继承、私有继承、保护继承

news2024/9/23 23:26:14

派生类继承了基类的全部数据成员和除了构造函数和析构函数之外的全部函数成员,但是这些成员的访问属性在派生的过程中是可以调整的。从基类继承的成员,其访问属性由继承方式控制。

基类的成员有public(公有)、protected(保护)和private(私有)三种访问属性。基类的自身成员可以对基类中任何一个其他成员进行访问,但是通过基类的对象,只能访问该类的公有成员。

类的继承方式有public(公有继承)、protected(保护继承)和private(私有继承)三种。不同的继承方式,导致原来具有不同访问属性的基类成员在派生类中的访问属性也有所不同。这里说的访问来自两个方面:一是派生类中新增成员访问从基类继承的成员;二是在派生类外部,通过派生类对象访问从基类继承的成员。

1.公有继承

当类的继承方式为公有继承时,基类的公有成员和保护成员的访问属性在派生类中不变,而基类的私有成员不可以直接访问。 也就是说基类的公有成员和保护成员被继承到派生类中访问属性不变,仍作为派生类的公有成员和保护成员,派生类的其他成员可以直接访问它们。在类族之外只能通过派生类的对象访问从基类继承的公有成员,而无论是派生类的成员还是派生类的对象都无法直接访问基类的私有成员。

【例1】Point类公有继承
将从Point类派生出新的Rectangle(矩形)类。矩形是由一个点加上长、宽构成。矩形的点具备了Point类的全部特征,同时,矩形自身也有一些特点,就需要在继承Point类的同时添加新的成员。

程序的头文件部分如下:
Point.h文件:

#pragma once
#ifndef _POINT_H
#define _POINT_H
class Point//基类Point的定义
{
public://公有函数成员
	void initpoint(float x = 0, float y = 0)
	{
		this->x = x;
		this->y = y;
	}
	void move(float ox, float oy)
	{
		x += ox;
		y += oy;
	}

	float getX()const
	{
		return x;
	}
	float getY()const
	{
		return y;
	}
private://私有数据成员
	float x, y;
};

#endif

Rectangle.h文件:

#pragma once
#ifndef _RECTANGLE_H
#define _RECTANGLE_H
#include"Point.h"
class Rectangle :public Point//派生类的定义部分
{
public:
	void initRectangle(float x, float y, float w, float h)//新增公有函数成员
	{
		initpoint(x, y);//调用基类公有成员函数
		this->w = w;
		this->h = h;
	}
	float getH()const
	{
		return h;
	}
	float getW()const
	{
		return w;
	}
private://新增私有数据成员
	float w, h;
};

#endif

这里首先定义了基类Point。派生类Rectangle继承了Point类的全部成员(隐含的默认构造函数和析构函数除外),因此在派生类中,实际拥有的成员就是从基类继承过来的成员和派生类新定义成员的总和。继承方式为公有继承,这时,基类中的公有成员在派生类中的访问属性保持原样,派生类的成员函数及对象可以访问到基类的公有成员(例如在派生类Rectangle的函数成员initRectangle中直接调用基类的函数initpoint),但是无法访问基类的私有成员(例如x,y)。基类原有的外部接口(例如getX()和getY()函数)变成了派生类外部接口的一部分。当然,派生类自己新增的成员之间都是可以互相访问的。

Rectangle类继承了Point类的成员,也就实现了代重用,同时通过新增成员,加入了自身的独有特征,达到了程序的扩充。

程序的主函数部分如下:

#include<iostream>
using namespace std;
#include"Rectangle.h"
int main()
{
	Rectangle r;//定义Rectangle类的对象
	r.initRectangle(2, 3, 20, 10);//设置矩形的数据
	r.move(3, 2);//移动矩形的位置
	cout << "这个矩形的数据(x,y,w,h):" << endl;
	cout << r.getX() << "," << r.getY() << "," << r.getW() << "," << r.getH() << endl;//输出矩形的特征参数
	return 0;
}

运行结果:
在这里插入图片描述
分析:
主函数中首先声明了一个派生类对象r,对象生成时调用了系统所产生的默认构造函数,这个函数的功能是什么都不做。任何通过派生类的对象r访问了派生类的公有函数initRectangle,也访问了派生类从基类继承来的公有函数和move()、getX()和getY()。这样我们看到,从一个基类以公有继承的方式产生了派生类之后,在派生类的成员函数中,以及通过派生类的对象如何访问从基类继承的公有成员。

2.私有继承

当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可以直接访问。 也就是说,基类的公有成员和保护成员被继承后作为派生类的私有成员,派生类的其他成员可以直接访问它们,但是在类族外部通过派生类的对象无法直接访问它们。无论是派生类成员还是通过派生类的对象,都无法直接访问从基类继承的私有成员。

经过私有继承后,所有基类的成员都成为了派生类的私有成员或不可直接访问的成员,如果进一步派生的话,基类的全部成员就无法在新的派生类中被直接访问。因此,私有继承之后,基类的成员再也无法在以后的派生类中直接发挥作用,实际上相当于中止了基类功能的继续派生,出于这种原因,一般情况私有继承使用较少。

【例2】Point类私有继承

程序类的定义部分如下:

Point.h文件:

#pragma once
#ifndef _POINT_H
#define _POINT_H
class Point//基类Point的定义
{
public://公有函数成员
	void initpoint(float x = 0, float y = 0)
	{
		this->x = x;
		this->y = y;
	}
	void move(float ox, float oy)
	{
		x += ox;
		y += oy;
	}

	float getX()const
	{
		return x;
	}
	float getY()const
	{
		return y;
	}
private://私有数据成员
	float x, y;
};

#endif

Rectangle.h文件:

#ifndef _RECTANGLE_H
#define _RECTANGLE_H
#include"Point.h"
class Rectangle :private Point
{
public:
	void initRectangle(float x, float y, float w, float h)//新增公有函数成员
	{
		initpoint(x, y);//调用基类公有成员函数
		this->w = w;
		this->h = h;
	}
	void move(float ox, float oy)
	{
		Point::move(ox, oy);
	}
	float getX()const
	{
		return Point::getX();
	}
	float getY()const
	{
		return Point::getY();
	}
	float getH()const
	{
		return h;
	}
	float getW()const
	{
		return w;
	}
private://新增私有数据成员
	float w, h;
};
#endif

派生类Rectangle继承了Point类的成员,因此在派生类中,实际拥有的成员就是从基类继承的成员与派生类新成员的总和。继承方式是私有继承,这时,基类中的公有和保护成员在派生类中都以私有成员的身份出现。派生类成员的成员函数及对象无法访问基类的私有成员(例如基类的x,y)。派生类的成员仍然可以访问到从基类继承过来的公有和保护成员(例如在派生类函数成员initRectangle中直接调用基类的函数initpoint),但是在类的外部通过派生类的对象无法访问到基类的任何成员,基类原有的外部接口(例如getX()和getY()函数)被派生类封装和隐藏起来。当然,派生类新增的成员之间仍然可以自由地相互访问。

在私有继承的情况下,为了保证基类的一部分外部接口能够在派生类中也存在,就必须在派生类中重新声明同名的成员。这里在派生类Rectangle中,重新声明了move,getX,getY等函数,利用派生类对基类成员的访问能力,把基类的原有成员函数的功能照搬过来。这种在派生类中重新声明的成员函数具有比基类同名成员函数更小的作用域,因此在调用时,根据同名隐藏的原则,自然会使用派生类的函数。

程序的主函数部分:

#include"Rectangle.h"
int main()
{
	Rectangle r;//定义Rectangle类的对象
	r.initRectangle(2, 3, 20, 10);//设置矩形的数据
	r.move(3, 2);//移动矩形的位置
	cout << "这个矩形的数据(x,y,w,h):" << endl;
	cout << r.getX() << "," << r.getY() << "," << r.getW() << "," << r.getH() << endl;//输出矩形的特征参数
	
	return 0;
}

运行结果:

在这里插入图片描述
分析:

主函数部分和公有继承的主函数部分的代码完全相同,但是执行过程不同。本例中的Rectangle类对象r调用的函数都是派生类自身的公有成员,因为私有继承,它不可能访问到任何一个基类的成员。和例1的Point的公有继承相比较,本例对程序修改的只是派生类的内容,基类和主函数部分没有改变。Rectangle类的外部接口不变,内部成员的实现做了改造,没有影响到程序的其他部分,这正是面向对象程序设计重用和扩充的一个实际体现。

3.保护继承

保护继承中,基类的公有成员和保护成员都以保护成员的身份出现在派生类中,而基类的私有成员不可以直接访问。 这样,派生类的其他成员就可以直接访问从基类继承来的公有和保护成员,但在类的外部通过派生类的对象无法直接访问它们。无论时派生类成员还是派生类对象都无法直接访问基类的私有成员。

比较私有继承和保护继承可以看出,实际上在直接派生类中,所有成员的访问属性都是完全相同的。但是如果派生类作为新的基类继续派生时,二者就有区别了。假设Rectangle类以私有继承的方式继承了Point类之后,Rectangle类又派生出Square类,那么Square类的成员和对象都不能访问间接从Point来中继承来的成员。如果Rectangle类是以保护继承的方式继承了Point类,那么Point类中的公有和保护成员在Retangle类中都是保护成员,Retangle类再派生出Square类之后,Point类中的公有和保护成员被Square类间接继承后,有可能是保护或者私有(根据从Rectangle到Square的派生方式不同而不同)。因而,Square类的成员有可能可以访问间接从Point类中继承来的成员。

从继承的访问规则,可以看到类中保护成员的特征。如果Point类中含有保护成员,对于建立Point类对象的模块来讲,保护成员和该类的私有成员一样是不可以访问的。如果Point类派生除了Rectangle类,则对于Rectangle类来讲,保护成员和私有成员具有相同的访问特性。换句话来说,就是Point类中的保护成员有可能被它的派生类访问,但是决不可能被其他外部使用者(程序中的普通函数、与Point类平行的其他类)访问。

这样,如果合理地利用保护成员,就可以在类的复杂层关系中在共享与成员隐藏之间找到一个平衡点,既能实现成员隐蔽,又能方便继承,实现代码的高效重用和扩充。

假定某一个类A有保护数据成员x,我们来讨论x的访问特征。

类A定义为:

class A
{
protected://保护数据成员
	int x;	
};

如果主函数为:

int main()
{
	A a;
	a.x=5;//错误
}

程序在编译阶段就会出错,错误原因是:在建立A类对象的模块——主函数中试图访问A类的保护成员,这是不允许的,因为该成员的访问规则和A类的私有成员是相同的。这就说明在建立A类对象a的模块中无法访问A类的保护成员,在这种情况下,保护成员和私有成员一样得到了很好的隐蔽。

如果类A以公有继承的方式派生产生了B类,则在B类中,A类的保护成员和该类的公有成员一样可以访问。例如:

class A
{
protected:
	int x;
};
class B:public A
{
public:
	void fun();
};

void B::fun()
{
	x=5;
}

在派生类B的成员函数fun内部,是完全可以访问基类的保护成员的。

【注意】如果B是A的派生类,B的成员函数只能通过B的对象访问A中定义的protected成员,而不能通过A的对象访问A的protected成员。

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

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

相关文章

ArduPilot开源代码之Companion Computers简单分析

ArduPilot开源代码之Companion Computers简单分析 1. 源由2. 伴机系统2.1 APSync2.2 DroneKit2.3 FlytOS2.4 Maverick2.5 ROS2.6 Rpanion-server 3. 总结4. 参考资料 1. 源由 从稳定性&#xff0c;社区群体&#xff0c;以及开源方式的角度看&#xff0c;Ardupilot是不错的选择…

骑砍二 ATC MOD 使用教程与应用案例解析

骑砍二 ATC MOD 使用教程与应用案例解析 作者&#xff1a;blibli-财不外漏 / NEXUSMODS-PuepleKarmen 案例MOD依赖&#xff1a;ATC - Adonnay’s Troop Changer & AEW - Adonnay’s Exotic Weaponry & New Armor 文本编辑工具&#xff1a;VS Code&#xff08;推荐使用&…

【小沐学NLP】在线AI绘画网站(百度:文心一格)

文章目录 1、简介2、文心一格2.1 功能简介2.2 操作步骤2.3 使用费用2.4 若干示例2.4.1 女孩2.4.2 昙花2.4.3 山水画2.4.4 夜晚2.4.5 古诗2.4.6 二次元2.4.7 帅哥 结语 1、简介 当下&#xff0c;越来越多AI领域前沿技术争相落地&#xff0c;逐步释放出极大的产业价值&#xff0…

Amazon CloudFront 部署小指南(四)- CloudFront Function 基础与诊断

内容简介 本文适用于希望使用 Amazon CloudFront Functions 提升 Amazon CloudFront 边缘计算能力的用户&#xff0c;旨在帮助您更好的进行 CloudFront Functions 的开发、调试、测试、部署等工作。 首先我们会对 CloudFront Function 做个简单的介绍&#xff0c;然后分为七个步…

全志F1C200S嵌入式驱动开发(应用程序开发)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 我们在开发soc驱动的时候,很多情况下也要验证下当前的驱动功能是否正确。当然除了验证驱动功能之外,我们还要编写业务代码和流程代码。这中间就和各行各业有关了,有的是算法,有…

Redis BigKey案例

面试题&#xff1a; 阿里广告平台&#xff0c;海量数据里查询某一固定前缀的key小红书&#xff0c;你如何生产上限制keys*/flushdb/flushall等危险命令以防止误删误用&#xff1f;美团&#xff0c;MEMORY USAGE命令你用过吗&#xff1f;BigKey问题&#xff0c;多大算big&#…

GODOT游戏引擎简介,包含与unity性能对比测试,以及选型建议

GODOT&#xff0c;是一个免费开源的3D引擎。本文以unity作对比&#xff0c;简述两者区别和选型建议。由于是很久以前写的ppt&#xff0c;技术原因视频和部分章节丢失了。建议当做业务参考。 GODOT目前为止遇到3个比较重大的基于&#xff0c;第一个是oprea的合作奖&#xff0c;…

【redis】redis的认识和安装

目录 1.redis是什么2.Redis的特点3.安装redis4.设置远程连接4.1 开启隧道4.2 可视化客户端连接4.3 开启防火墙 5.redis常见数据类型5.1 redis的一些全局命令5.2 数据结构 6. redis的典型应用---缓存&#xff08;cache&#xff09;6.1 使用redis做缓存6.2 缓存穿透&#xff0c;缓…

【绪论0】

#pic_center R 1 R_1 R1​ R 2 R^2 R2 目录 知识框架No.0 引言No.1 操作系统的概念功能和定义一、操作系统的概念和定义1、电脑的演变 二、操作系统的功能和目标 No.2 操作系统的特征一、并发二、共享三、虚拟四、异步 No.3 操作系统的发展与分类一、手工操作阶段二、批处理阶段…

Windows11 家庭中文版关于本地组策略编辑器gpedit.msc找不到即打不开的解决办法(征诚小张售后实测有效)

Windows11 家庭中文版关于本地组策略编辑器gpedit.msc找不到即打不开的解决办法 根本原因&#xff1a;是因为Windows11家庭中文版的 版本系统没内置安装本地策略组编辑器 好了废话不多说 直接说解决办法 第一步 首先电脑上新建一个空文本文件 输入以下内容&#xff1a; echo o…

Android Studio安装AI编程助手Github Copilot

csdn原创谢绝转载 简介 文档链接 https://docs.github.com/en/copilot/getting-started-with-github-copilot 它是个很牛B的编程辅助工具&#xff0c;装它&#xff0c;快装它&#xff0e; 支持以下IDE: IntelliJ IDEA (Ultimate, Community, Educational)Android StudioAppC…

Qt应用开发(基础篇)——时间类 QDateTime、QDate、QTime

一、前言 时间类QDateTime、QDate、QTime、QTimeZone保存了Qt的时间、日期、时区信息&#xff0c;常用的时间类部件都会用到这些数据结构&#xff0c;常用概念有年、月、日、时、分、秒、毫秒和时区&#xff0c;时间和时区就关系到时间戳和UTC的概念。 UTC时间&#xff0c;又称…

FPGA初步学习之串口发送模块【单字节和字符串的发送】

串口相关简介 UART 在发送或接收过程中的一帧数据由4部分组成&#xff0c;起始位、数据位、奇偶校验位和停止位&#xff0c;如图所示。其中&#xff0c;起始位标志着一帧数据的开始&#xff0c;停止位标志着一帧数据的结束&#xff0c;数据位是一帧数据中的有效数据。 通常用…

【贪心算法】leetcode刷题

贪心算法无固定套路。 核心思想&#xff1a;先找局部最优&#xff0c;再扩展到全局最优。 455.分发饼干 两种思路&#xff1a; 1、从大到小。局部最优就是大饼干喂给胃口大的&#xff0c;充分利用饼干尺寸喂饱一个&#xff0c;全局最优就是喂饱尽可能多的小孩。先遍历的胃口&a…

Win11大小写切换图标关闭方法

大家使用Win11操作系统的时候经常会切换大小写键盘&#xff0c;有些游戏本在游戏过程中需要切换大小写&#xff0c;这个时候电脑的屏幕就会出现大小写切换的图标而影响游戏体验&#xff1b; 那么想要关闭Win11电脑上大小写切换图标&#xff0c;又不知道具体怎么操作&#xff0c…

VS Code search tab

Vs Code search 栏的应用 我发现&#xff0c;在vs code种&#xff0c;上面的搜索框的功能非常多。在最初使用vscode时候&#xff0c;以为这只是一个普通的搜索框。后来&#xff0c;发现它可以用于全局搜索文件&#xff0c;比如使用ctrlshiftp。 后来&#xff0c;我发现&#xf…

lifecycleScope Unresolved reference

描述 导入了lifecycle.lifecycleScope&#xff0c;但是在activity中使用lifecycleScope报错出现Unresolved reference找不到引用。 导包 import androidx.lifecycle.lifecycleScope使用 lifecycleScope.launch(Dispatchers.IO) {...}错误 方案 代码中的activity继承Activ…

SSM(Vue3+ElementPlus+Axios+SSM前后端分离)--功能实现【四】

文章目录 SSM--功能实现实现功能06-修改家居信息需求分析/图解思路分析代码实现注意事项和细节 实现功能07-删除家居信息需求分析/图解思路分析代码实现 实现功能08-分页显示列表需求分析/图解思路分析代码实现完成测试分页显示效果 SSM–功能实现 实现功能06-修改家居信息 需…

39.利用matlab寻找素数(matlab程序)

1.简述 MATLAB嵌套循环允许使用一个循环在另一循环内&#xff0c;下面用一个嵌套循环来把所有从1到100的素数显示出来。 2.代码 %% 学习目标&#xff1a;寻找素数 clear sum5; %求0&#xff5e;100素数之和 ss0; %用来标定是否是素数&#xff0c;0表示不是 p…

MongoDB SQL

Microsoft Windows [版本 6.1.7601] 版权所有 (c) 2009 Microsoft Corporation。保留所有权利。C:\Users\Administrator>cd C:\MongoDB\Server\3.4\binC:\MongoDB\Server\3.4\bin> C:\MongoDB\Server\3.4\bin> C:\MongoDB\Server\3.4\bin>net start MongoDB 请求的…