【C++进阶】深入了解继承机制

news2025/1/24 4:09:37

目录

前言

1. 继承的概念

2. 继承的定义

 3. 继承中基类与派生类的赋值转换

 4. 继承中的作用域

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

 6. 继承与友元、静态成员

 7. 多继承与菱形继承

7.1 如何解决


前言

         继承是面向对象编程中的一个重要概念,也是面向对象编程语言中普遍存在的特性,本文我将会深入的向大家介绍C++的继承以及继承中菱形继承的问题;

在这里插入图片描述

1. 继承的概念

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

         之前我们在实现函数中,如果存在多个函数中重复相同的操作,我们就会把这个重复操作单独写成一个函数,以便于不同接口的调用;在类当中也存在这样的问题,两个或者多个类同时拥有相同属性,比如:

class Student 
{
public:
	//...
protected:
	string _name;// 姓名
	string _gender;// 性别
	string _id;	 // 身份号
	int age;	 // 年龄
	int _stuid;  // 学号
};

class Teacher
{
public:
	//...
protected:
	string _name;
	string _gender;
	string _id;
	int age;
	int _jobid; // 工号
};

他们都拥有一些相同的属性,这时我们就可以把这些属性提取出来,写成一个类Person,用的时候只需继承Person类即可;

2. 继承的定义

 继承的定义规则:

 前边我们在学习类的时候提到,类有三种访问限定符:

public、protected、private;

 它的继承方式也是这三种,继承方式不同,派生类使用基类时的权限也不同:

 继承基类成员访问方式的变化

 小tips:

 这张表我们可以分为两部分:

        基类的private成员为一类,它在派生类中不可见(不可见不代表没有继承下来),派生类无法使用(类里边和类外边都无法使用)

         基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected> private

         在继承部分才能体现出private和protected的区别

 如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected;

         继承一个类时没有写继承方式,它们的默认继承方式会因为声明有些不同的,使用的是class,子类继承时默认是private,使用struct时默认的继承方式是public

class Person
{
public :
    void Print ()
    {
        cout<<_name <<endl;
    }
protected :

    string _name ; // 姓名
    string _gender;// 性别
    string _id;	 // 身份号
private :
    int _age ; // 年龄
};
//class Student : Person
//class Student : protected Person
//class Student : private Person
class Student : public Person
{
protected :
    int _stunum ; // 学号
};

 3. 继承中基类与派生类的赋值转换

         在继承体系中,派生类对象 可以赋值给 基类的对象,包括派生类的指针,引用都可以赋值给基类;这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去;

Student sobj ;
// 1.子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj ;
Person* pp = &sobj;
Person& rp = sobj;
//他们中间不会产生临时对象

 父子继承是一种 is-a 的关系,子类是一个特殊的父类,子类包含一个父类,所以可以通过切片的方式将派生类对象赋值给父类但父类对象不能赋值给子类

 4. 继承中的作用域

 .      在继承体系中基类和派生类都有独立的作用域;子类创建的对象可以在类外部调用父类的public成员函数;

        当子类和父类中有相同的成员时,子类对象调用时,优先调用自己的成员;子类成员会屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义;

class Person
{
public:
	void fun(){
		cout << "父类" << endl;
	}
protected:
	string _name = "小李子"; // 姓名
	int _num = 111; 	   // 身份证号
};

class Student : public Person
{
public:
	void Print()
	{
		cout << " 姓名:" << _name << endl;
		// 当子类和父类中有两个相同变量时,可以通过指定类域进行访问
		cout << " 身份证号:" << Person::_num << endl;
		cout << " 学号:" << _num << endl;
	}
	void fun(){
		cout << "子类" << endl;
	}
protected:
	int _num = 999; // 学号
};
int main()
{
	Student s;
	s.Print();
    //默认调用的是子类的成员
	//可以通过指定类域的方式进行调用
	s.Person::fun();

	return 0;
}

但是我们也可以通过指定类域的方法去调用父类的成员; 继承中,同名的成员函数,函数名相同就构成隐藏,不管参数和返回值

注意:

        在实际中应用中,继承体系里面最好不要定义同名的成员!

 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:
	
protected:
	int _id;
};

int main()
{	
	Student s1;

	return 0;
}

 我们实现一个父类,Student继承Person,Student什么都不实现,编译器会默认生成的构造函数和析构函数;默认的构造函数和析构函数会去调父类的构造和析构;

  • 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员;
  • 派生类对象析构先调用派生类析构再调基类的析构(保证派生类对象先清理派生类成员再清理基类成员的顺序);
  • 派生类对象初始化先调用基类构造再调派生类构造
     

 要实现子类的构造函数,来对子类对象进行初始化:

Student(const char* name, int id)
	:_id(id)
	, Person(name) // _name(name)不可以
{
	cout << "Student(const char* name, int id)" << endl;
}

Student s1("张三", 18);
  • 对子类对象进行初始化,同时也需要传参数对父类进行初始化
  • 对父类进行初始化时需要注意,子类是父类的衍生类,
  • 在对父类部分进行初始化时,要把父类看作一个单独的类进行初始化(对父类整体进行初始化)
  • 不可以单独对父类成员进行初始化

拷贝构造:

Student(const Student& s)
	:Person(s)
	, _id(s._id)
{
	cout << "Student(const Student& s)" << endl;
}

Student s2(s1);

        使用s1进行初始化,对父类进行初始化时可以直接使用子类进行赋值,衍生类在向基类赋值时会有切片(切割),基类会截取对基类部分初始化的数据;这里也体现出了切片的意义;

 赋值重载:

Student& operator=(const Student& s)
{
	if (&s != this)
	{
        // 注意这里需要指定类域,不然会构成隐藏,一直调用子类的operator=
		Person::operator=(s);
		_id = s._id;
	}

	cout << "Student& operator=(const Student& s)" << endl;

	return *this;
}

Student s3("李四", 19);
s1 = s3;

 派生类的operator=必须要调用基类的operator=完成基类的复制;

 派生类的析构:

~Student()
{
	//Person::~Person();
    //父类的析构不可以显示的调用比如:~Person();
	cout << "~Student()" << endl;
}

         这是因为父子类的析构函数构成隐藏,由于多态的原因,析构函数统一会被处理成destructor,如果想要调用就必须指定类域;但是调用后又会很奇怪,每个派生类对象调用了两次基类的析构函数,父类析构函数不需要显示调用,子类析构函数结束时会自动调用父类析构,保证析构先子后父(构造函数先父后子),我们调用的父类析构函数是多余的;(如果子类显式调用就会造成析构先父后子,存在风险)

总结就是派生类中有一个基类,这个基类需要把它当成一个单独的类来处理;

 6. 继承与友元、静态成员

 继承与友元只有一个需要注意的点:

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

 继承与静态成员:

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子
类,都只有一个static成员实例

 7. 多继承与菱形继承

 前边我们玩的都是单继承,单继承:一个子类只有一个直接父类时称这个继承关系为单继

 现在我们来聊一聊多继承,多继承可以说是C++的一个不好的设计,它又会引发菱形继承的问题;

什么是多继承?

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

 菱形继承:菱形继承是多继承的一种特殊情况

 菱形继承的问题:从下面的对象成员模型构造,课以看出他们的继承关系,Student和Teacher继承了Person(Student和Teacher内部都有一个Person),Postgraduate又继承了Student和Teacher,Postgraduate内部就会有两个Person;编译器到底用哪个Person?

 从这里可以看出菱形继承有数据冗余和二义性的问题。

7.1 如何解决

 C++有具体的应对措施,那就是虚拟继承(虚继承),在继承时加上virtual;

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 ()
{
    //使用虚继承后他们用的就是同一Person
    Assistant a ;
    a._name = "peter";
}

 注意virtual不是随便加的,它有具体的添加位置;virtual要添加在派生类对基类的继承声明中的类名之前;

 C++的语法设计复杂,多继承就是一个体现,有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题;

8. 组合

         继承一般不会单独的使用,因为继承的使用会增加程序的耦合度,程序设计要求的是高内聚,低耦合,就是减少代码之间的关联度;C++的继承都是配合多态一起使用;介绍了继承,那就不得不提一下组合;

  • 继承是is - a的关系:每个派生类对象都是一个基类对象;
  • 组合就是has - a的关系:假设B组合了A,每个B对象中都有一个A对象;;
class A
{
protected:
    
    int data;
};

class B
{
protected:

    double x;
    A _a; 
};

类之间的关系可以用继承,也可以用组合,但能用组合最好还是用组合,优先使用组合!


 总结

         继承是C++的一个重要的概念,继承几乎不会单独使用,更多的是配合多态使用,要实现多态,必须需要继承;下期我将会向大家介绍C++的多态;好了以上便是本文的全部内容,希望可以对你有所帮助,感谢阅读!

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

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

相关文章

Python爬虫中的单线程、多线程问题(文末送书)

前言 在使用爬虫爬取数据的时候&#xff0c;当需要爬取的数据量比较大&#xff0c;且急需很快获取到数据的时候&#xff0c;可以考虑将单线程的爬虫写成多线程的爬虫。下面来学习一些它的基础知识和代码编写方法。 一、进程和线程 进程可以理解为是正在运行的程序的实例。进…

实验室储样瓶耐强酸强碱PFA材质试剂瓶适用新材料半导体

PFA&#xff0c;全名可溶性聚四氟乙烯&#xff0c;试剂瓶又叫取样瓶、样品瓶、广口瓶、储样瓶等。主要用于痕量分析、同位素分析等实验室&#xff0c;广泛应用于新兴的半导体、新材料、多晶硅、硅材、微电子等行业。 规格参考&#xff1a;30ml、60ml、100ml、125ml、250ml、30…

QT项目打包

十、项目打包 设置图标 以下是个项目设置图标的 操作步骤 设计或下载一个图标图片&#xff08;推荐分辨率6464及其以上&#xff0c;256256及其以下&#xff09;。转换为.ico格式&#xff0c;转换可以使用下面的网站。 Convertio — 文件转换器 PNG转ICO, 在线转换器 - 转换视频…

国家开放大学 新学期 电大搜题助力学子们取得理想成绩

尊敬的读者朋友们&#xff0c;您是否曾经遇到过在学习中遇到困难&#xff0c;却找不到合适的学习资源和答案的尴尬处境&#xff1f;您是否经历过为了解决学习难题四处奔波而导致时间和精力的浪费&#xff1f;如果您曾经面临这样的困扰&#xff0c;那么您一定不能错过今天为您推…

三、系统知识笔记-计算机系统基础知识

一、计算机系统概述 计算机系统是指用于数据管理的计算机硬件、软件及网络组成的系统。 它是按人的要求接收和存储信息&#xff0c;自动进行数据处理和计算&#xff0c;并输出结果信息的机器系统。 冯诺依曼体系计算机结构&#xff1a; 1.1计算机硬件组成 冯诺依曼计算机结…

中间件-Nginx漏洞整改(限制IP访问隐藏nginx版本信息)

中间件-Nginx漏洞整改&#xff08;限制IP访问&隐藏nginx版本信息&#xff09; 一、限制IP访问1.1 配置Nginx的ACL1.2 重载Nginx配置1.3 验证结果 二、隐藏nginx版本信息2.1 打开Nginx配置文件2.2 隐藏Nginx版本信息2.3 保存并重新加载Nginx配置2.4 验证结果2.5 验证隐藏版本…

LeetCode刷题---查询结果的质量和占比

思路&#xff1a; 首先按照query_name进行分组操作 对组内的rating/position的值求平均值得到quality 对组内的rating的值使用IF函数进行判断&#xff0c;如果rating<3则将其标识为1&#xff0c;如果rating>3,则将其标识为0&#xff0c;通过次方法可以找出组内rating的值…

MNIST数据集下载(自动下载)

&#x1f4da;**MNIST数据集下载(自动下载)**&#x1f4da; &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程 &#x1f448; 希望得到您的…

微信小程序的医院体检预约管理系统springboot+uniapp+python

本系统设计的目的是建立一个简化信息管理工作、便于操作的体检导引平台。共有以下四个模块&#xff1a; uni-app框架&#xff1a;使用Vue.js开发跨平台应用的前端框架&#xff0c;编写一套代码&#xff0c;可编译到Android、小程序等平台。 语言&#xff1a;pythonjavanode.js…

【UE 材质】冰冻效果

效果 步骤 1. 打开“Quixel Bridge” 下载冰的纹理 2. 新建一个材质&#xff0c;这里命名为“M_Frozen” 打开“M_Frozen”&#xff0c;添加如下节点&#xff0c;此时我们可以通过控制参数“偏移”来改变边界的偏移 此时预览效果如下 如果增加参数“偏移”的默认值效果如下 3.…

在 Android 运行 GNU/Linux 二进制程序 (proot)

在 GNU/Linux 系统上运行 Android 应用比较容易 (比如 waydroid), 但是反过来就很麻烦了. Android 虽然也使用 Linux 内核 (kernel), 但是系统环境和一般的 GNU/Linux 系统 (比如 ArchLinux, Debian, Ubuntu, Fedora, NixOS 等) 具有不可忽略的显著差异, 所以为 GNU/Linux 编译…

泽攸科技JS系列高精度台阶仪在半导体领域的应用

泽攸科技JS系列高精度台阶仪是一款先进的自主研发的国产台阶仪&#xff0c;采用了先进的扫描探针技术。通过扫描探针在样品表面上进行微观测量&#xff0c;台阶仪能够准确获取表面形貌信息。其工作原理基于探针与样品表面的相互作用力&#xff0c;通过测量探针的微小位移&#…

[论文笔记] Mistral论文解读

https://arxiv.org/pdf/2310.06825.pdf GQA: 1、加快推理速度 2、减小内存需求 3、允许更大的batch 4、更高的吞吐量 SWA&#xff1a; 1、较低的计算成本 更有效的处理 较长的序列。 2、感受野更符合常理。不再是全局感受野&#xff0c;而是只和前4096个进行语义融合。…

AI入门系列——数据分析(待续)

我们首先要对人工智能领域有个宽泛的了解&#xff0c;有自己的全局性的认识&#xff0c;产生一些判断&#xff0c;才不会人云亦云地因为“薪资高、压力大”等去做出选择或者放弃。再者你做的准备调研越多&#xff0c;确认方向后越不容易放弃&#xff08;等门槛效应&#xff09;…

智能手表的革命性突破:TRIZ理论引领未来穿戴技术!

在科技日新月异的今天&#xff0c;智能手表已经从单纯的计时工具转变为集健康监测、信息通讯、娱乐休闲等多功能于一体的智能穿戴设备。而基于TRIZ理论的智能手表更是在这一变革中扮演着引领者的角色。TRIZ&#xff0c;即发明问题解决理论&#xff0c;是一套系统的创新方法学&a…

FullCalendar日历组件:进行任务增删改,参考gitee例子修改

效果 参考路径 zxj/FullCalendar开发示例 - 码云 - 开源中国 (gitee.com) 代码 主页面&#xff1a;index.php <?php ob_start(); include(includes/session.inc); ?> <!DOCTYPE html> <html><head><title>日程管理</title><meta …

阿里云-系统盘-磁盘扩容

阿里云系统磁盘扩容 之前是测试环境磁盘用的默认的有 40G&#xff0c;后面升级到正式的 磁盘怕不够用打算升级到 100G&#xff0c; 系统镜像&#xff1a; Alibaba Cloud Linux 3.2104 LTS 64 位 磁盘 ESSD 40G 升级步骤&#xff1a; 扩容与创建快照 在阿里云后台首先去扩容…

Stable Diffusion 模型分享:PicX_real(真实照片)

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八 下载地址 模型介绍 作者述&#xff1a;这个模型比“超真实”模型更加多样化&#xff0c;不那么乏味粗糙&#…

搭建Facebook直播网络对IP有要求吗?

在当今数字化时代&#xff0c;Facebook直播已经成为了一种极具吸引力的社交形式&#xff0c;为个人和企业提供了与观众直接互动的机会&#xff0c;成为推广产品、分享经验、建立品牌形象的重要途径。然而&#xff0c;对于许多人来说&#xff0c;搭建一个稳定、高质量的Facebook…

vue3 + vite + ts 中使用less文件全局变量

文章目录 安装依赖新建css变量文件全局引入css变量文件使用css变量 一、安装依赖 npm install less less-loader --save-dev 二、新建CSS变量文件 (1) :在根目录下的src文件中 src-> asset -> css ->glibal.less // glibal.less :root{--public_background_font_Col…