继承知识及扩展(C++)

1. 继承是什么?

继承是面向对象编程的三大特征之一,也是代码复用的手段之一。之前我们在很多的地方尝试函数的复用,而继承是为了类的复用提供了很好的方式。

(1)继承的代码怎么写

在一个类后面使用 :继承方式 类名 表示以某种方式继承某个类

下面的代码的意思是学生类和老师类都继承了人类,而且是public继承(这个是什么意思请往后看),也就是说,老师和学生的public成员都可以访问对应人类的public成员,protected成员可以访问对应的protected成员(这个可以看后面关于继承方式对基类成员访问的影响)

class Person
{
public:
    void Print()
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }
protected:
    string _name = "Gogo"; 
    int _age = 18; 
};

class Student : public Person
{ 
protected:
    int _stuID; // 学号 
};

class Teacher : public Person
{
protected:
    int _jobid; // 工号 
};

(2)三种继承方式

三种访问限定符对应三种继承方式

访问限定符:public        protected        private

继承方式:    public        protected        private

基类/父类:指的是被继承的类

派生类/子类:指的是继承的父类的类

类成员\继承方式public继承protected继承private继承
基类public成员派生类的public成员派生类的protected成员派生类的private成员
基类protected成员派生类的protected成员派生类的protected成员派生类的private成员
基类private成员派生类中不可访问派生类中不可访问派生类中不可访问

三种访问限定符的大小:public > protected > private

一般来说,派生类的成员的访问方式是继承方式和成员在基类的访问方式的较小的那个

(a)其实,我们多数时候用的都是public继承,因为后两种继承方式的成员只能在派生类中使用,扩展性不够,class默认的继承方式private,struct是public,和成员的默认访问权限是一样的;

(b)基类的private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就需要定义为protected,由此可以看出,protected限定符是因继承才出现的。

2. 赋值转换

看回刚刚那个例子,学生类作为人类的派生类,可以将自己的对象赋值给基类的对象、指针、引用

注意:基类的对象不能赋值给派生类,因为其没有独属于派生类的成员;基类的指针可以强制类型转换成派生类指针,但是基类的指针必须指向派生类对象才行。

class Person
{};

class Student : public Person
{};


int main()
{
    Student s;
    Person p = s;     // 派生类对象赋值给基类对象
    Person *ptr = &s; // 派生类对象赋值给基类指针
    Person &ref = s;  // 派生类对象赋值给基类引用
    return 0;
}

【图解】

把派生类中基类的部分赋值过去,这就是所谓的切片或切割,很形象对不对。

3. 隐藏/重定义

在继承关系中,基类和派生类都回有各自的作用域。因此当两者存在同名成员的时候,派生类使用这个名字的成员时,会调用自己类内部的,这被称为隐藏,也叫做重定义

当然,如果想访问父类的同名成员,也可以用域作用限定符 :: 来进行访问

【例子】

class Person
{
public:
    void f(int x)
    {
        cout << x << endl;
    }
protected:
	int _telephone = 10086;
};

class Student : public Person
{
public:
	void fun()
	{
		cout << _telephone << endl;
	}

    void f(double x)
    {
        cout << x << endl;
    }   
    
protected:
	int _telephone = 10010;
};


int main()
{
	Student s;
	s.fun(); // 10010,子类对象对父类成员变量的隐藏
    s.f(1.23); // 调用子类的f函数,对成员函数的隐藏
    s.Person::f(1); // 调用父类的f函数
	return 0;
}

注意:虽然是合法的,但是在继承体系中最好不要定义同名的成员

4. 默认成员函数

关于默认成员函数是什么,可以翻阅我之前的文章,包括类和对象的基础知识都需要了解,这部分内容才能看懂。而在继承的体系中,也有一些特殊的规则需要了解。

构造函数:自动调用基类中的构造函数初始化基类部分的成员,若基类中无默认的构造函数,则需要显示调用基类的构造函数。

先调用基类的构造,再调用派生类的,因为先定义的先构造。

拷贝构造:调用基类的拷贝构造完成对基类成员的拷贝构造

赋值重载:调用基类的赋值重载实现对基类成员的赋值

析构:先调用派生类的析构,再调用基类的析构函数,和构造的顺序正好相反

class Person
{
public:
    Person(const string &name, int age)
        : _name(name), _age(age)
    {
        cout << "Person()" << endl;
    }

    Person(const Person &p)
        : _name(p._name), _age(p._age)
    {
        cout << "Person(const Person& p)" << endl;
    }

    Person &operator=(const Person &p)
    {
        cout << "Person& operator=(const Person& p)" << endl;
        if (this != &p)
        {
            _name = p._name;
            _age = p._age;
        }
        return *this;
    }

    ~Person()
    {
        cout << "~Person()" << endl;
    }

private:
    string _name = "Gogo";
    int _age = 18;
};

class Student : public Person
{
    Student(const string &name, int age, int id)
        : Person(name, age)  // 调用基类的构造函数初始化基类的部分成员
        , _stuID(id)  // 初始化派生类的成员
    {
        cout << "Student()" << endl;
    }

    Student(const Student &s)
        : Person(s) // 调用基类的拷贝构造函数完成基类成员的拷贝构造
        ,_stuID(s._stuID) // 拷贝构造派生类的成员
    {
        cout << "Student(const Student& s)" << endl;
    }

    Student &operator=(const Student &s)
    {
        cout << "Student& operator=(const Student& s)" << endl;
        if (this != &s)
        {
            Person::operator=(s); // 调用基类的operator=完成基类成员的赋值,切片
            _stuID = s._stuID;    // 完成派生类成员的赋值
        }
        return *this;
    }

    ~Student()
    {
        cout << "~Student()" << endl;
        // 派生类的析构函数会在被调用完成后自动调用基类的析构函数
    }

private:
    int _stuID; 
};

【重点】

1. 派生类的赋值运算符重载和基类的函数名相同,构成隐藏,在派生类当中调用基类的赋值重载要用域作用限定符

2. 因为多态的需要,基类和派生类的析构会统一成 destructor() 。因此,要调用父类的析构函数需要域作用限定符


5. 友元和静态成员 

友元关系不能继承,当基类的友元函数想访问派生类的私有和保护是不行的。要想访问,必须得成为派生类的友元才能实现。

静态成员:已经定义了一个静态成员,那么在一个继承体系中只能有一个该名称的静态成员

class Person
{
public:
    Person()
    {
        _count++;
    }

    Person(const Person& p) 
	{
		_count++;
	}
protected:
    string _name = "Gogo";
    int _age = 18;
public:
    static int _count;
};

class Student : public Person
{
protected:
    int _stuID;
};

int Person::_count = 0; // 静态成员变量在类外初始化

int main()
{
    Student s1;
    Student s2;
    Student s3(s1);
    
    cout << Person::_count << endl;  // 运行结果为3
    cout << Student::_count << endl; // 也为3
    return 0;
}


6. 单继承多继承菱形继承

单继承:一个子类只有一个父类

多继承:一个子类有两个或两个以上的直接父类

菱形继承:多继承的意外

菱形继承看起来平平无奇,但是其实他是存在一定的问题的,比如如果创建了一个Assistant的对象,如果给 _name 成员赋值,其实是无法明确是赋给 Student 还是 Teacher 的成员。

当然我们也可以通过域作用限定符来指定那个 _name ,可以解决二义性的问题,但是没有办法解决冗余的问题,因为 Assistant 对象在 Person 的成员会存在两份。

Assistant a;
a.Student::_name = "Micheal";
a.Teacher::_name = "Micheal";

7. 菱形虚拟继承

这个算是菱形继承的解决方案,那么我们来比较一下普通菱形继承和菱形虚拟继承有什么区别吧

【菱形继承】

class Person
{
public:
	int _person;
};
class Student : public Person
{
public:
	int _student;
};
class Teacher : public Person
{
public:
	int _teacher;
};
class Assistant : public Student, public Teacher
{
public:
	int _assistant;
};

int main()
{
	Assistant ass;
	ass.Student::_person = 1;
	ass.Teacher::_person = 2;
	ass._student = 3;
	ass._teacher = 4;
	ass._assistant = 5;
	return 0;
}

【菱形虚拟继承】

仅展示变化的部分:

class Student : virtual public Person
{
public:
	int _student;
};
class Teacher : virtual public Person
{
public:
	int _teacher;
};

从代码上看,似乎没有多大的变化,但是成员变量在内存中的存储方式发生了改变

【普通菱形继承】

可见,_person在Student和Teacher中各存了一份,导致ass对象中存了两个_person,从而导致二义性和冗余。

【菱形虚拟继承】

而菱形虚拟继承是在原先放_person对象的位置存放了公共虚基类成员变量的地址,保证两个_person存在同一个地址,_student和_teacher都能通过地址找到同一个_person。

【总结】

一般不建议使用菱形继承,会导致代码的复杂性和性能出现问题,而且可能使得代码难以分析。

8. 继承和组合

【代码对比】

// 继承
class Car
{
protected:
	string _colour; 
	string _num; 
};
class BMW : public Car
{
public:
	void Drive()
	{
		cout << "this is BMW" << endl;
	}
};
// 组合
class Tire
{
protected:
	string _brand; 
	size_t _size; 
};
class Car
{
protected:
	string _colour;
	string _num; 
	Tire _t; 
};

1. 继承是 is-a 的关系,组合是 has-a 的关系;

解释:BMW is a Car. / Car has a Tire.

2. 继承在一定程度上破坏了基类的封装,也就是基类的改变对派生类有很大的影响,依赖关系很强,也就是所谓的耦合度高,也称为白箱复用。组合则相反,属于黑箱复用,耦合度较低。

3. 所以在实践中尽可能多使用组合,提高代码的维护性。不过多态的实现必须依赖继承。

如果你能看到这里,给你点个赞!

如果觉得这篇文章不错的话,不妨点个赞支持一下,你的支持是我持续更新的动力~

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

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

相关文章

知识图谱融入RAG模型:LinkedIn重塑智能客服新范式【附LeCun哈佛演讲PPT】

原文&#xff1a;Retrieval-Augmented Generation with Knowledge Graphs for Customer Service Question Answering 一、研究背景与问题 在客服领域,快速准确地匹配用户问题与历史工单,是提供优质回答的关键。传统的检索增强生成(Retrieval-Augmented Generation, RAG)方法虽…

IDEA-控制台日志过滤插件 - Grep Console

IDEA-控制台日志过滤插件 - Grep Console 当idea控制台日志较多时&#xff0c;为了方便查找关键字&#xff0c;使用Grep Console插件&#xff0c;指定控制台中关键字高亮显示 1.安装 2.使用 2.1 高亮显示 控制台中指定颜色高亮显示指定字符 效果: 重启项目后还是会高亮显示 取…

【软考高项】三十三、质量管理

一、管理基础 质量定义 国际标准&#xff1a;反映实体满足主体明确和隐含需求的能力的特性总和。 国家标准&#xff1a;一组固有特性满足要求的程度。固有特性是指在某事或某物中本来就有的&#xff0c;尤其是那种永久的可区分的特征。 ➢ 对产品来说&#xff0c;例如…

缓存菜品操作

一&#xff1a;问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大。 二&#xff1a;实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; 每个分…

k8s保持pod健康

存活探针 Kubemetes 可以通过存活探针 (liveness probe) 检查容器是否还在运行。可以为 pod 中的每个容器单独指定存活探针。如果探测失败&#xff0c;Kubemetes 将定期执行探针并重新启动容器。 Kubemetes 有以下三种探测容器的机制&#xff1a; HTTP GET 探针对容器的 IP 地…

Day61:单调栈 739. 每日温度 496.下一个更大元素 I

739. 每日温度 给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &#xff0c;其中 answer[i] 是指对于第 i 天&#xff0c;下一个更高温度出现在几天后。如果气温在这之后都不会升高&#xff0c;请在该位置用 0 来代替。 示例 1: 输…

发表博客之:gemm/threadblock/threadblock_swizzle.h 文件夹讲解,cutlass深入讲解

文章目录 [发表博客之&#xff1a;gemm/threadblock/threadblock_swizzle.h 文件夹讲解&#xff0c;cutlass深入讲解](https://cyj666.blog.csdn.net/article/details/138514145)先来看一下最简单的struct GemmIdentityThreadblockSwizzle结构体 发表博客之&#xff1a;gemm/th…

vue2 webpack-dev-server Unknown promise rejection reason

在vue.config.js中添加如下配置&#xff0c;重启项目即可 module.exports defineConfig({devServer: {client: {overlay: false,},} })参考

探索中位数快速排序算法:高效寻找数据集的中间值

在计算机科学领域&#xff0c;寻找数据集的中位数是一个常见而重要的问题。而快速排序算法作为一种高效的排序算法&#xff0c;可以被巧妙地利用来解决中位数查找的问题。本文将深入探讨中位数快速排序算法的原理、实现方法以及应用场景&#xff0c;带你领略这一寻找中间值的高…

vue 金额组件,输入提示单位:‘千’、‘万’、‘十万’...并用‘,’三个格式化

近期项目中遇到一个需求&#xff0c;金额输入框&#xff0c;输入过程中自动提示‘千’、‘万’、‘十万’、‘百万’......等单位提示&#xff0c;鼠标失去焦点后&#xff0c;并用‘,’三位隔开计数。 例如&#xff1a; 输入&#xff1a;12345.99 失去焦点&#xff1a;12,34…

Vue--》从零开始打造交互体验一流的电商平台(一)

今天开始使用 vue3 ts 搭建一个电商项目平台&#xff0c;因为文章会将项目的每处代码的书写都会讲解到&#xff0c;所以本项目会分成好几篇文章进行讲解&#xff0c;我会在最后一篇文章中会将项目代码开源到我的github上&#xff0c;大家可以自行去进行下载运行&#xff0c;希…

【Node.js工程师养成计划】之express中间件与接口规范

一、Express中间件的概念与基本应用 const express require(express)// 加一个注释&#xff0c;用以说明&#xff0c;本项目代码可以任意定制更改 const app express()const PORT process.env.PORT || 3000// // 挂载路由 // app.use(/api, router)// // 挂载统一处理服务端…

【倪亲斫经典水墨云纹仲尼式】倪诗韵亲斫古琴

【倪亲斫经典水墨云纹仲尼式】倪诗韵亲斫古琴 松透润&#xff0c;适合大曲文曲潇湘欸乃平沙&#xff0c;余韵悠长&#xff0c;手感极其舒适&#xff0c;久弹不疲。

[Linux][网络][TCP][三][超时重传][快速重传][SACK][D-SACK][滑动窗口]详细讲解

目录 1.超时重传1.什么是超时重传&#xff1f;2.超时时间是如何确定的&#xff1f; 2.快速重传3.SACK4.D-SACK1.ACK丢失2.网络延迟 5.滑动窗口0.问题抛出1.发送方的滑动窗口2.如何表示发送方的四个部分&#xff1f;3.接收方的滑动窗口4.滑动窗口的完善理解 1.超时重传 1.什么是…

C++手写协程项目(协程实现线程结构体、线程调度器定义,线程挂起函数、线程切换函数、线程恢复函数、线程结束函数、线程结束判断函数,模块测试)

协程结构体定义 之前我们使用linux下协程函数实现了线程切换&#xff0c;使用的是ucontext_t结构体&#xff0c;和基于这个结构体的四个函数。现在我们要用这些工具来实现我们自己的一个线程结构体&#xff0c;并实现线程调度和线程切换、挂起。 首先我们来实现以下线程结构体…

Splay 树简介

【Splay 树简介】 ● Treap 树解决平衡的办法是给每个结点加上一个随机的优先级&#xff0c;实现概率上的平衡。Splay 树直接用旋转调整树的形态&#xff0c;通过旋转改善树的平衡性。计算量小&#xff0c;效果好。 ● Splay 树的旋转主要分为“单旋”和“双旋”。 所谓“单旋”…

基于52单片机的AS608指纹密码锁电路原理图+源程序+PCB实物制作

目录 1、前言 2、实物图 3、PCB图 4、原理图 5、程序 资料下载地址&#xff1a;基于52单片机的AS608指纹密码锁电路原理图源程序PCB实物制作 1、前言 这是一个基于AS608STC89C52单片机的指纹识别和键盘密码锁。 里面包括程序&#xff0c;原理图&#xff0c;pcb图和实…

OpenNJet:云原生技术中的创新者与实践者

目录 引言OpenNJet介绍OpenNJet优势1. 性能无损动态配置2. 灵活的CoPilot框架3. 支持HTTP/34. 支持国密5. 企业级应用6. 高效安全 OpenNJet 编译与安装环境准备编译环境配置配置yum源yum 安装软件包创建符号连接修改 ld.so.conf 配置 编译代码 部署 WEB SERVER配置OpenNJet部署…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-13-按键实验

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

FTP协议与工作原理

一、FTP协议 FTP&#xff08;FileTransferProtocol&#xff09;文件传输协议&#xff1a;用于Internet上的控制文件的双向传输&#xff0c;是一个应用程序&#xff08;Application&#xff09;。基于不同的操作系统有不同的FTP应用程序&#xff0c;而所有这些应用程序都遵守同…