c++ 继承多态详解

第一节:继承:

1,相关概念

父类,基类。子类,派生类
(1)基类的私有成员,派生类不可访问
(2)基类中被保护的成员再子类中可以被访问,但是在类外不可以访问
(3)基类其他成员在子类中的访问方式为(成员在基类的访问限定符和继承方式,取小值)
(4)如果继承方式不写的话,class默认为私有继承,struck默认为共有继承

(5)继承的时候“类名:继承方式+父类名”

class person {};
class student :public person {};

(6)子类对象赋值给父类对象/父类指针/父类的引用。我们认为是天然的,中间不产生临时对象,这个叫做父子类赋值兼容规则(切割/切片)。也就是说,rp是子类研究对象中父类那一部分的别名。

student s;
person p = s;
person& rp = s;

父类和子类可以有同名成员,默认访问的是子类的,子类同名成员隐藏了父类的同名成员,如果想要访问父类的成员变量,就要在成员变量前面加类域(person1::_num )。

同名的成员函数就构成隐藏,参数和返回值都不重要!!

——————————————————————————————————

2,子类如何继承父类

class Person {
public:
    Person(string str="zhangsan",int age=18) {
        _name = str;
        _age = age;
        cout << "Person()" << endl;
    }

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

    Person& operator=(const Person& per) {
        if (&per != this) {
            _name = per._name;
            _age = per._age;

        }
        cout << " Person& operator=(const Person& per)" << endl;
        return *this;
    }

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

    
protected:
    string _name;
    int _age;

};
class Student :public Person {
public:
    Student(const char* name,int id)
        :Person(name)
    {
        cout << "Student(char* name,int id)" << endl;
    }

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

    Student& operator=(const Student& stu) {
        if (&stu != this) {
            _id = stu._id;
            Person::operator=(stu);
        }
        cout << "Student& operator=(const Student& stu)" << endl;
        return *this;
    }

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

    
protected:
    int _id;
};这里需要注意观察,子类如何调用父类的相关函数
————————————————————————————————
    Student s1("lihua", 1001)

Student s2(s1);

 Student s1("lihua", 1001);

 Student s3("zhangsan",2002);
 s3 = s1;

由以上三个例子可知:构造函数,拷贝构造,运算符重载都是先调父类,再调子类,但是析构函数除外,时先调子类,再调父类(父子类析构函数会构成隐藏,由于多态的原因,析构函数统一被处理成destructor)

为了保证先子后父,子类析构函数完成后会自动调用父类的析构函数从而实现先子后父。原因:难免子类的析构函数会访问父类的成员变量,如果先析构了父类,就会越界。

——————————————————————————————

3,如何实现一个不能被继承的类?

(1)构造函数私有化

(2)c++11新增了一个关键字,final

——————————————————————————————

(1)友元关系不能被继承

(2)基类定义了静态成员变量,则整个继承关系中只有这样一个这个样的成员

(3)c++允许多继承,之间用逗号隔开,但是多继承回引发菱形继承

4,菱形继承

class A {
public:
    int _a;
};
class B:public A {
public:
    int _a;
};

class C :public A {
public:
    int _c;
};
class D :public B, public C{
public:
    int _d;
};

——————————————————————————

缺点:

(1)这样会有二义性(继承了两份的名字和年龄),无法明确知道访问的是哪一个,

(2)数据冗余

为了解决这个问题,引入了虚拟继承,用关键字virtual,加在继承同一个类的两个类前面。这样一来,将父类A的成员变量放到另一块空间里,B,C类里面会有一个指针,指向一个表,表里会存放父类A的成员变量的位置信息,这个表被称为“虚基表”,这样子类在实例化对象时就只有一份父类(A类)的成员变量了。

二,多态

 1,多态的条件

(1)虚函数重写
(2)父类指针/引用,调用虚函数(引用没有开空间,只是子类中父类部分的别名)

void func(person& p) {p.print();}//引用
void func(person* p) { p->print();}//指针

2,分类:

(1)静态多态,函数重载
(2)动态多态,虚函数重写,父类指针/引用调用虚函数

3,虚函数

(1)虚函数和虚继承没有关系

(2)虚函数的重写:继承关系中父子的两个虚函数,三同(函数名/参数/返回)

(3)virtual只能修饰成员函数

(4)三同(函数名/参数/返回)返回值可以不同,但必须是父子类关系的指针或者引用,

(5)派生类重写虚函数可以不加virtual(建议都加上)

(6)普通函数的继承时实现继承,虚函数的继承,是一种接口继承,继承的是接口

4,调用方式

(1)普通调用:调用的函数类型是谁,就调用这个类型的函数
(2)多态调用:调用指针或者引用指向的对象,指向父类调用父类函数,指向子类调用子类函数

5,建议析构函数运用多态的原因

编译器把析构函数特殊处理成distructor,从而构成重名,完成虚函数重写。为什么要写成虚函数,清理派生类,防止内存泄漏。


6,小试牛刀:

会输出什么:

答:B->1

当调用test()时,调用的是父类的test函数,所以test()这里的隐形参数是A* this(父类指针),子类对象传给了父类指针,然后调用func(),发生多态,会调用子类的func(),多态调用是接口继承,派生类是重写父类的函数的内容,但用的还是父类的接口,所以子类的缺省参数还是1,所以输出B->1

7,重载,重写,隐藏区别:

重载:作用域同一作用域,参数不同
重写(覆盖)两个函数分别在基类和派生类(三同)虚函数
隐藏(重定义)两个函数分别在基类和派生类,函数名相同,不构成重写

8,其他关键字

(1)final可以修饰类,不能被继承,修饰虚函数,不能被重写

(2)override,修饰派生类的虚函数,检查是否完成重写

9,虚函数,纯虚函数区别:

虚函数:成员函数前添加 `virtual` 关键字,可以直接使用或被子类重载实现后以多态形式调用。
纯虚函数:在虚函数后添加 `=0`,必须被子类重载实现后才能以多态形式调用,基类中只有声明。

10,抽象类

(1)抽象类:无法实例化对象
(2)包括纯虚函数的类称为抽象类,如果父类包含纯虚函数,那么他的派生类也不能实例化对象
除非将纯虚函数进行重写
 

class car {
public:
    virtual void drive()= 0 ;
};

class BenZ :public car{
public:
    virtual void drive() {
        cout << " BenZ" << endl;
    }

};

class BMW :public car {
public:
    virtual void drive() {
        cout << " BMW" << endl;
    }

};

——————————————————————————————

11,含有虚函数类的大小

class M {
private:
    int _b = 1;
    char _ch;
public:
    virtual void func() {
        cout << "func()" << endl;
    }

   cout << sizeof(M) << endl;在32位的计算机上占12字节
原因:会把虚函数地址存到虚函数表中,所以这个类会有一个指针,指向虚函数表(称为虚函数表指针)所以出了一个int和通过内存对齐的char型,还有一个4个字节的指针,所以一共12字节。
注意:编译好后,虚函数在代码段,虚函数表在代码段(常量区)

问:指针和引用,是指向子类中切割出来的父类那一部分,那么对象为什么不可以接收?

答:切割出子类中父类那一部分,成员拷贝给父类,但是不会拷贝虚函数表指针

12,多态的原理:(画图)

父类对象为Person,里面有两个成员函数,分别为print和func,均为虚函数。子类为student类,有成员变量_a,这里初始化为了1,里面有一个成员函数,为print,且print进行了重写。

我们查看父类对象和子类对象的内存情况,父类对象中含有一个指针(虚表),我们打开指针所指的空间,发现有两个指针分别对应print和func,子类对象中也含有一个指针(虚表),我们打开指针所指的空间,发现有两个指针分别对应print和func,func是继承的父类的函数。而且func没有进行重写,所以子类中指向func和父类指向func为同一地址(同一函数),但是print进行了重写,所以,所以子类中指向print和父类指向print为不同地址。所以子类传给父类的引用调用的是覆盖后的print函数。

注意:一个类可以有多个对象,但是只有一张虚表

13,打印虚表中的函数

(1)单继承

class A1 {
public:
    virtual void func1() { cout << "A::func1" << endl;}
    virtual void func2() { cout << "A::func2" << endl; }

};
class B1 :public A1 {
public:
    virtual void func1() {cout << "B::func1" << endl; }
    virtual void func3() { cout << "B::func3" << endl; }
    virtual void func4() { cout << "B::func4" << endl; }

};
class C1 :public B1 {
public:
    virtual void func3() { cout << "C::func3" << endl; }
};

————————————————————————

typedef void (*VFUNC)();函数指针的类型
void printVFT(VFUNC* a) {
    for (size_t i = 0; a[i]!=0 ; i++)
    {
        printf("[%d]:%p->", i, a[i]);
        VFUNC f = a[i];
        f();//打印出函数地址对应的函数
    }
    printf("\n");
}
int main() {
    A1 a;
    printVFT((VFUNC*)(*((int*)&a)));
    B1 b;
    printVFT((VFUNC*)(*((int*)&b)));
    C1 c;
    printVFT((VFUNC*)(*((int*)&c)));将对象取地址,只取对象前四个字节的地址,因为前四个字节储存的为虚表的地址。所以转型为(int*)再解引用,再转为函数数组指针的类型。然后进行遍历

——————————————————————

(2)多继承

class A1 {
public:
    virtual void func1() {cout << "A::func1" << endl;}
    virtual void func2() { cout << "A::func2" << endl; }

};
class B1 {
public:
    virtual void func1() { cout << "B::func1" << endl; }
    virtual void func2() { cout << "B::func3" << endl; }

protected:
    int _b;
};
class C1 :public A1,public B1 {
public:
    virtual void func3() { cout << "C::func3" << endl; }
    virtual void func1() { cout << "C::func1" << endl; }

protected:
    int _c;
};

————————————————————————
typedef void (*VFUNC)();
void printVFT(VFUNC* a) {
    for (size_t i = 0; a[i] != 0; i++)
    {
        printf("[%d]:%p->", i, a[i]);
        VFUNC f = a[i];
        f();
    }
    printf("\n");
}

C1 c;

printVFT((VFUNC*)(*((int*)&c)));
B1* p2 = &c;
printVFT((VFUNC*)(*(int*)p2));

我们可以清楚地看到,A1和B1的虚表存放着相同的func1,但是他们的地址却不一样,下面我们来分析原因:

如图所示:我们分别用p1和p2去调用func1,p2比p1多跳转了几步最终访问到了同一个func1,由此可以他们指向的是同一个函数,只不过走的路径不同

(3)菱形继承(虚拟继承)(这里简单说明)

他们的父类A,单独放到了下方的空间(只有一份),B和C里面都有两份指针,第一个是虚函数表,第二个是虚基表,第一个存放虚函数的地址,第二个存放父类A类中的成员变量距这里有多少个字节。

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

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

相关文章

计算机毕业设计Hadoop+Spark+Hive知识图谱租房推荐系统 租房数据分析 租房爬虫 租房可视化 租房大数据 大数据毕业设计 大数据毕设 机器学习

毕 业 设 计&#xff08;论 文&#xff09; 基于大数据的租房数据爬虫与推荐分析系统 姓 名 学 院 专 业 班 级 指导教师 摘 要 本设计是一个基于爬虫技术的房地产数据采集与可视化分析应用程序。该程序首先通过爬虫采集网上所有房地产的房源数据…

华为坤灵交换机S300, S500, S210,S220, S200, S310 如何WEB抓包

通过S系列交换机配置端口镜像实现抓包 1、应用场景 端口镜像是指将经过指定端口(源端口或者镜像端口)的报文复制一份到另一个指定端口(目的端口或者观察端口)。在网络运营与维护的过程中&#xff0c;为了便于业务监测和故障定位&#xff0c;网络管理员时常要获取设备上的业务报…

数据分析常用模型合集(二)RARRA模型、RFM模型

随着互联网的发展&#xff0c;前期平台的砸钱拉新、抢占市场&#xff0c;大家都叫AARRR小甜甜&#xff1b; 现在市场基本抢占得差不多&#xff0c;形成了一个平衡&#xff0c;新人基本拉不到多少&#xff0c;用户都知道干什么事有哪些平台&#xff0c;比如买东西主流淘宝、京东…

kotlin1.8.10问题导致gson报错TypeToken type argument must not contain a type variable

书接上回&#xff0c;https://blog.csdn.net/jzlhll123/article/details/139302991。 之前我发现gson报错后&#xff1a; gson在2.11.0给我的kotlin项目代码报错了。 IllegalArgumentException: TypeToken type argument must not contain a type variable 上次解释原因是因为&…

金钱的认知,你如何理解呢?

金钱的认知 建立在金钱之上的爱情是纯真的爱&#xff0c;朋友关系也才够纯粹&#xff0c;才是单纯的世界&#xff0c;反之没了钱的条件爱情和友情的美好关系极易破碎&#xff0c;也极易反目成仇。 心若美好钱就美好&#xff0c;心有欲望狰狞钱就是只咬人的老虎&#xff0c;钱…

量子加速超级计算简介

本文转载自&#xff1a;量子加速超级计算简介(2024年 3月 13日) By Mark Wolf https://developer.nvidia.cn/zh-cn/blog/an-introduction-to-quantum-accelerated-supercomputing/ 文章目录 一、概述二、量子计算机的构建块&#xff1a;QPU 和量子位三、量子计算硬件和算法四、…

3DMAX建筑生长动画插件PolyFX安装使用方法

3DMAX建筑生长动画插件PolyFX安装使用教程 PolyFX插件是一个功能强大的工具&#xff0c;它可以将对象分解为片段并根据需要设置动画。它有许多用于微调动画的选项和一些附加工具。这是制作宣传视频、游戏开发等的绝佳解决方案。 【版本要求】 3ds max 2010-2025&#xff08;不…

探究MySQL中的“树”结构

1 引言 树高千丈,叶落求索 – 唐代杜牧 树结构在MySQL中常用于表示层次关系,如组织结构或分类体系。引入树结构可使数据之间建立父子关系,便于查询和管理。益处包括快速检索子节点、方便展示层次关系、支持递归查询等。 2 基础概念 2.1 名词解析 程序就像是一张有向图,你…

Pipecat: 创建语音对话agent的开源框架,支持多模态!

项目简介 pipecat 是用于构建语音&#xff08;和多模态&#xff09;对话代理的框架。诸如私人教练、会议助理、儿童讲故事玩具、客户支持机器人、摄入流程和尖刻的社交伙伴。 看看一些示例应用&#xff1a; 语音代理入门 您可以开始在本地计算机上运行 Pipecat&#xff0c;然…

ES6-03-模版字符串、对象的简化写法

一、模版字符串 1-1、声明 反引号。 1-2、特性 1、字符串内容可以直接换行 得用号连接 2、变量拼接 现在&#xff1a; 二、对象的简化写法 ES6允许在大括号里面&#xff0c;直接写入变量和函数&#xff0c;作为对象的属性和方法。 let name milk;let chage function(){con…

【pip安装】YOLOv8目标检测初步上手

说明&#xff1a;本篇blog是关于Ultralytics官方教程的学习笔记&#xff0c;环境为windowsconda 1、下载安装YOLOv8 1.1 YOLOv8介绍 Ultralytics YOLOv8 是一个尖端的、最先进的&#xff08;SOTA&#xff09;模型&#xff0c;它建立在以前 YOLO 版本的成功基础之上&#xff0…

使用System-Verilog实现FPGA基于DE2-115开发板驱动HC_SR04超声波测距模块|集成蜂鸣器,led和vga提示功能

文章目录 前言一、实验原理1.1 传感器概述&#xff1a;1.2 传感器引脚1.3 传感器工作原理1.4 整体测距原理及编写思路 二、System-Verilog文件2.1 时钟分频&#xff08;1&#xff09;clk_div.sv2.2 超声波测距&#xff08;1&#xff09;hc_sr_trig.sv&#xff08;2&#xff09;…

简单聊聊分布式系统和微服务

分布式系统是由多个独立的计算机节点通过网络相互连接协作&#xff0c;共同完成一项或多项任务的系统。这些节点可以是服务器、个人电脑、移动设备等&#xff0c;它们之间通过消息传递或共享数据来协调工作&#xff0c;每个节点负责系统整体功能的一部分。分布式系统的关键在于…

k8s学习--k8s集群使用容器镜像仓库Harbor

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 环境 步骤一 容器镜像仓库Harbor部署二、Kubernetes集群使用harbor仓库 环境 Ip主机名cpu内存硬盘192.168.10.11master1cpu双核2G40G192.168.10.12node011cpu双核2…

网络链路层

data: 2024/5/25 14:02:20 周六 limou3434 叠甲&#xff1a;以下文章主要是依靠我的实际编码学习中总结出来的经验之谈&#xff0c;求逻辑自洽&#xff0c;不能百分百保证正确&#xff0c;有错误、未定义、不合适的内容请尽情指出&#xff01; 文章目录 1.协议结构2.封装分离3.…

计算机毕业设计 | SpringBoot 房屋销售租赁平台 房屋购物网站(附源码)

1&#xff0c;绪论 1.1 背景调研 在房地产行业持续火热的当今环境下&#xff0c;房地产行业和互联网行业协同发展&#xff0c;互相促进融合已经成为一种趋势和潮流。本项目实现了在线房产平台的功能&#xff0c;多种技术的灵活运用使得项目具备很好的用户体验感。 这个项目的…

Python自动化识别与删除Excel表格空白行和列

在处理Excel数据时&#xff0c;经常会遇到含有空白行和空白列的情况。这些空白区域不仅占用表格显示空间&#xff0c;还可能导致数据分析时出现偏差&#xff0c;影响数据处理的效率与结果的准确性&#xff0c;如空白行可能干扰数据聚合操作&#xff0c;导致统计计数不准确&…

【嵌入式DIY实例】-OLED显示天气数据

OLED显示天气数据 文章目录 OLED显示天气数据1、硬件准备与接线2、天气数据获取准备3、代码实现在这个物联网项目中,本文将展示如何使用 ESP8266 NodeMCU (ESP-12E) Wi-Fi 开发板和 SSD1306 OLED 显示屏(12864 像素)制作一个简单的互联网气象站。 NodeMCU 从天气网站 openwe…

牛客网刷题 | BC114 圣诞树 (不理解)

目前主要分为三个专栏&#xff0c;后续还会添加&#xff1a; 专栏如下&#xff1a; C语言刷题解析 C语言系列文章 我的成长经历 感谢阅读&#xff01; 初来乍到&#xff0c;如有错误请指出&#xff0c;感谢&#xff01; 这道题没搞懂 也没找到视…

软件定义汽车,通信连接世界 | 2024汽车软件与通信大会开幕

5月28日-30日&#xff0c;在江苏省工业和信息化厅、智能汽车安全技术全国重点实验室指导下&#xff0c;由中国中检所属中国汽车工程研究院股份有限公司&#xff08;下称&#xff1a;中国汽研&#xff09;主办&#xff0c;中汽院(江苏)汽车工程研究院有限公司承办的2024汽车软件…