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

news2024/10/6 18:28:43

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. 所以在实践中尽可能多使用组合,提高代码的维护性。不过多态的实现必须依赖继承。

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

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

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

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

相关文章

JMeter - 如何测试REST API / 微服务

概述&#xff1a; 有许多方法和工具可用于测试REST API。 当我需要测试REST API时&#xff0c;在查看了各种工具和选项之后&#xff0c;由于以下原因&#xff0c;我选择了JMeter。 JMeter是免费和开源的。 JMeter可以从CSV文件中直接读取您的测试数据。参数化非常简单。 可以…

基于SWIFT框架的Phi-3推理、微调实战教程

近期&#xff0c; Microsoft 推出 Phi-3&#xff0c;这是 Microsoft 开发的一系列开放式 AI 模型。Phi-3 模型是一个功能强大、成本效益高的小语言模型 (SLM)&#xff0c;在各种语言、推理、编码和数学基准测试中&#xff0c;在同级别参数模型中性能表现优秀。为开发者构建生成…

零基础自学网络安全/Web安全(超详细入门到进阶)学完即可就业(含学习笔记)

一、为什么选择网络安全&#xff1f; 这几年随着我国《国家网络空间安全战略》《网络安全法》《网络安全等级保护2.0》等一系列政策/法规/标准的持续落地&#xff0c;网络安全行业地位、薪资随之水涨船高。 未来3-5年&#xff0c;是安全行业的黄金发展期&#xff0c;提前踏入…

ICode国际青少年编程竞赛- Python-1级训练场-变量练习

ICode国际青少年编程竞赛- Python-1级训练场-变量练习 1、 a 8 for i in range(8):Dev.step(a)Dev.turnRight()a - 12、 a 3 for i in range(4):Dev.step(a)Dev.turnRight()a a 1 Dev.step(5)3、 a 4 for i in range(4):Dev.step(2)Dev.step(-5)Dev.step(3)Spaceship.…

一次性邮箱API发送邮件的方法?如何配置?

一次性邮箱API发送邮件有哪些注意事项&#xff1f;怎么安全发信&#xff1f; 随着网络安全问题的日益凸显&#xff0c;如何安全、高效地发送邮件成为了一个亟待解决的问题。一次性邮箱API的出现&#xff0c;为我们提供了一种新的解决方案。那么&#xff0c;如何使用一次性邮箱…

在uni-app开发的小程序中引入阿里的多色图标

uniapp不支持阿里多色图标&#xff0c;需要使用工具iconfont-tools进行处理 1.首先 在阿里图标库将 需要的图标添加到项目中 并下载压缩包&#xff0c;取出iconfont.js文件 2.安装iconfont-tools,安装完成会显示出安装到了电脑的那个目录 3&#xff0c;进入目录就会看到下面的…

【Unity动画系统】动画层级(Animation Layer)讲解与使用

如何使用Unity的Animation Layer和Avater Mask把多个动画组合使用 想让玩家持枪行走&#xff0c;但是手里只有行走和持枪站立的动作。 Unity中最方便的解决办法就是使用动画层级animation layer以及替身蒙版avatar mask。 创建一个动画层级 Weight表示权重&#xff0c;0的话则…

MFC扩展库BCGControlBar Pro v34.1 - 可视化设计器、主题新升级

BCGControlBar库拥有500多个经过全面设计、测试和充分记录的MFC扩展类。 我们的组件可以轻松地集成到您的应用程序中&#xff0c;并为您节省数百个开发和调试时间。 BCGControlBar专业版 v34.1已正式发布了&#xff0c;这个版本包含了对Windows 10/11字体图标的支持、功能区和…

国内小白用什么方法充值使用ChatGPT4.0?

首先说一下IOS礼品卡订阅&#xff0c;目前最经济实惠的订阅方式&#xff0c;具体操作步骤 使用IOS设备充值&#xff0c;用 App Stroe 兑换券 1、支付宝地址切换旧金山&#xff0c;在里面买app store 的兑换卷 2、美区Apple ID登陆app store &#xff0c;充值兑换券 3、IOS设…

AI 数据观 | TapData Cloud + MongoDB Atlas:大模型与 RAG 技术有机结合,落地实时工单处理智能化解决方案

本篇为「AI 数据观」系列文章第二弹&#xff0c;在这里&#xff0c;我们将进一步探讨 AI 行业的数据价值。以 RAG 的智能工单应用场景为例&#xff0c;共同探索如何使用 Tapdata Cloud MongoDB Atlas 实现具备实时更新能力的向量数据库&#xff0c;为企业工单处理的智能化和自…

7款AI绘画软件推荐,必备工具,一键生成绘画

随着人工智能技术的不断发展&#xff0c;AI绘画软件在艺术创作中扮演着越来越重要的角色。这些软件利机器学习和深度学习算法&#xff0c;可以模仿艺术家的风格&#xff0c;并生成逼真的绘画作品。一起来见证下吧&#xff0c;先给大家展示下AI绘画生成的效果图&#xff1a; 一、…

边界框(bounding box) 目标物体的位置和大小 交并比(Intersection over Union,IoU) 锚框(Anchor box)

边界框(bounding box) 在检测任务中,我们需要同时预测物体的类别和位置,因此需要引入一些跟位置相关的概念。通常使用边界框(bounding box,bbox)来表示物体的位置,边界框是正好能包含物体的矩形框。 在目标检测任务中,边界框(bounding box,bbox)是一个非常重要的…

RS232/RS485转12路模拟信号隔离D/A转换器4-20mA0-20mA0-25mA0-5V0-10V0-75mV

特点&#xff1a; ● RS-485/232接口&#xff0c;隔离转换成12路标准模拟信号输出 ● 可选型输出4-20mA或0-10V控制其他设备 ● 模拟信号输出精度优于 0.2% ● 可以程控校准模块输出精度 ● 信号输出 / 通讯接口之间隔离耐压3000VDC ● 宽电源供电范围&#xff1a;10 ~ …

git与gitlab

目录 gitlab 下载与安装 重置管理员密码 gitlab命令 git远程gitlab相关命令 认证 补充 git git 分布式版本控制 安装 git的四个区域与文件的四个状态 使用git 常用命令 git 分布式管理系统 gitlab 企业私有库 github 公网共享库&#xff0c;全球…

2024五一赛数学建模ABC题成品论文word+完整代码数据汇总

2024五一赛A题参考论文42页1-3问可执行代码[py Mtlab双版本]各类可视化图表https://www.jdmm.cc/file/27105852024五一赛B题保姆级建模思路32页可执行代码[py mtlab]后续成品论文处理可视化图表https://www.jdmm.cc/file/27105862024五一赛C题成品论文23页word页1-3问可执行代码…

亚马逊自养号测评环境搭建需要准备哪些?

在当下电商领域竞争白热化的背景下&#xff0c;亚马逊平台的卖家们对流量之于店铺转化率的重要性有着深刻的认识。随着对平台内部流量的依赖逐渐减弱&#xff0c;他们纷纷寻求更多元化的途径来提升销售业绩和品牌的市场影响力。在此过程中&#xff0c;自养号测评成为了一种备受…

alphassl ocsp泛域名证书

泛域名SSL证书又可以称之为通配符SSL证书&#xff0c;这款SSL数字证书可以同时保护主域名以及主域名下的所有子域名&#xff0c;被广泛应用于各种类型的网站&#xff0c;为多个网站提供安全服务。AlphaSSL是比较知名的SSL证书品牌之一&#xff0c;旗下的OCSP泛域名SSL证书比较受…

linux——主从同步

1. 保证主节点开始二进制日志&#xff0c;从节点配置中继日志 2. 从节点的开启一个 I/O 线程读取主节点二进制日志的内容 3. 从节点读取主节点的二进制日志之后&#xff0c;会将去读的内容写入从节点的中继日志 4. 从节点开启 SQL 线程&#xff0c;读取中继日志的内容&a…

计算机视觉中的计算几何

计算几何领域出现于 20 世纪 70 年代&#xff0c;研究解决几何问题的数据结构和算法。这尤其包括确定图像内的拓扑结构&#xff0c;或者实际上是更高维的表示&#xff0c;例如点邻域&#xff0c;这可以帮助从数字图像数据等中导出几何意义[1]。 计算机视觉主要涉及静态或动态图…

利用傅里叶变换公式理解camera raw中的纹理和清晰度的概念(可惜的是camera raw的计算公式应该不会是这个傅里叶变换,只能说类似于这里的效果)

知乎说&#xff1a;在Adobe官方的解释中&#xff0c;就像图片可以分解成彩色通道&#xff08;如&#xff1a;红绿蓝通道&#xff09;&#xff0c;同样的&#xff0c;图片也可以分解成不同的频率&#xff0c;一张图片可以是由高频&#xff0c;中频和低频组成&#xff0c;例如&am…