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

目录

前言

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.mfbz.cn/a/413392.html

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

相关文章

项目登录方案选型

一.Cookie + Session 登录 大家都知道,HTTP 是一种无状态的协议。无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。即我们给服务器发送 HTTP 请求之后,服务器根据请求返回数据,但不会记录任何信息。为了解决 HTTP 无状态的问题,出现了 Cookie。Co…

计网Lesson15 - TCP可靠传输

文章目录 1. 停止等待ARQ协议2. 连续ARQ协议与滑动窗口协议 1. 停止等待ARQ协议 ARQ&#xff08;Automatic Repeat–reQuest&#xff09;自动重传请求 几种重传情况 发送端丢失 发送方过久没有接收到接收方的确认报&#xff0c;这种情况会触发超时重传机制&#xff0c;发送方…

【Godot4.2】菜单栏生成函数库menuDB

概述 关于Godot的手动菜单栏制作&#xff0c;我已经在之前的文章中有所描述。 但是对于一些场景&#xff0c;手动制作菜单仍然是一个比较低效的做法。所以我将MenuBar以及基于HBoxContainerMenuButton创建菜单栏写成了一个静态函数库。 利用此函数库我们可以用函数形式构造P…

【Oracle】玩转Oracle数据库(六):模式对象管理与安全管理

前言 嘿&#xff0c;数据库大冒险家们&#xff01;准备好迎接数据库管理的新挑战了吗&#xff1f;今天我们要探索的是Oracle数据库中的模式对象管理与安全管理&#xff01;&#x1f6e1;️&#x1f4bb; 在这篇博文【Oracle】玩转Oracle数据库&#xff08;六&#xff09;&#…

CTFHub技能树web之XSS

在XSS系列的题目中&#xff0c;由于需要使用能够接受XSS数据的平台&#xff0c;并且由于使用的是CTFHub的模拟机器人点击我们的虚假URL&#xff0c;因此使用的XSS平台不能是自己本地搭建的&#xff0c;如果是本地的模拟点击的机器人将无法访问我们给的这个URL地址&#xff0c;也…

Maven【1】(命令行操作)

文章目录 一丶创建maven工程二、理解pom.xml三、maven的构建命令1.编译操作2.清理操作3.测试操作4.打包操作5.安装操作 一丶创建maven工程 首先创建这样一个目录&#xff0c;然后从命令行里进入这个目录&#xff1a; 然后接下来就在这个命令行里进行操作了。 这个命令是&…

听劝!年后跳槽需谨慎……

​新年新气象&#xff0c;许多不满需求的开发者都想开展一番新的事业。跳槽找工作是要吃老本行&#xff1f;还是换岗&#xff1f;请三思啊&#xff01;&#xff01; 2024年的移动开发行业岗位还友好吗&#xff1f; 随着互联网的时间发展推移&#xff0c;大部分开发岗已经走向末…

Spring Cloud Gateway官方文档学习

文章目录 推荐写在前面一、熟悉Gateway基本概念与原理1、三大概念2、工作流程 二、基本使用路由断言的两种写法 三、路由断言工厂1、After路由断言工厂2、Before路由断言工厂3、Between路由断言工厂4、Cookie路由断言工厂5、Header路由断言工厂6、Host路由断言工厂7、Method路由…

中间件-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 验证隐藏版本…

LabVIEW光伏逆变器低电压穿越能力测试

LabVIEW光伏逆变器低电压穿越能力测试 随着光伏发电技术的迅速发展&#xff0c;光伏逆变器的低电压穿越&#xff08;LVRT&#xff09;能力日益成为影响电网稳定性的关键因素。为了提升光伏逆变器的并网性能&#xff0c;开发了一套基于LabVIEW的光伏逆变器LVRT测试系统。该系统…

【VSCode】SSH Remote 通过跳板机连开发机提示“bash行1 powershell未找到命令”

需求背景 因为需要&#xff0c;在家我需要挂上公司VPN然后SSH连到跳板机&#xff0c;然后再从跳板机SSH进开发机。 问题背景 跳板机进开发机输入完密码显示 bash行1 powershell未找到命令VSCode SSH Remote跳板机配置请自行搜素其他文章config配置 注意其中ssh.exe地址请根据…

用39块钱的全志V851se视觉开发板做了个小相机,还可以物品识别、自动追焦!

用39块钱的V851se视觉开发板做了个小相机。 可以进行物品识别、自动追焦&#xff01; 这个超低成本的小相机是在V851se上移植使用全志在线开源版本的Tina Linux与OpenCV框架开启摄像头拍照捕获视频&#xff0c;并结合NPU实现Mobilenet v2目标分类识别以及运动追踪等功能…并最终…

k8s节点负载使用情况分析命令kubectl describe node [node-name]

1.到任意安装了kubectl节点命令的节点上执行kubectl describe node [node-name] 上面的Requests最小分配 Limits最大分配是所有pod之和&#xff0c;最小分配之和不能超过服务器实际参数&#xff0c;否则新的pod会因为资源不够起不来&#xff0c;最大分配是预设之和&#xff0…

图片数据爬取工具Image-Downloader

图片数据爬取工具Image-Downloader_image downloader-CSDN博客文章浏览阅读1.2k次。既然我们使用 Image-Downloader 这个工具进行图片搜索&#xff0c;相比python我们都非常熟悉&#xff0c;在此不在叙述&#xff0c;可参考其他文章。_image downloaderhttps://blog.csdn.net/w…

有方机器人 STM32智能小车 项目学习笔记1

今天开始学习有方机器人--智能小车项目&#xff0c;正点原子部分的学习先放一放&#xff0c;还是小车更有吸引力哈哈。 新建工程及工程模板搭建 新建工程须知 目前常用的 STM32 的开发方式主要有基于寄存器编程、基于标准库函数编程、基于 HAL 库编程这三种。 寄存器版本--…

七、防御保护---VPN篇

七、防御保护---VPN篇 一、VPN介绍二、VPN的分类2.1 根据建设的单位不同分类2.2 根据组网方式不同分类2.3 根据应用场景不同分类2.4 按照VPN技术实现的网络层次进行分类&#xff1a; 三、VPN的核心技术3.1 隧道技术3.2 身份认证技术3.3 加密技术3.4 数据验证技术 一、VPN介绍 …

【Python-语法】

Python-语法 ■ Python基础■ 数据类型■ 注释 单行注释&#xff0c;多行注释■ 编码方式 ■■■■■ ■ Python基础 ■ 数据类型 ■ 注释 单行注释&#xff0c;多行注释 ■ 编码方式 ■ ■ ■ ■ ■

vue3新特性-defineOptions和defineModel

defineOptions 背景说明&#xff1a; 有 <script setup> 之前&#xff0c;如果要定义 props, emits 可以轻而易举地添加一个与 setup 平级的属性。 但是用了 <script setup> 后&#xff0c;就没法这么干了 setup 属性已经没有了&#xff0c;自然无法添加与其平…

apachectl: line 79: 20233 Segmentation fault (core dumped) $HTTPD “$@“

[TOC](apachectl: line 79: 20233 Segmentation fault (core dumped) $HTTPD “$”) 1、问题描述 apache 启动报错 apachectl: line 79: 20233 Segmentation fault (core dumped) $HTTPD “$” 2、问题分析 参考链接: https://stackoverflow.com/questions/43726930/apache…

【leetcode热题】杨辉三角 II

难度&#xff1a; 简单通过率&#xff1a; 41.1%题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题目描述 给定一个非负索引 k&#xff0c;其中 k ≤ 33&#xff0c;返回杨辉三角的第 k 行。 在杨辉三角中&#xff0c;每个数是它左上方和右上方的数的和。 示…