c++入门学习⑦——继承和多态(超级详细版)

news2025/1/8 4:37:37

目录

前言

继承

继承是什么?

为什么会存在继承?

语法:

一些基本的定义:

三种继承方式:

对象模型

对于构造和析构的顺序

同名函数的处理方式

总结:

静态成员:

定义:

性质:

共享数据 

编译阶段分配内存

类内声明类外初始化

静态成员函数

静态成员函数与普通成员函数的区别:

静态成员访问:

多继承

菱形继承

菱形继承会遇到的问题:

如何解决?

原理:

多态

多态分类:

静态多态和动态多态区分:

静态多态:(静态绑定)

动态多态:(动态绑定)

动态多态实现:

引入:

那什么是虚函数?

虚函数的重写?

多态实现代码示例:

虚函数的动态绑定机制(面试经常问的)

 重载、重定义、重写的区别

纯虚函数

抽象类:

那什么是接口继承呢?

那正常的继承叫做什么?

多态原理(工具观察):

虚析构

语法:

原理:通过父类指针来释放子类空间

纯虚析构

特点:

语法:

纯虚析构和虚析构的共性与区别:

虚析构和纯虚析构共性:

虚析构和纯虚析构区别:

总结:

总结:


前言

这篇博客介绍相关c++继承以及多态的内容,在c++中属于很重要的一个部分,请认真学习(ง •_•)ง

继承

继承是什么?

简单从字面意思上来说就是继承上一个事物的基本特点

准确定义是:继承是面向对象三大特征之一,可以使得子类具有父类的属性和方法 , 还可以在子类中重新定义 ,以及追加属性和方法。

为什么会存在继承?

假如要构造两个类——斑点猫和白猫,其中包含的行为有类对象的行为习惯等,而这两个同属于猫类,他们有很多相似的地方,如果没有继承,则需要重复性写这一相似的内容,一个两个还好,如果多个类都有相似之处,继承就是比较好的方式了。

语法:

class 子类名:继承方式(public/private/protected)  父类名

一些基本的定义:

基类:被继承的类,又称为“父类”

派生类:继承其他类的类,又称为“子类”

三种继承方式:

公共继承public:继承父类的公共权限,则相应的私有权限和保护权限也继承过去,也是相应的私有和保护权限

保护继承protected:继承父类的保护权限,则父类的公共权限到子类中变为保护权限,而私有权限仍然是私有权限

私有继承private:继承父类的私有权限,父类中所有权限到子类中都是私有权限。

对象模型

这里创造两个类,一个基类,一个派生类,B类继承A类的公共权限

class A

class B

#include<iostream>
using namespace std
class A
{
public:
    int a;
};
class B:public A
{
public:    
    int b;
};

则它们在内存中的分布是:

也就是说,派生类会继承一份基类的成员,然后在旁边创建自己的成员

对于构造和析构的顺序

当创建一个子类对象后,如果要初始化它,则哪一个类先构造,哪一个类后构造,程序结束时,谁先析构?

谁的构造函数先调用,谁的析构函数先调用

代码示例:

#include<iostream>
using namespace std;
 
class A
{
public:
    A()
    {
        cout<<"A的构造函数"<<endl;
    }
    ~A()
    {
        cout<<"A的析构函数"<<endl;
    }
    int m_a;
};
class B:public A
{
public:
    B()
    {
        cout<<"B的构造函数"<<endl;
    }
    ~B()
    {
        cout<<"B的析构函数"<<endl;
    }
    int m_b;
};
test1(){
	B b;
}
int main()
{
    test1();
    system("pause"); 
    return 0;
}

 输出结果为:

由此可见刚才问题的答案是:父类先构造然后子类构造,子类析构再父类析构

这个可以用鸡生蛋来记——鸡比它生的蛋要早出生,先有这只鸡才有这个蛋,然后吃的时候,先吃蛋,再吃鸡

同名函数的处理方式

对于基类和派生类的同名函数如何处理呢?换句话说当调用这个同名函数时,真正会起作用的是哪一个函数呢?

我们来通过代码看一看:

#include<iostream>
using namespace std;
 
class A
{
public:
    void speak()
	{
    	cout<<"A会说话了"<<endl;
	}
    int m_a;
};
class B:public A
{
public:
    void speak()
	{
    	cout<<"B会说话了"<<endl;
	}
    int m_b;
};
test1(){
	B b;
	b.speak();
}
int main()
{
    test1();
    system("pause"); 
    return 0;
}

通过代码发现派生类调用同名函数,则调用派生类的同名函数

得出结论:

如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数

那么如何通过派生类调用基类的同名函数呢?

通过添加作用域      类名::

总结:

  • 当子类与父类拥有同名的成员函数,子类会隐意父类中同名成员函教,加作用域可以访问到父类中同名函数
  • 子类对象可以直接访问到子类中同名成员
  • 子类对象加作用域可以访问到父类同名成员

作用域是👉类名::

静态成员:

定义:

简单来说:普通的成员前加上一个static关键字,就被称为静态成员。

当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。

性质:

共享数据 

静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。

编译阶段分配内存

在编译阶段就已经分配好内存了

类内声明类外初始化

静态成员不可以在类内初始化,要在类外初始化,初始化时在成员名加上作用域即可

静态成员函数

在成员函数前加上关键字static,这样就把类的特定对象和该函数独立开

静态函数只要使用类名加范围解析运算符 :: 就可以访问

静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数

静态成员函数与普通成员函数的区别:
  • 静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)
  • 普通成员函数有 this 指针,可以访问类中的任意成员

静态成员访问:

  1. 通过类名访问:类名::父类作用域::成员
  2. 通过对象访问
  3. 同名静态函数或者是变量,父类的所有同名函数包括函数重载会被隐藏,除非加上作用域才可以成功

多继承

子类是否只可以继承一个类,可以进行多个类吗?如果可以继承多个类,多继承的语法是什么?

c++允许子类可以继承多个类

语法为:class 子类  :继承权限 父类1 ,继承权限 父类2,继承权限 父类3……

但是注意:在实际开发过程中,不建议使用多继承语法

菱形继承

什么是菱形继承?

如下图:

这样一种几个类相互有一定的继承关系的看起来像菱形一样的,被称为菱形继承

定义:两个派生类继承同一个基类,又有某个类同时继承者两个派生类,这种继承被称为菱形继承,或者钻石继承

菱形继承会遇到的问题:

由于类4同时继承类2和类3,而类2和类3都继承了1,那么相当于类4继承了两份类1.

如何解决?

使用虚继承的方式,用关键字virtual,可以使派生类不重复继承

在派生类继承的时候在继承权限前加上这个关键字virtual即可

示例:

原理:

虚继承会产生虚基类指针vbptr

该指针指向虚基表,虚基表记录的是通过指针访问公共祖先的数据的偏移量

多态

多态是c++面向对象三大特征之一

多态按字面的意思就是多种形态,具体解释是指:不同对象去完成某一个行为是而产生的不同状态

当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态

多态性提供接口与具体实现之间的隔离,将what和how这两个板块分离开

多态好处:

  1. 组织结构清晰
  2. 可读性强
  3. 对于前期和后期扩展以及维护性高

多态分类:

分为静态多态动态多态(其实静态多态之前就已经涉及了)

静态多态和动态多态区分:

静态多态的函数地址早绑定-编译阶段确定函数地址
动态多态的函数地址晚绑定-运行阶段确定函数地址

静态多态:(静态绑定)

静态多态:函数重载,重定义,运算符重载属于静态多态

静态多态的函数地址早绑定-编译阶段确定函数地址

动态多态:(动态绑定)

动态多态:派生类和虚函数实现运行时的多态

动态多态的函数地址晚绑定-运行阶段确定函数地址

动态多态满足条件:

1.有继承关系

2.子类重写父类虚函数,函数返回值类型,名称,参数列表完全相同

3.使用->父类指针或者引用执行于类对象

下面仅介绍重点——动态多态

动态多态实现:

如何实现动态多态呢?有什么作用?使用场景又是什么?

多态是当不同继承关系的类对象去调用一个函数而产生的不同的行为

实现动态多态的条件

  • 类对象要调用虚函数,且派生类中必须要包含基类虚函数的重写
  • 通过基类的引用/指针去调用虚函数⭐ 

引入:

继承会使子类都含有父类的数据,而每一个子类都对这份数据进行重写,而如果想创建一个函数,使其可以操纵父类所有派生的子类?(即使以后还有派生的子类,也可以操纵)

该怎么做呢?

其实最重要的是函数的参数

参数设定其实需要找到这几个派生类的共性——》它们都有父类的数据

因此如果要实现这样的一个函数,那么就需要其参数为父类的指针或是引用,而要操纵所有子类,这就需要用父类指针来保存子类的空间地址⭐⭐⭐

而用父类指针保存子类地址会造成问题:

代码示例:

#include<iostream>
using namespace std;
class A
{
public:
    void say()
    {
        cout << "A会说话了" << endl;
    }
};

class B : public A
{
public:
    void say()
    {
        cout << "A类里的B会说话了" << endl;
    }
};
class C :public A
{
public:
    void say()
    {
        cout << "A类里的C会说话了" << endl;
    }
};
void test1(A*a)
{
    a->say();//输出的都是 A会说话了
}
int main()
{
    A* a=new B;
    test1(a);
    A* b = new C;
    test1(b);
    return 0;
}

我们发现本身要通过父类的指针来完成子类的重定义函数,但是实际上调用的都是父类的函数,为什么呢?

因为指针指向的地址是由指针指向类型决定的,A*a,这个式子就决定了它要指向父类,而子类中继承了父类的数据,因此a指向子类中的父类,实现的也是父类的函数

如何解决这一个问题呢? 

用虚函数

那什么是虚函数?

被关键字virtual修饰的类成员函数,而且子类中要重写虚函数(可加virtual也可不加)

虚函数的重写?

虚函数的重写(又可以叫做覆盖)👉

派生类中有一个跟基类完全相同的虚函数(这里指它们的返回值类型、函数名字、参数列表完全相同),则称子类的虚函数重写了基类的虚函数,而其中的重写内容可以做适当改变,来实现多态

多态实现代码示例:

#include<iostream>
using namespace std;
class A
{
public:
    virtual void say()
    {
        cout << "A会说话了" << endl;
    }
};

class B : public A
{
public:
    void say()
    {
        cout << "A类里的B会说话了" << endl;
    }
};

void test1(A*a)
{
    a->say();//输出的是A类里的B会说话了
}
int main()
{
    A* a=new B;
    test1(a);
    return 0;
}

这样父类指针就可以调用子类的函数了,解决了父类指针指向子类地址的一个问题 

那为什么变成虚函数后,父类指针可以调用子类中的子类函数而非父类呢?

虚函数的动态绑定机制(面试经常问的)

当一个类中的函数变为虚函数之后,会产生虚函数指针(vfptr),虚函数指向虚函数表(vftable),而如果这个类没有被继承的话——》虚函数表保存的是这个虚函数的入口地址

如果被继承了,子类会把父类中的虚函数指针给继承过来,但是这时候这个虚函数表里的内容就发生变化了,它里面的是子类重写的地址

如图本质上A*a指针还是指向父类地址,但是由于去调用时发现它是一个虚函数指针,而这个指针指向的虚函数表是重写的say函数地址,因此调用的是子类函数。

 重载、重定义、重写的区别

重载:同一作用城,同名函教,参数的顺序,个数,类型不同都可以重载。函数的返回值类型不能作为重载条件(函数重载,运算行重载)
重定义:有继承,子类重定义父亲的同名函数(非虚函数),参数顺序,个数,类型可以不同,子类的同名函数会屏蔽父类的所有同名函数(可以通过作用域解决)
重与(覆盖):有继承,子类重写父类的虚函数。返回值类型,函数名,参数顺序,个数,类型都必须一致。 

而当这样写后,代码只进行子类函数调用,此时的父类虚函数就没有什么用了,那么我们可以不可以找到一种方式去省略掉父类虚函数的内容——可以,用纯虚函数,下面就来介绍什么是纯虚函数?

纯虚函数

在多态中,通常父类中虚函数的实现是毫无意义的主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数

纯虚函数是指在虚函数上加上=0则称为虚函数

例如:

virtual void say ()=0;

而当一个类中由纯虚函数后这个类就成了——抽象类👇 

抽象类:

抽象类是指类中含有纯虚函数的类,抽象类又称为接口类,抽象类不能实例化对象(因为它没有函数体,怎么调用?)

抽象类特点:

无法实例化对象

子类必须重写抽象类中的所有纯虚函数,否则也属于抽象类⭐

其实抽象类主要目的是设计类的接口

也就是说只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,其实这里的纯虚函数存在接口继承

那什么是接口继承呢?

简单来说虚函数的继承——接口继承,派生类并没有继承基类函数,而是基类虚函数的接口,这是一个实现多态的方式,可以达到重写的目的,如果不要求多态实现,函数尽量不要定义为虚函数

那正常的继承叫做什么?

普通函数的继承为——实现继承,派生类它继承了基类函数,继承的是函数的实现,而非接口,派生类可以使用这个接口

多态原理(工具观察):

前面已经将结果多态如何通过虚函数实现的了,现在来使用工具观察多态实现的虚函数表

⭐满足多态以后的函数调用,并不是在编译时确定的,而是运行起来以后到对象中去找的

不满足多态的函数调用是在编译时确认好的。

这里借助vs下的一个工具

输入dir空格后出现目录,然后直接输入以下内容,即可看代码中的类

  • cl(空格)/d1(空格)reportSingleClassLayout(类名)(空格)(文件名)(回车)     

例如在test.cpp文件中,A类的结构:

  • cl /d1 reportSingleClassLayoutA test.cpp

最后就可以看见这个类的内部情况。

⭐回顾:我们要达到多态,有两个条件,一个是虚函数覆盖,一个是基类对象的指针或引用调用虚函数

哎?有人发现没,其实刚才多态实现里的代码有个问题——new出来的空间一直没有释放,堆区空间没释放会发生内存泄漏,其实这里就涉及到另一个知识了——虚析构

这时可能会问,为什么非要用虚析构呢?正常的析构为什么不可以释放掉代码呢?这时假如你写一个代码,就会发现——父类的析构函数正常调用,而子类的析构函数无法调用,也就是我们没办法在子类析构函数中去写代码,释放掉子类的堆区数据。

做法就是在父类的析构前加上一个很熟悉的关键字——virtual

这也就是下面要介绍的虚析构

虚析构

多态使用时,若子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,父类在析构的时候不会调用子类析构,如果子类有堆区属性,则会出现内存泄漏
则需要将父类中的析构函数改为虚析构

语法:

虚析构语法:
virtua1 ~类名()

原理:通过父类指针来释放子类空间

由于刚开始实现多态的时候,子类的空间是由父类指针储存的,因此只能通过父类指针来释放子类空间。

已知的是

构造是:父类——》成员——》子类

析构是:子类——》成员——父类

析构函数本身也是个成员函数,虚析构,产生虚函数指针,指向虚函数表:包含这个类的析构函数 

纯虚析构

特点:

纯虚析构的本质:是析构函数,完成各个类的回收工作。
必须为纯虚析构函数提供一个函数体
而且纯虚析构函数必须在类外实现

含有纯虚析构的类也是抽象类

语法:

virtual ~类名()=0;
在类外:类名::~类名() 

纯虚析构和虚析构的共性与区别:

虚析构和纯虚析构共性:

可以解决父类指针释放子类对象
都需要有具体的函数实现


虚析构和纯虚析构区别:

如果是纯虚析构,该类属于抽象类,且无法实例化对象,需要在类外实现

总结:

  • 1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
  • 2.如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
  • 3.拥有纯虚析构函数的类也属于抽象类

总结:

c++的入门核心内容基本介绍完毕,整理的有关c++的文件操作以及内存分区放在下面了,可以按需观看

c++文件操作-CSDN博客

c++内存的四大分区详解-CSDN博客

下一阶段是对于c++的模板,欢迎点赞收藏关注主页专栏o(* ̄▽ ̄*)ブ

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

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

相关文章

超级详细的python考核试题及答案

一、选择题&#xff08;每题2分&#xff0c;共20分&#xff09; 1&#xff0e;下列哪个语句在Python中是非法的&#xff1f; &#xff08;B&#xff09; A、x y z 1 B、x (y z 1) C、x, y y, x D、x y??xxy 2&#xff0e;关于Python内存管理&#xff0c;下列说法…

创意办公:专注 ONLYOFFICE,探索办公新境界

一.ONLYOFFICE 介绍 ONLYOFFICE 是一个基于 Web 的办公套件&#xff0c;提供了文档处理、电子表格和演示文稿编辑等功能。它被设计为一个协作工具&#xff0c;支持多人实时协作编辑文档&#xff0c;并且可以在本地部署或者作为云服务使用。 二.ONLYOFFICE 特点和功能 以下是 …

机器学习——强化学习作业

作业内容 成功降落在两个黄色旗子中间为成功&#xff0c;其他为失败 Policy Gradient方法 Actor-Critic方法 范例结果 baseline Policy Gradient实现

【Java系列】JDK

目录 JDK介绍JDK版本系列文章版本记录JDK介绍 JDK是 Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的java应用程序。JDK是整个java开发的核心,它包含了JAVA的运行环境(JVM+Java系统类库)和JAVA工具。 JDK版本 SE(JavaSE),standard edition,标准版,是我们通…

AOSP10 替换系统launcher

本文实现将原生的launcher 移除&#xff0c;替换成我们自己写的launcher。 分以下几个步骤&#xff1a; 一、新建一个自己的launcher项目。 1.直接使用android studio 新建一个项目。 2.修改AndroidManifest.xml <applicationandroid:persistent"true"androi…

TSINGSEE青犀AI智能分析网关V4初始配置与算法相关配置介绍

TSINGSEE青犀AI智能分析网关V4内置了近40种AI算法模型&#xff0c;支持对接入的视频图像进行人、车、物、行为等实时检测分析&#xff0c;上报识别结果&#xff0c;并能进行语音告警播放。硬件管理平台支持RTSP、GB28181协议、以及厂家私有协议接入&#xff0c;可兼容市面上常见…

Java的String类

目录 String类的常用方法 1.1 字符串构造 1.2 String对象的比较 1.3 字符串查找 1.4 转换 1.5 字符串替换 1.6字符串拆分 1.7 字符串截取 1.8 其他操作方法 1.9 字符串的不可变性 1.10 字符串修改 String类的常用方法 1.1 字符串构造 String类常用的构造方法有很多…

基于springboot+vue的B2B平台的购物推荐网站(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

基于Java (spring-boot)的社区物业管理系统

一、项目介绍 本系统共分为两个角色&#xff1a;管理员和业主。 主要功能有&#xff0c;核心业务处理&#xff0c;基础信息管理&#xff0c;数据统计分析 核心业务处理&#xff1a;车位收费管理&#xff0c;物业收费管理&#xff0c;投诉信息管理&#xff0c;保修信息管理。 …

Vue3之生命周期基础介绍

让我为大家介绍一下vue3的生命周期吧&#xff01; 创建阶段&#xff1a;setup 我们直接console.log就可以了 console.log("创建");挂载阶段&#xff1a;onBeforeMount(挂载前)、onMounted(挂载完毕) import { onBeforeMount, onMounted } from vue; // 挂载前 on…

开源模型应用落地-工具使用篇-向量数据库(三)

一、前言 通过学习"开源模型应用落地"系列文章&#xff0c;我们成功地建立了一个完整可实施的AI交付流程。现在&#xff0c;我们要引入向量数据库&#xff0c;作为我们AI服务的二级缓存。本文将详细介绍如何使用Milvus Lite来为我们的AI服务部署一个前置缓存。 二、术…

Git笔记——1

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 Git安装_centos 创建本地仓库 配置本地仓库 添加文件——场景一 查看.git文件 添加文件——场景二 修改文件 版本回退 总结 前言 世上有两种耀眼的光芒&#…

【复现】CVE-2024-0939 smart管理平台漏洞_55

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 百卓Smart S85F是Smart系列上网行为管理设备&#xff0c;包括网络应用封堵、流量控制、链路负载均衡、网页分类阻断、上网内容审计…

Vue | (三)使用Vue脚手架(下)| 尚硅谷Vue2.0+Vue3.0全套教程

文章目录 &#x1f4da;Vue 中的自定义事件&#x1f407;使用方法&#x1f407;案例练习&#x1f407;TodoList案例优化 &#x1f4da;全局事件总线&#x1f407;使用方法&#x1f407;案例练习&#x1f407;TodoList案例优化 &#x1f4da;消息订阅与发布&#x1f407;使用方法…

error Error: certificate has expired

解决方案&#xff1a; yarn config set "strict-ssl" false -g 我开发的chatgpt网站&#xff1a; https://chat.xutongbao.top

npm run serve启动报错npm ERR! Missing script: “serve“

启动项目的时候用npm run serve发现报了以下的错误 解决方法&#xff1a; 1.一般情况下&#xff0c;这个问题是因为package.json文件里面确实没有 这里没有可能因为你的脚手架版本比较低&#xff0c;如果不想换&#xff0c;可以用 这里面有的 npm run dev去启动也是可以的 n…

centos 9 编译安装 LAMP wordpress

[rootlocalhost ~]# ll 总用量 655760 -rw-------. 1 root root 1040 2月 17 16:57 anaconda-ks.cfg drwxr-xr-x. 29 501 games 4096 2月 21 11:00 apr-1.7.4 -rw-r--r--. 1 root root 1122147 2月 21 10:57 apr-1.7.4.tar.gz drwxr-xr-x. 21 501 games …

微前端(qiankun,webpack5模块联邦)

1singleSpa vue 出现这个错误是因为 node.js V17版本中最近发布的OpenSSL3.0, 而OpenSSL3.0对允许算法和密钥大小增加了严格的限制&#xff0c;可能会对生态系统造成一些影响. 临时方案 export NODE_OPTIONS--openssl-legacy-provider 总结 子应用 子应用独立运行 判断是…

嵌入式系统在智慧城市建设中的关键角色与挑战

&#xff08;本文为简单介绍&#xff0c;观点源于网络&#xff09; 智慧城市的概念&#xff0c;随着信息技术的日益发展而不断深化。它利用各种信息传感器&#xff0c;通过物联网、云计算、大数据等技术手段&#xff0c;实现城市管理的智能化、精细化。在这一过程中&#xff0…

【C#】List泛型数据集如何循环移动,最后一位移动到第一位,以此类推

欢迎来到《小5讲堂》 大家好&#xff0c;我是全栈小5。 这是《C#》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识点的理解和掌握。…