c++的多态

news2024/12/23 9:41:51

目录

1、多态

1.1多态的构成条件

1.2多态的好处

2、虚函数

2.1虚函数重写

2.2虚函数的默认参数

2.3纯虚函数重写

2.4抽象类

2.5虚析构,纯虚析构重写

3、重载、覆盖(重写)、隐藏(重定义)的对比

​编辑

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

程序调用函数时,将使用哪个可执行代码块呢?编译器负责回答这个问题。将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编。在c语言中,在非常简单,因为每个函数名都对应一个不同的函数。在c++中,由于函数重载的缘故,这项任务更复杂。编译器必须查看函数参数以及函数名才能确定使用哪个函数。然而,c/c++编译可以在编译过程完成这种联编。在编译过程中进行联编被称为给静态联编,又称为早期联编,然而虚函数使这项工作变得更困难。编译器必须生成能够在程序运行时选择正确使虚方法的代码,这被称为动态联编,又称为晚期联编。

1、多态

多态就是函数调用的多种形态,使用多态能够使得不同的对象去完成同一件事时,产生不同的动作和结果。

多态分为静态多态和动态多态

(1)静态多态,也成为静态绑定或前期绑定(早绑定):函数重载和运算符重载就属于静态多态。静态多态也成为编译期间的多态,编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果又对应的函数就调用该甘薯,否则出现编译错误

(2)动态多态,也成为动态绑定或后期绑定(晚绑定):在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,即运行时的多态。在程序执行期间(非编译期)判断所英勇的实际类型,根据其实际类型调用相应的方法。

父类指针或引用指向父类,调用的就是父类的虚函数

父类指针或引用指向子类,调用的就是子类的虚函数

静态多态和动态多态区别:

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

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

1.1多态的构成条件

同一操作作用于不同的对象,可以有不同的执行结果,产生不同的执行结果,这就是多态性。简单的说,就是用基类的指针指向子类的对象

所以在继承中要想构成多态需要满足两个条件:

1、必须通过基类的指针或者引用指向子类的对象

2、被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

1.2多态的好处

多态的优点:

1、代码组织结构清晰

2、可读性强

3、利于前期和后期的扩展以及维护

2、虚函数

被virtual修饰的类成员函数被称作虚函数

1、只有类的非静态成员函数前可以加virtual,普通函数前不能加virtual

2、虚函数的virtual和虚继承的vittual是同一个关键字,但实际上并没有任何关系。虚函数的virtual是为了实现多态,而虚继承的virtual是为了解决菱形继承的数据冗余和二义性

2.1虚函数重写

虚函数的重写也叫做虚函数的覆盖,若派生类中有一个和基类完全相同的虚函数(返回值类型相同,函数名相同以及参数列表完全相同),这种称为派生类的虚函数重写了基类的虚函数。

通过基类的指针或引用子类对象,从而调用我们写的虚函数,此时根据不同类型的对象,调用的就是不同的函数,产生的也是不同的结果,进而实现了动态多态。

调用哪个类型的虚函数,取决于基类指针指向或引用的对象是哪种类型的对象。

如果不使用基类的指针或引用去调用虚函数,则只会调用基类的虚函数。

#include <iostream>
using namespace std;

class Animal
{
public:
	//Speak函数就是虚函数
	//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};

class Cat :public Animal
{
public:
	void speak()
	{
		cout << "小猫在说话" << endl;
	}
};

class Dog :public Animal
{
public:

	void speak()
	{
		cout << "小狗在说话" << endl;
	}

};

void DoSpeak(Animal & animal)
{
	animal.speak();
}

void test01()
{
	Cat cat;
	DoSpeak(cat);

	Dog dog;
	DoSpeak(dog);
}

int main() {

	test01();

	return 0;
}

 注意:在重写基类虚函数时,派生类的虚函数不加virtual关键字也可以进行动态多态,主要原因是因为继承后基类的虚函数被继承下来了,在派生类中依旧保持虚函数属性。但是这种写法不是很规范,因为派生类也有可能会被继承,为了区分虚函数,建议在派生类的虚函数前也加上virtual关键字。

2.2虚函数的默认参数

动态多态中,虚函数默认参数,调用者是哪个类,就用对应类中的函数的默认参数,如果不是使用默认参数,那么就会用传递进去的参数

参数值的优先顺序:传递进去的参数 > 基类的默认参数 > 派生的默认参数

测试代码如下:

#include <iostream>
using namespace std;

class Base
{
public:
    Base(){cout<<"Base()"<<endl;}
    ~Base(){cout<<"~Base()"<<endl;}
    virtual void show(int a=123) //基类的虚函数带默认参数
    {
        cout<<"Base show"<<a<<endl;
    }
private:
};

//派生类
class Child:public Base{
public:
    Child(){cout<<"Child()"<<endl;}
    ~Child(){cout<<"~Child()"<<endl;}
    void show(int a=456) //派生类也带默认参数
    {
        cout<<"Child show"<<a<<endl;
    }
private:
};

int main()
{
    //派生类访问自己的成员 不是多态
    Child mya;
    //mya.show();//Child show456

    //多态中,虚函数默认参数,调用者是哪个类,就用对应类中的函数的默认参数
    Base *d = &mya;//Child show123
    d->show();

    //如果不是使用默认参数,那么就会用传递进去的参数
    //参数值得优先顺序 传递进去的参数 > 基类的默认参数 >派生类的默认参数
    // d->show(1000);
    return 0;
}

2.3纯虚函数重写

在堕胎中,通常父类的虚函数的实现是某无意义的,主要都是调用了子类重写的内容,那我们就可以将虚函数写成纯虚函数。c++通过使用纯虚函数提供未实现的函数。

纯虚函数的格式:

virtual 函数返回类型 函数名(参数表) = 0;

在虚函数声明的时候直接赋值为0,这样虚函数就变成纯虚函数了

什么时候下使用纯虚函数

在基类本身生成对象时不合理的时候,比如动物作为一个基类派生出老虎等子类,但动物本身这基类直接生成对象时不合理的,所以为解决这问题,方便使用类的多态性,引入了纯虚函数的概念,将函数定义为纯虚函数,则编译器要求在派生类中必须要重新给纯虚函数以实现多态性

纯虚函数并不需要实现,如果一个类中有纯虚函数那么这个类就是抽象类,如果派生类没有把基类的纯虚函数全部实现,那么派生类还是抽象类。

2.4抽象类

在类中包含纯虚函数,那么一个类中有纯虚函数那么这个类就是抽象类

抽象类的特点:

1、无法实例化对象

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

实现继承: 普通函数的继承是一种实现继承,派生类继承了基类函数的实现,可以使用该函数。
接口继承: 虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态。

建议: 所以如果不实现多态,就不要把函数定义成虚函数。

2.5虚析构,纯虚析构重写

我们有时会让一个基类指针指向用 new 运算符动态生成的派生类对象;同时,用 new 运算符动态生成的对象都是通过 delete 指向它的指针来释放的。如果一个基类指针指向用 new 运算符动态生成的派生类对象,而释放该对象时是通过释放该基类指针来完成的,就会导致delete的时候只会调用基类的析构函数,而不会调用派生类的析构函数,如果派生类的析构函数中有释放成员内存空间,可能会造成内存泄漏。所以C++ 规定,需要将基类的析构函数声明为虚函数,即虚析构函数。只要基类的析构函数是虚函数,那么派生类的析构函数不论是否用virtual关键字声明,都自动成为虚析构函数。一般来说,一个类如果定义了虚函数,则最好将析构函数也定义成虚函数。

为什么要需要虚析构函数呢?

动态多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,会造成内存泄漏,解决这个问题就需要用到虚析构函数。

虚析构函数的格式:

virtual ~类名(){}

1、虚析构函数就是用来解决通过父类指针释放子类对象。

2、如果子类中没有堆区数据,可以不写虚析构函数

纯虚函数的格式

virtual ~类名() = 0;        //类内定义

类名::~类名(){}`             //类外声明

和包含普通纯虚函数的类一样,包含了纯虚析构函数的类也是抽象类,不能被实例化,测试代码如下:

#include<iostream>
#include <unistd.h>
#include<cstring>
using namespace std;


//基类 --动物类
class Animal{
public:
    Animal(){cout<<"Animal()"<<endl;}
    //将基类的析构函数声明为 虚析构函数
    //作用:当使用基类的指针销毁 派生类对象的时候,让派生类对象的析构函数也执行
    virtual ~Animal(){cout<<"~Animal()"<<endl;} //常用方法---派生类里面是否有指针
    //可以把基类的虚析构函数 声明定义为 纯虚析构函数
    //纯虚析构 函数必须在类内声明 类外实现  
    //virtual ~Animal() = 0;
    //~Animal();
            
    //行为
    //如何让基类 不能实例化对象
    //有两种方法:第一种 将虚函数声明定义为纯虚函数
    //           第二种方法将虚析构函数声明定义为纯析构函数,但是纯析构函数必须在类外实现
    // virtual void speak(){
    //     cout<<"Animal::speak"<<endl;
    // }
    virtual void speak() = 0;

};

// Animal::~Animal()
// {
//     cout<<"~Animal()"<<endl;
// }

//派生类 狗
class Dog:public Animal
{
public:
    Dog(const char*name = "旺财"){
        cout<<"Dog()"<<endl;

        d_name = new char[strlen(name) + 1];
        strcpy(this->d_name,name);
    }
    //当基类的析构函数声明为 虚析构的时候,派生类的析构函数也默认会加上关键字 virtual
    // ~Dog()
    virtual ~Dog()
    {
        cout<<"~Dog()"<<endl;
        //在析构函数中 释放 指针成员 指向的堆空间
        delete []this->d_name;
    }

    //派生类 中 实现 基类的 虚函数    
    virtual void speak(){
        cout<<"Dog::speak"<<endl;
    }
private:
    char *d_name;       
};

int main()
{
    //Animal *p = new Dog;
    //通过基类指针指向 派生类对象
    //p->speak();
    //通过基类指针 释放 对象的内存空间,默认只会调用 基类的析构函数
    //delete p; //虚拟析构不仅释放了基类的new空间,也释放了派生类的new空间

    //动物类 是 基类 ,实例化 对象 不合理 
    //因为基类中有纯虚函数(纯虚析构函数),所以该类是抽象类,抽象类不能实例化
    //抽象类:只能通过继承 在子类中 重写纯虚函数
    Animal p1;
    //p1.speak();

    return 0;
}

//将基类的析构函数声明为 虚析构函数

/作用:当使用基类的指针销毁 派生类对象的时候,让派生类对象的析构函数也执行

//可以把基类的虚析构函数 声明定义为 纯虚析构函

//纯虚析构 函数必须在类内声明 类外实现  

//如何让基类 不能实例化对象

//有两种方法:第一种 将虚函数声明定义为纯虚函数

//           第二种方法将虚析构函数声明定义为纯析构函数,但是纯析构函数必须在类外实现

3、重载、覆盖(重写)、隐藏(重定义)的对比

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

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

相关文章

人人都是数据分析师-数据分析之数据图表可视化(下)

当前的BI报表、运营同学的汇报报告中数据图表大多为 表格、折线图、柱状图和饼图&#xff0c;但是实际上还有很多具有代表性的可视化图表&#xff0c;因此将对常见的可视化图表进行介绍&#xff0c;希望这些图表可视化方法能够更好的提供数据的可用性。 人人都是数据分析师-数…

QT网络通信-服务器(一)

目录 1、简介 2 、TCP通信流程 3、基于TCP通信所需要的类 4、QT端设计 4.1项目建立 4.2 TCP网络程序设计 4.2.1 QT界面设计 4.2.2 UI布局 4.2.3 控件重命名 5、widget.h 6、widget.c 1、简介 网络有TCP和UDP。本文主要通过QT完成TCP网络设计&#xff0c;通过ESP8266与单片…

JavaEE简单实例——一些基本操作

在配置类中配置页面解析器 之前我们使用页面解析器是在XML配置文件中使用的&#xff0c;但是当我们试用了纯注解式的整合之后&#xff0c;我们没有了配置文件&#xff0c;要如何去将之前我们在配置文件中编写的前端控制器&#xff0c;以及静态资源的释放这些功能配置添加到项目…

二叉排序树(二叉查找树)基本操作_20230417

二叉排序树&#xff08;二叉查找树&#xff09;基本操作_20230417 前言 二叉排序树首先是一颗二叉树&#xff0c;它不同于常规二叉树的地方在于&#xff0c;如果左子树不为空&#xff0c;那么左子树上所有结点的值都不大于根节点的值&#xff0c;如果右子树不为空&#xff0c…

从GPT-4、文心一言再到Copilot,AIGC卷出新赛道?

业内人都知道&#xff0c;上一周是戏剧性的&#xff0c;每一天&#xff0c;都是颠覆各个行业&#xff0c;不断 AI 化的新闻。 OpenAI发布GPT-4、百度发布文心一言、微软发布Microsoft 365 Copilot 三重buff叠加&#xff0c;打工人的命运可以说是跌宕起伏&#xff0c;命途多舛了…

pmp证书报考流程+pmp备考+pmp学习干货+pmp指南汇总

2023年共有4次PMP考试&#xff0c;分别是3月、5月、8月、11月&#xff0c;由于3月份考试不开放新报名&#xff0c;所以第一次备考PMP的同学可以选择参加5月份考试。那么&#xff0c;现在备考5月份PMP考试还来得及吗&#xff1f; 现在开始备考5月PMP考试&#xff0c;时间是非常…

Scrum

目录 1、Scrum&#xff1a; 敏捷里的3355&#xff1a; 什么是Scrum&#xff1a; Scrum的优点&#xff1a; Scrum的理论&#xff1a; Scrum的三大支柱&#xff1a; 透明性&#xff1a; 检视&#xff1a; 调整&#xff1a; 2、Scrum的角色简介&#xff1a; Scrum各角色…

【数据结构学习笔记 之 栈和队列】——上

前言&#xff1a;栈和队列是常用的数据结构之一&#xff0c;本文主要介绍有关栈的基本特性以及基本操作和一些经典的OJ题目&#xff0c;关于队列的介绍放到下篇。那么话不多说&#xff0c;让我们开始吧。 一、栈的基本知识 1. 栈的基本概念 栈是一种特殊的线性表&#xff0c…

同学在外包干了两年的点点点,24岁人就快废了

前言 简单的说下&#xff0c;我大学的一个同学&#xff0c;毕业后我自己去了自研的公司&#xff0c;他去了外包&#xff0c;快两年了我薪资、技术各个方面都有了很大的提升&#xff0c;他在外包干的这两年人都要废了&#xff0c;技术没一点提升&#xff0c;学不到任何东西&…

JavaScript 的学习

文章目录一、简介总结一、简介 JavaScript 是互联网上最流行的脚本语言&#xff0c;这门语言可用于 HTML 和 web&#xff0c;更可广泛用于服务器、PC、笔记本电脑、平板电脑和智能手机等设备。 JavaScript 是脚本语言 JavaScript 是一种轻量级的编程语言。 JavaScript 是可插入…

如果要向“硅谷精神之父”提一道问题,你会问什么?| CSDN 访谈世界互联网教父 Kevin Kelly

ChatGPT 的问世不禁让人遐想&#xff0c;接下来的 5000 天&#xff0c;将会发生什么事&#xff1f; 硅谷精神之父、世界互联网教父、《失控》作者凯文凯利&#xff08;Kevin Kelly&#xff0c;以下简称 K.K.&#xff09;是这样预测的&#xff1a; 未来将会是一切都与 AI 相连的…

Vue3通知提醒框(Notification)

Vue3相关组件项目依赖版本信息 可自定义设置以下属性&#xff1a; 消息的标题&#xff08;title&#xff09;&#xff0c;默认温馨提示自动关闭的延时时长&#xff08;duration&#xff09;&#xff0c;单位ms&#xff0c;默认4500ms消息从顶部弹出时&#xff0c;距离顶部的位…

【问题】开发遇到的小问题

文章目录使用糊涂工具&#xff0c;将时间字符串转化为LocalDateTime类型Date类型转换LocalDate类型jdk8 LocalDateTime获取当前时间和前后推时间echarts图中显示表格是需要添加宽高前端往后端传值时&#xff0c;需要转一下对象再往后端传使用 value-format"yyyy-MM-dd HH:…

jwt授权

JWT格式 由header、payload、signature三部分组成&#xff0c;中间用圆点(.)连接: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJpYW0uYXBpLm1hcm1vdGVkdS5jb20iLCJleHAiOjE2NDI4NTY2MzcsImlkZW50aXR5IjoiYWRtaW4iLCJpc3MiOiJpYW0tYXBpc2VydmVyIiwib3JpZ19pYXQiOjE2MzUw…

拼团系统开发|全民拼购商业模式解读

拼团系统开发|拼团模式在市场上已经屡见不鲜&#xff0c;某夕夕就是这方面的典范。不过现在市场上又出现一种升级版的拼团商业模式&#xff0c;也就是全民拼购&#xff0c;它除了可以帮助商家提升销量&#xff0c;还有引流裂变获客的效果&#xff0c;因此受到了许多企业商家的热…

chatgpt教你练习前端算法

今天想试试chatgpt关于代码算法这一块儿是否好用。 判断质数 上面的代码有一点小问题&#xff0c;当num为2时&#xff0c;返回的结果是错误的&#xff0c;我改进了一下&#xff0c;并优化了一点性能 // 判断是否是素数&#xff08;质数&#xff09; function isprime(number)…

【Linux】用LCD文字祝愿(Framebuffer+Freetype)

目录 前言 一、LCD操作原理 &#xff08;1&#xff09;LCD和Framebuffer。 &#xff08;2&#xff09;LCD的操作&#xff1a; &#xff08;3&#xff09;核心函数&#xff08;后续也会经常用到&#xff09; ①open函数 ②ioctl函数 ③mmap函数 二、字符的点阵显示 &a…

4K高清修复,模糊视频4k修复是怎么实现的?

在当今数字时代&#xff0c;高分辨率视频已成为大众观影的标配。4K分辨率作为其中高端的选项&#xff0c;提供了比传统1080p高出四倍的细节和清晰度&#xff0c;使得观众们能够更加身临其境地享受影视作品。然而&#xff0c;有时候我们可能会遇到4K视频质量不佳的问题&#xff…

Chapter3-用适合的方式发送和接收消息

3.1 不同类型的消费者 消费者可分为两种类型。 一个是DefaultMQPushConsumer &#xff0c;由系统控制读取操作&#xff0c;收到消息后自动调用传人的处理方法来处理&#xff1b;另 一个是 DefaultMlConsumer &#xff0c;读取操作中的大部分功能由使用者自主控制 。 3.1.1 Def…

uni-app:登录与支付--用户信息

用户信息 实现用户头像昵称区域的基本布局 在 my-userinfo 组件中&#xff0c;定义如下的 UI 结构&#xff1a; <template><view class"my-userinfo-container"><!-- 头像昵称区域 --><view class"top-box"><image src"…