【C++进阶】C++继承概念详解

C++继承详解

  • 一,继承的概念和定义
    • 1.1 继承的概念
    • 1.2 继承的定义
    • 1.3 继承关系和访问限定符
  • 二,基类和派生类的对象赋值转移
  • 三,继承的作用域
  • 四,派生类的默认成员函数
  • 五,继承和友元&静态成员和继承
  • 六,菱形继承和菱形虚拟继承
    • 1. 菱形继承
    • 2. 菱形虚拟继承
  • 七,总结

一,继承的概念和定义

C++面向对象有三大特性,分别是封装,继承,多态。前面我们了解了封装,在这一部分我们来看一下继承。

1.1 继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。

看下面的代码:

class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "peter"; // 姓名
	int _age = 18; // 年龄
};

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

Student和Teacher这两个类继承了Person类,在Person类的基础上有增加了自己的属性,同时Person类中的成员变量和方法也都是两个子类的一部分,可以看到Student和Teacher类可以复用Person中的函数。

1.2 继承的定义

继承的格式:

派生类也叫子类,基类也叫父类
在这里插入图片描述


1.3 继承关系和访问限定符

继承方式和访问限定符有三种

在这里插入图片描述


实际上对基类成员的访问方式是由继承方式和成员变量共同决定的:

在这里插入图片描述

  1. 上面的表格我们会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),并且public > protected> private
  2. 基类private成员在派生类中无论以什么方式继承都是不可见的。也就是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
  3. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
  4. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

二,基类和派生类的对象赋值转移

基类和派生类的对象赋值转移也就是说派生类对象可以赋值给基类的对象指针/引用,形象地说是切片或切割,是将派生类中基类的部分切下赋给基类。

在这里插入图片描述


基类对象不能赋值给子类对象,但是基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类
的指针是指向派生类对象时才是安全的。

三,继承的作用域

  1. 在继承体系中基类和派生类都有其自己的作用域
  2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
  3. 如果是成员函数的隐藏,只需要函数名相同就构成隐藏
class A
{
public:
	void fun()
	{
	cout << "func()" << endl;
	}
};
class B : public A
{
public:
	void fun(int i)
	{
		A::fun();//显式调用A中的fun
		cout << "func(int i)->" <<i<<endl;
	}
};
void Test()
{
	B b;
	b.fun(10);
};

这里可以看到A和B中都有fun函数,且同名,所以构成隐藏。但是注意,这两个fun可不是重载,因为不在同一个作用域

四,派生类的默认成员函数

派生类也有默认成员函数,我们在前面讲过类的默认成员函数有六个
在这里插入图片描述

  1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用
  2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
  3. 派生类的operator=必须要调用基类的operator=完成基类的复制
  4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序
  5. 派生类对象初始化先调用基类构造再调派生类构造

下面的代码可以看到上面所说的几个结论:

class Person
{
public :
	Person(const char* name = "peter")
		: _name(name )
	{
		cout<<"Person()" <<endl;
	}
	Person(const Person& p)
		: _name(p._name)
	{
		cout<<"Person(const Person& p)" <<endl;
	}
	Person& operator=(const Person& p )
	{
		cout<<"Person operator=(const Person& p)"<< endl;
		if (this != &p)
			_name = p._name;
			return *this ;
	}
	~Person()
	{
		cout<<"~Person()" <<endl;
	}
protected :
	string _name ; // 姓名
};
class Student : public Person
{
public :
	Student(const char* name, int num)
		: Person(name )
		, _num(num )
	{
		cout<<"Student()" <<endl;
	}
	Student(const Student& s)
		: Person(s)
		, _num(s._num)
	{
		cout<<"Student(const Student& s)" <<endl ;
	}
	Student& operator = (const Student& s )
	{
		cout<<"Student& operator= (const Student& s)"<< endl;
		if (this != &s)
		{
			Person::operator =(s);
			_num = s._num;
		}
		return *this ;
	}
	~Student()
	{
		cout<<"~Student()" <<endl;
	}
protected :
	int _num ; //学号
};

五,继承和友元&静态成员和继承

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例

六,菱形继承和菱形虚拟继承

继承有单继承和多继承,单继承就是每个派生类只有一个直接基类,而多继承是指一个子类有两个或以上的直接父类的继承关系。而菱形继承是多继承的一种特殊情况。下面我们一起来探讨一下
在这里插入图片描述


在这里插入图片描述


1. 菱形继承

所谓菱形继承,就是如下图所示,Assistant继承了Student和Teacher,而继承的两个类又继承自Person.
在这里插入图片描述

class Person
{
public :
	string _name ; // 姓名
};
class Student : public Person
{
protected :
	int _id ;
};
class Teacher : public Person
{
protected :
	int _no ; 
};
class Assistant : public Student, public Teacher
{
protected :
	string _majorCourse ; // 主修课程
};
void Test ()
{
// 这样会有二义性无法明确知道访问的是哪一个
	Assistant a ;
	a._name = "peter";
}

从上面代码可以看出菱形继承会造成数据冗余和二义性的问题。在Assistant的对象中会有两份Person成员。

在这里插入图片描述

解决办法有两种,一种是显式地指定访问哪一个name,另一种是在上面的继承关系中,在Student和Teache的继承时采用虚拟继承

2. 菱形虚拟继承

下面是菱形虚拟继承的代码示例:

class A
{
public:
	int _a;
};

class B : virtual public A
{
public:
	int _b;
};

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

虚拟继承会解决数据冗余和二义性

这是因为在菱形虚拟对象的模型中,A中的_a放到了D的最下面,同属于B,C。B,C又通过内部的指针(虚基表指针),指针指向的是一张表(虚基表),表里存放的是偏移量,根据偏移量就可以找到共有的_a.

在这里插入图片描述

七,总结

有了多继承也就有了菱形继承,菱形继承其实来说是个C++的缺陷,比较复杂。

除了继承之外,还有一种关系叫组合,继承实际上像一种is-a的关系,而组合更像是一种has-a的关系,

  1. 也就是说继承是每个派生类对象就是一个基类对象
  2. 而组合是说B组合了A,A包含在B中,每一个B对象都有一个A对象。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【案例】义乌佛堂智慧蔬菜产业园投入使用,为高效农业提供技术保障

项目背景 佛堂蔬菜产业园位于佛堂镇毛陈村&#xff0c;由义乌市市场发展集团旗下义乌市农业开发有限公司负责打造。园区于2021年开始筹建&#xff0c;总投资5000万元&#xff0c;占地面积约450亩&#xff0c;建有标准化蔬菜大棚72个。 传统大棚对农民依赖性特别强&#xff0c;需…

视频素材哪里找?几个高清短视频素材下载网站分享

哥们姐妹们&#xff0c;是不是在追求完美的短视频创作路上因为找不到那个令人心动的高清视频素材而头大呢&#xff1f;别着急&#xff0c;我这儿有几个秘密武器&#xff0c;即几个超给力的短视频素材网站&#xff0c;让你的作品从此分分钟高大上起来 1&#xff0c;蛙学府 这里…

什么是模块化机房?

在这个数据驱动的时代&#xff0c;数据中心的作用变得日益重要。而模块化机房&#xff0c;作为一种创新的数据中心解决方案&#xff0c;正在逐渐改变我们构建和管理这些关键设施的方式。但究竟什么是模块化机房呢&#xff1f;它又为何受到越来越多行业的青睐&#xff1f;在本文…

Caffeine--实现进程缓存

本地进程缓存特点 缓存在日常开发中起着至关重要的作用, 由于存储在内存中, 数据的读取速度非常快,能大量减少对数据库的访问,减少数据库的压力. 缓存分为两类: 分布式缓存, 例如Redis: 优点: 存储容量大, 可靠性更好, 可以在集群间共享缺点: 访问缓存存在网络开销场景: 缓存数…

1688平台最关键的接口接入实例|获得1688商品详情| 按关键字搜索商品| 按图搜索1688商品(拍立淘)| 获得淘口令真实url

参数说明 通用参数说明 version:API版本key:调用key,测试key:test_api_keyapi_name:API类型[item_get,item_search]cache:[yes,no]默认yes&#xff0c;将调用缓存的数据&#xff0c;速度比较快result_type:[json,xml,serialize,var_export]返回数据格式&#xff0c;默认为jsonl…

pytorch CV入门3-预训练模型与迁移学习

专栏链接&#xff1a;https://blog.csdn.net/qq_33345365/category_12578430.html 初次编辑&#xff1a;2024/3/7&#xff1b;最后编辑&#xff1a;2024/3/8 参考网站-微软教程&#xff1a;https://learn.microsoft.com/en-us/training/modules/intro-computer-vision-pytorc…

三个el-radio选项怎么知道用户选择了哪一个

问: 回答: 要获取用户选择了第二个还是第三个 <el-radio>&#xff0c;你可以在 change 事件处理函数 changeAPPVersion 中判断选中的值是什么。你需要给第二个和第三个 <el-radio> 设置不同的值&#xff0c;然后在 changeAPPVersion 方法中根据这个值来确定用户选…

Selenium自动化测试面试题全家桶

1、什么是自动化测试、自动化测试的优势是什么&#xff1f; 通过工具或脚本代替手工测试执行过程的测试都叫自动化测试。 自动化测试的优势&#xff1a; 1、减少回归测试成本 2、减少兼容性测试成本 3、提高测试反馈速度 4、提高测试覆盖率 5、让测试工程师做更有意义的…

建造家庭泳池位置选择尤为重要

建造家庭泳池位置选择尤为重要 在自家别墅庭院中建造一座游泳池是很多人的梦想&#xff0c;因为有泳池家人健身起来是非常方便的&#xff0c;但是建造泳池选择合适的位置显得尤为关键&#xff0c;因为合适的选址可以带来美观性及在泳池的日常使用维护中也起到了很重要的作用。…

idea实现ssh远程连接服务器

1. 首先&#xff0c;打开idea&#xff0c;点击左上角File->settings 2. 点击tools->SSH Configurations->填写必要的信息&#xff0c;Host就是访问服务器的ip地址&#xff0c;Username就是服务器的用户账户&#xff0c;比如root&#xff0c;Password账户对应的密码&am…

如何克服应用程序性能监控( APM )面临的挑战

应用程序性能监控&#xff08;APM&#xff09;使组织能够监控性能 其关键业务应用程序的指标&#xff0c;在出现性能问题时及时收到警报&#xff0c;以及生成用于定期性能分析的报告。应用程序性能监视工具对于任何依赖应用程序的组织来说都是必不可少的&#xff0c;它可以帮助…

【渗透测试】常见文件上传漏洞处理与防范

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属的专栏&#xff1a;网络安全渗透 景天的主页&#xff1a;景天科技苑 文章目录 1.文件上传漏洞1.1. 描述1.2. 危害1.3. 有关文件上传的知识1.4…

VB购房系统-175-(代码+开题+文献综述+翻译+说明)

转载地址: http://www.3q2008.com/soft/search.asp?keyword175 1/客户资料登记那张表上不能以客户身份证号作为主键&#xff0c;因为一般来看房的客户不会留下身份证号码&#xff0c;实施起来有难度&#xff0c;你可以设置一个自动编号字段&#xff0c;以这个字段来作为主键。…

电商数据分析|电商数据采集|Python数据采集|电商API接口数据采集系统搭建

电商API&#xff08;Application Programming Interface&#xff0c;应用程序编程接口&#xff09;是指电商平台开放的一组数据接口&#xff0c;通过这些接口可以实现对电商平台商品、订单、物流等信息进行访问、查询、修改、删除等操作。电商API接口涉及到的主要数据包括&…

[论文笔记]跨语言摘要最新综述:典型挑战及解决方案

https://arxiv.org/abs/2203.12515 跨语言摘要是指为给定的一种语言(例如中文)的文档生成另一种语言(例如英文)的摘要。 图1:四个端到端框架的概述。XLS:跨语言摘要;MT:机器翻译;MS:单语摘要。虚线箭头表示监督信号。无框彩色方块表示相应任务的输入或输出…

深入理解,java标识符?类型转换?

1、标识符 下面这张图是中国的一些姓氏 中国人起名字的规则都是以姓开头&#xff0c;名结尾。通过这个规则可以起&#xff1a;刘爱国、张三等&#xff0c;都是以汉字起的。但是不会起李ad、王123等名字&#xff0c;因为不符合规则。 所以&#xff0c;java在给变量、方法、类等…

【深度学习笔记】7_5 AdaGrad算法

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 7.5 AdaGrad算法 在之前介绍过的优化算法中&#xff0c;目标函数自变量的每一个元素在相同时间步都使用同一个学习率来自我迭代。举个…

tomcat架构

俗话说&#xff0c;站在巨人的肩膀上看世界&#xff0c;一般学习的时候也是先总览一下整体&#xff0c;然后逐个部分个个击破&#xff0c;最后形成思路&#xff0c;了解具体细节&#xff0c;Tomcat的结构很复杂&#xff0c;但是 Tomcat 非常的模块化&#xff0c;找到了 Tomcat最…

【Unity】时间戳与DateTime

介绍 在开发游戏的时候&#xff0c;和时间打交道是一个必不可少的部分。而时间戳就是用的最多的一个&#xff0c;尤其是和服务端通信。 那时间戳是什么&#xff1f; 时间戳就是从1970年1月1日0时0分0秒起到现在的总毫秒数&#xff0c;为什么时1970/1/1/00:00:00&#xff0c;因…

天梯赛的赛场安排(Python)

作者 陈越 单位 浙江大学 天梯赛使用 OMS 监考系统&#xff0c;需要将参赛队员安排到系统中的虚拟赛场里&#xff0c;并为每个赛场分配一位监考老师。每位监考老师需要联系自己赛场内队员对应的教练们&#xff0c;以便发放比赛账号。为了尽可能减少教练和监考的沟通负担&#…