【C++】学习笔记——多态_1

文章目录

  • 十二、继承
    • 8. 继承和组合
  • 十三、多态
    • 1. 多态的概念
    • 2. 多态的定义和实现
      • 虚函数重写的两个特殊情况
      • override 和 final
    • 3. 多态的原理
      • 1. 虚函数表
  • 未完待续


十二、继承

8. 继承和组合

我们已经知道了什么是继承,那组合又是什么?下面这种情况就是 组合

class A
{//
};class B
{
private:A _a;
};

组合和继承都是让代码复用,但是继承的复用是一种 白箱复用 ,父类的内部细节是对子类透明的,根透明箱子一样。而组合的复用是一种 黑箱复用 ,因为对象的内部细节是不可见的。
继承一定程度破坏了父类的封装,父类的改变,对子类有很大的影响。子类和父类间的依赖关系很强,耦合度高组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于保持每个类被封装

优先使用对象组合,而不是继承。

public继承是一种 is-a 的关系。也就是说每个子类对象都是一个父类对象。
组合是一种 has-a 的关系。假设B组合了A,每个B对象中都有一个A对象。

十三、多态

1. 多态的概念

多态 通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成某个行为时会产生出不同的状态 。举个栗子:比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票。

2. 多态的定义和实现

我们先实现一下多态,来尝尝鲜:

#include<iostream>
using namespace std;class Person
{
public:virtual void BuyTicket(){cout << "买票-全价" << endl;}
};class Student : public Person
{
public:virtual void BuyTicket(){cout << "买票-半价" << endl;}
};// 多态
void Func(Person& p)
{p.BuyTicket();
}int main()
{Person ps;Student st;Func(ps);// 子类可以赋值给父类---切片Func(st);return 0;
}

在这里插入图片描述
在继承中想要构成多态是有条件的。

1. 必须通过父类的指针或者引用调用虚函数。
2. 被调用的函数必须是 虚函数 ,且子类必须对父类的虚函数进行重写。

虚函数的重写(覆盖/隐藏):子类中有一个跟父类完全相同的虚函数(即子类虚函数与父类虚函数的 返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了父类的虚函数。(实际上父类的虚函数可以被子类继承,所以只要父类写上 virtual ,子类即使不写 virtual 也能构成重写)

关于重写:重写是重写的 实现仅仅会改变实现方式,声明并不会改变

虚函数重写的两个特殊情况

协变
在虚函数重写时,父类和子类的虚函数返回类型可以不同,但要求返回类型必须是父子类关系的指针和引用,则称为 协变

#include<iostream>
using namespace std;class A {};
class B : public A {};class Person
{
public:// 虚函数重写,返回类型是对应的指针或引用virtual A* f(){cout << "A::f()" << endl;return new A;}
};class Student : public Person
{
public:// 虚函数重写,返回类型是对应的指针或引用virtual B* f(){cout << "B::f()" << endl;return new B;}
};int main()
{Person* p = new Student;p->f();return 0;
}

在这里插入图片描述
当返回类型是对应的指针或引用时成功实现多态,当返回类型不是时:

#include<iostream>
using namespace std;class A {};
class B : public A {};class Person
{
public:// 返回类型不同且不说相应的指针或引用virtual A f(){cout << "A::f()" << endl;return *new A;}
};class Student : public Person
{
public:// 返回类型不同且不说相应的指针或引用virtual B f(){cout << "B::f()" << endl;return *new B;}
};int main()
{Person* p = new Student;p->f();return 0;
}

在这里插入图片描述
析构函数的重写
如果父类的析构函数为虚函数,此时子类析构函数只要定义,无论是否加 virtual 关键字,都与父类的析构函数构成重写。原因是编译器对析构函数的名称做了特殊处理,编译后所以析构函数的名称统一处理成 destructor

当父类的析构函数不是虚函数时,如下情况则会:

#include<iostream>
using namespace std;class Person
{
public:~Person(){cout << "~Person()" << endl;}
};class Student : public Person
{
public:~Student(){cout << "~Student()" << endl;}
};int main()
{// 父类指针指向父类对象Person* p1 = new Person;// 父类指针指向子类对象Person* p2 = new Student;delete p1;cout << endl;delete p2;return 0;
}

在这里插入图片描述
没能成功进行多态调用,访问的还是父类的析构函数。当父类的析构函数是虚函数时:

#include<iostream>
using namespace std;class Person
{
public:virtual ~Person(){cout << "~Person()" << endl;}
};class Student : public Person
{
public:// 子类可以不写 virtual ,自动构成虚函数重写~Student(){cout << "~Student()" << endl;}
};
// 只有派生类Student的析构函数重写了Person的析构函数
//下面的delete对象调用析构函数,才能构成多态
//才能保证p1和p2指向的对象正确的调用析构函数
int main()
{// 父类指针指向父类对象Person* p1 = new Person;// 父类指针指向子类对象Person* p2 = new Student;delete p1;cout << endl;delete p2;return 0;
}

在这里插入图片描述
成功构成多态调用。我们怎么分辨 普通调用多态调用 呢?

普通调用 看指针或引用或者对象的类型
多态调用 看指针或引用指向的对象

在这里插入图片描述

override 和 final

如果我们想实现一个类,使其不能被继承,应该怎么做?方法一:将父类的构造函数私有化,由于子类的构造函数必须调用父类的构造函数,所以父类的构造函数私有化会导致子类无法实例出对象。方法二:使用关键字 final

// 父类增加关键词 final
class A final
{//
};class B : public A
{//
};

在这里插入图片描述

final 还可以修饰虚函数,表示该虚函数不能再被重写。

class Car
{
public:virtual void Drive() final{//}
};class Benz :public Car
{
public:virtual void Drive(){cout << "Benz-舒适" << endl;}
};

在这里插入图片描述
override 可以检查子类虚函数是否重写了父类某个虚函数,如果没有重写则编译报错。

class Car
{
public:void Drive(){//}
};class Benz :public Car
{
public:// override 写在子类后面virtual void Drive() override{cout << "Benz-舒适" << endl;}
};

在这里插入图片描述

3. 多态的原理

1. 虚函数表

这里常考一道笔试题:sizeof(Base)是多少?

class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};int main()
{Base bb;cout << sizeof(Base) << endl;return 0;
}

在这里插入图片描述
答案是:8;原因是,int 占 4 个字节,而只要类里面有虚函数,类就会在内部 额外生成一个指针 ,指针指向函数指针数组,函数指针数组里存的都是虚函数的地址,称为 虚函数表 。指针占 4 个字节,故答案是 8 。
在这里插入图片描述
对于上面的代码,我们再进行改造一下:

#include<iostream>
using namespace std;class Base
{
public:// 虚函数virtual void Func1(){cout << "Base::Func1()" << endl;}// 虚函数virtual void Func2(){cout << "Base::Func2()" << endl;}// 普通函数void Func3(){cout << "Base::Func3()" << endl;}
private:int _b = 1;
};class Derive : public Base
{
public:// 虚函数重写virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};int main()
{Base b;Derive d;return 0;
}

在这里插入图片描述
我们发现,父类b对象和子类d对象虚函数表是不一样的,这里我们发现Func1完成了重写,所以d的虚函数表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚函数表中虚函数的覆盖。b对象的虚函数表先拷贝一份父类的虚函数表,然后子类重写的函数覆盖进b对象的虚函数表。重写是语法的叫法,覆盖是原理层的叫法。Func3由于不是虚函数,所以没有进入虚函数表。
运行时是通过本身的父类虚函数表或者切片的父类虚函数表(自己的)找到相应的虚函数,不同的对象虚函数表不同,因此实现多态。


未完待续

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

echarts学习文档

echarts学习文档 基础概念初始化样式&#xff08;颜色&#xff09;数据集(dataset&#xff09;数据转换(数据转换&#xff08;transform&#xff09; 基础概念 项目里使用npm安装echarts依赖包 npm install echarts在要使用的地方引入 import * as echarts from echarts初始…

建模:3dmax

3Dmax 制作模型和动画&#xff08;橘肉&#xff09;&#xff1b; RizomUV 对模型进行展UV&#xff08;橘皮&#xff09;&#xff1b; Substance Painter 纹理手绘&#xff08;给橘皮制定想要的皮肤&#xff09;&#xff1b; 1.基础 1.1可编辑多边形、可编辑样条线 体、面都需要…

2024最新Kali Linux安装教程(非常详细)从零基础入门到精通(附安装包)!

什么是Kali Linux&#xff1f; Kali Linux是一个高级渗透测试和安全审计Linux发行版&#xff0c;其功能非常强大&#xff0c;能够进行信息取证、渗透测试、攻击WPA / WPA2保护的无线网络、离线破解哈希密码、将android、Java、C编写的程序反编译成代码等等&#xff0c;是黑客的…

解锁Spring Boot数据映射新利器:深度探索MapperStruct

解锁Spring Boot数据映射新利器&#xff1a;深度探索MapperStruct MapperStruct 是一个强大的 Java 映射工具&#xff0c;它的主要作用是简化对象之间的映射操作。在 Spring Boot 应用程序中&#xff0c;MapperStruct 通常用于将领域模型对象&#xff08;Domain Model&#xff…

Go Gin使用JWT实现认证机制

什么是JWT JWT是JSON Web Token的缩写,是一种跨域认证的解决方案。 使用JWT解决什么问题 传统的登录认证的实现,依赖客户端浏览器的cookie和服务器的session,这种实现登录的方式有很大的局限性。 对于部署在单台服务器的应用来说,使用cookie+session登录认证的方案尚…

strcpy函数详解

strcpy函数详解 1.函数简介2.strcpy函数的使用2.1使用方法一2.1使用方法二 3.strcpy在使用过程中的注意事项3.1被复制字符必须以\0结尾3.2目标空间必须能够大于源字符串长度3.3目标空间必须可变 1.函数简介 strcpy函数包含在<string.h>库函数中&#xff0c;是将一个字符…

【C语言】strcmp函数讲解

文章目录 strcmp函数&#xff1a;例1&#xff1a;str2前6个元素和str1一样&#xff0c;多了一个G。例2&#xff1a;第3个字母不同&#xff0c;str2元素也比str1多个G。例3&#xff1a;第3个字母不同&#xff0c;str2元素也比str1少个f。例4&#xff1a;第3个字母不同&#xff0…

Spring 各版本发布时间与区别

版本版本特性Spring Framework 1.01. 所有代码都在一个项目中 2. 支持核心功能IoC、AOP 3. 内置支持Hibernate、iBatis等第三方框架 4. 对第三方技术简单封装。如&#xff1a;JDBC、Mail、事务等 5. 只支持XML配置方式。6.主要通过 XML 配置文件来管理对象和依赖关系&#xff0…

Linux 第三十三章

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C&#xff0c;linux &#x1f525;座右铭&#xff1a;“不要等到什么都没有了…

HCIP【VLAN综合实验】

目录 一、实验拓扑图&#xff1a; 二、实验要求&#xff1a; 三、实验思路&#xff1a; 四、实验步骤&#xff1a; 1、在交换机SW1,SW2,SW3配置VLAN和各个接口对应类型的配置 2、在路由器上面配置DHCP服务 一、实验拓扑图&#xff1a; 二、实验要求&#xff1a; 1、PC1 …

linux Docker在线/离线服务安装并支持centos7和centos8系统

注&#xff1a;以下内容都是经过测试;能在生产环境使用. 一、centos7版本的docker在线安装 1&#xff1a;运行以下命令&#xff0c;下载docker-ce的yum源。 sudo wget -O /etc/yum.repos.d/docker-ce.repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo…

做软件测试如何突破月薪20K?

IT行业从事技术岗位&#xff0c;尤其对于测试来说&#xff0c;月薪20K&#xff0c;即便在北上广深这类一线城市薪水也不算低了&#xff0c;可以说对于大部分测试岗位从业者来说&#xff0c;20K都是一个坎儿。 那么&#xff0c;问题来了&#xff0c;做软件测试如何可以达到月薪…