C++ 什么是虚函数?什么是纯虚函数,以及区别?(通俗易懂)

📚 当谈到虚函数时,通常是指在面向对象编程中的一种机制,它允许在派生类中重写基类的函数,并且能够通过基类指针或引用调用派生类中的函数。

目录

前言

🔥 虚函数

🔥  纯虚函数

🔥 两者区别

🔥 实践案例

总结


前言

虚函数使得面向对象编程中的多态性得以实现,能够更灵活地处理不同派生类的对象,提高代码的可扩展性和可维护性。


🔥 虚函数

虚函数(Virtual Function)是在面向对象编程中用于实现动态多态性的一种机制。通过将基类中的成员函数声明为虚函数,可以在派生类中重写(Override)这些函数,从而根据对象的实际类型确定调用的函数版本。

声明方式:在基类中用 virtual 关键字声明的函数称为虚函数。

class Base {
public:virtual void display() {// Base class implementation}
};

多态调用:通过基类指针或引用调用虚函数时,实际调用的是指向对象的派生类版本(如果派生类重写了这个函数)。

动态绑定:在运行时根据对象的实际类型来确定调用的函数版本,而不是在编译时静态确定。

虚函数表(vtable):编译器通常通过添加一个指向虚函数表的指针来实现虚函数的机制。虚函数表存储了每个类的虚函数的地址。

代码示例:

#include <iostream>// 基类 Base
class Base {
public:// 虚函数 display,提供默认实现virtual void display() {std::cout << "Display function of Base class" << std::endl;}
};// 派生类 Derived
class Derived : public Base {
public:// 重写基类的虚函数 displayvoid display() override {std::cout << "Display function of Derived class" << std::endl;}
};int main() {Base* basePtr = new Derived(); // 基类指针指向派生类对象basePtr->display(); // 调用派生类中的 display 函数delete basePtr;return 0;
}

Base 类中的 display() 被声明为虚函数,并提供了默认实现。

Derived 类重写了 display() 函数,改变了默认行为。

在主函数中,通过基类指针 basePtr 调用 display() 函数时,实际调用的是 Derived 类中的版本。

🔥  纯虚函数

纯虚函数(Pure Virtual Function)是一个在基类中声明的虚函数,但没有在基类中提供实现。它通过在函数声明的结尾处使用 = 0 来标记:

在很多情况下,基类生成对象很不合理。为了解决这个问题,引入了纯虚函数的概念,将函
数定义为纯虚函数,派生类中必须重写实现纯虚函数。对于实现了纯虚函数的子类,该纯虚
函数在子类中就变成了虚函数。

声明方式

class Base {
public:virtual void display() = 0; // Pure virtual function
};

无法实例化类:包含纯虚函数的类被称为抽象类(Abstract Class),不能直接创建实例对象。

强制派生类实现:派生类必须实现基类中的纯虚函数,否则它们也会成为抽象类,无法实例化。

代码示例:

#include <iostream>// 抽象基类 AbstractBase
class AbstractBase {
public:// 纯虚函数 display,没有默认实现virtual void display() = 0;
};// 派生类 Derived 实现抽象基类
class Derived : public AbstractBase {
public:// 实现抽象基类中的纯虚函数 displayvoid display() override {std::cout << "Display function of Derived class" << std::endl;}
};int main() {// AbstractBase baseObj; // 不能实例化抽象类Derived derivedObj; // 可以实例化派生类AbstractBase* basePtr = &derivedObj; // 抽象基类指针指向派生类对象basePtr->display(); // 调用派生类中实现的 display 函数return 0;
}

AbstractBase 类中的 display() 被声明为纯虚函数,没有提供默认实现,使得 AbstractBase 成为抽象类,不能实例化。

Derived 类继承自 AbstractBase,必须实现 AbstractBase 中的纯虚函数 display

函数中,派生类 Derived 被实例化,而抽象基类 AbstractBase 的指针 basePtr 可以指向 Derived 类对象,并调用其实现的 display() 函数。

无论虚函数还是纯虚函数,定义中都不能有 static 关键字。因为 static 关键字修饰的内容在编译前就要确定,而虚函数、纯虚函数是在运行时动态绑定的。

🔥 两者区别

虚函数 允许在派生类中重写函数,但可以有默认实现。它是可选的,可以在基类中提供实现。

纯虚函数 没有默认实现,派生类必须提供实现。它使得基类成为抽象类,不能实例化。

🔥 实践案例

假设我们有一个基类 Shape,它定义了所有形状的基本属性和行为。我们希望能够计算各种形状的面积,但具体的面积计算方法因形状而异,因此我们可以使用虚函数和纯虚函数来达到这个目的。

🎯 首先,定义 Shape 类作为抽象基类,其中包含一个纯虚函数 area() 用于计算形状的面积:

#include <iostream>// Abstract base class Shape
class Shape {
public:// 纯虚函数用于计算面积virtual double area() const = 0;// 虚析构函数(对多态性很重要)virtual ~Shape() {}
};

🎯 接着,我们可以创建不同类型的形状类(如矩形、圆形)作为 Shape 的派生类,并实现它们的具体面积计算方法。

创建一个矩形类 Rectangle 和一个圆形类 Circle

class Rectangle : public Shape {
private:double width;double height;public:Rectangle(double w, double h) : width(w), height(h) {}// 重写矩形的面积函数double area() const override {return width * height;}
};class Circle : public Shape {
private:double radius;public:Circle(double r) : radius(r) {}// 重写圆的面积函数double area() const override {return 3.14 * radius * radius;}
};

🎯 我们可以使用这些类来计算具体形状的面积,而无需关心具体是哪种形状

int main() {Rectangle rect(5, 3);Circle circle(2.5);// 使用 Shape 指针访问派生类 基类的指针指向了子类的对象Shape *shape1 = &rect;Shape *shape2 = &circle;// 使用虚函数计算并打印面积std::cout << "Area of Rectangle: " << shape1->area() << std::endl;std::cout << "Area of Circle: " << shape2->area() << std::endl;return 0;
}// 运行结果
Area of Rectangle: 15
Area of Circle: 19.625

通过使用虚函数 area() 和纯虚函数 virtual double area() const = 0;,我们实现了多态性,使得能够根据实际的对象类型来调用适当的面积计算方法。同时,基类 Shape 的设计强制所有派生类实现 area() 方法,确保了面积计算的统一性和规范性。

总结

在实际应用中,虚函数和纯虚函数结合使用,通常用来定义接口和基类的通用行为,同时强制派生类实现特定的行为,从而实现一种规范化的设计模式。

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

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

相关文章

mac|idea导入通义灵码插件

官方教程&#xff1a;通义灵码下载安装指南_智能编码助手_AI编程_云效(Apsara Devops)-阿里云帮助中心 下载插件&#xff1a; ⇩ TONGYI Lingma - JetBrains 结果如下&#xff1a; 选择apply、ok&#xff0c;会出现弹窗&#xff0c;点击登录 可以实现&#xff1a;生成单元测…

《C++20设计模式》代理模式

文章目录 一、前言二、实现1、UML类图2、实现 一、前言 这代理模式和装饰器模式很像啊。都是套一层类。&#x1f630; 主要就是功能差别 装饰器&#xff1a; 为了强化原有类的功能。代理模式&#xff1a; 不改变原有功能&#xff0c;只是强化原有类的潜在行为。 我觉的书上有…

【基于R语言群体遗传学】-8-代际及时间推移对于变异的影响

上一篇博客&#xff0c;我们学习了在非选择下&#xff0c;以二项分布模拟遗传漂变的过程&#xff1a;【基于R语言群体遗传学】-7-遗传变异&#xff08;genetic variation&#xff09;-CSDN博客 那么我们之前有在代际之间去模拟&#xff0c;那么我们就想知道&#xff0c;遗传变…

KVM虚机调整磁盘大小(注:需重启虚拟机)

1、将磁盘大小由15G调整为25G [rootkvm ~]# virsh domblklist kvm-client #显示虚拟机硬盘列表 [rootkvm ~]# qemu-img resize /var/lib/libvirt/images/tesk-disk.qcow2 10G #扩容 [rootkvm ~]# qemu-img info /var/lib/libvirt/images/test-disk.qcow2 #查看信息 注&…

奥威BI方案:多行业、多场景,只打高端局

奥威BI方案&#xff0c;确实以其卓越的性能和广泛的应用领域&#xff0c;在高端数据分析市场中占据了一席之地。以下是对奥威BI方案的详细解析。 奥威BI方案是一款针对多行业、多场景的全面数据分析解决方案&#xff0c;它结合了大数据、云计算等先进技术&#xff0c;为企业提…

看互联网大厂如何落地AI-Agent(3)

vivo一站式AI智能体构建平台的演进实践 引言 在AI技术的浪潮中&#xff0c;vivo互联网产品平台架构团队负责人张硕分享了vivo在构建一站式AI智能体平台方面的演进实践和深刻洞见。 背景与挑战 vivo面临的挑战包括创造商业价值、降低学习成本、合规性、以及LLM&#xff08;大…

hnust 1816: 算法10-9:简单选择排序

hnust 1816: 算法10-9&#xff1a;简单选择排序 题目描述 选择排序的基本思想是&#xff1a;每一趟比较过程中&#xff0c;在n-i1(i1,2,…,n-1)个记录中选取关键字最小的记录作为有序序列中的第i个记录。 在多种选择排序中&#xff0c;最常用且形式最为简单的是简单选择排序。…

收银系统源码-收银台副屏广告

1. 功能描述 门店广告&#xff1a;双屏收银机&#xff0c;副屏广告&#xff0c;主屏和副屏同步&#xff0c;总部可统一控制广告位&#xff0c;也可以给门店开放权限&#xff0c;门店独立上传广告位&#xff1b; 2.适用场景 新店开业、门店周年庆、节假日门店活动宣传&#x…

【HICE】DNS反向解析

反向解析&#xff1a;IP ----> 主机名 1.更改主配置文件 2.:更改反向的信息 3.重启服务 4.测试解析是否成功

论文辅导 | 基于多尺度分解的LSTM⁃ARIMA锂电池寿命预测

辅导文章 模型描述 锂电池剩余使用寿命&#xff08;Remaining useful life&#xff0c;RUL&#xff09;预测是锂电池研究的一个重要方向&#xff0c;通过对RUL的准确预测&#xff0c;可以更好地管理和维护电池&#xff0c;延长电池使用寿命。为了能够准确预测锂电池的RUL&…

待研究课题记录

最近了解到两个新的有趣的节点&#xff0c;但是对于实际效果不是很确定&#xff0c;所以这里记录下&#xff0c;后续慢慢研究&#xff1a; 扰动注意力引导 Perturbed Attention Guidance GitHub - KU-CVLAB/Perturbed-Attention-Guidance: Official implementation of "…

CTS单测某个模块和测试项

1 &#xff0c;测试单个模块命令 run cts -m <模块名> 比如&#xff1a;run cts -m CtsUsbTests模块名可以从测试报告中看&#xff0c;如下&#xff1a; 2&#xff0c; 测试单个测试项 run cts -m <模块名> -t <test_name> 比如&#xff1a;run cts -m ru…

Linux程序地址空间

1. 进程地址空间 简单来说&#xff0c;就是从高地址往低地址&#xff0c;内存分区分别是&#xff1a; 内核空间&#xff1a;命令行参数argv和环境变量env等栈区&#xff1a;大部分局部变量&#xff0c;栈区内存往低处增长堆区&#xff1a;用于动态内存管理&#xff0c;堆区内存…

C # @逐字字符串

逐字字符串 代码 namespace TestAppConsole {class program{static void Main(string[] args){int a 0;int b 9;string c "2ui923i9023";//Console.Write(sizeof(int));string d "\t8282jjksk";string e "\t8282jjksk";Console.WriteLine(…

Java——继承(Inheritance)

一、继承简要介绍 1、继承是什么 在Java中&#xff0c;继承是一种面向对象编程的重要特性&#xff0c;它允许一个类&#xff08;子类或派生类&#xff09;继承另一个类&#xff08;父类或基类&#xff09;的属性和方法。继承的目的是实现代码的重用和设计的层次化。 子类通常…

LT8711GX 国产芯片 Type-C/DP1.4转HDMI2.1 用于加密狗 对接站

描述 LT8711GX是一款高性能的Type-C/DP1.4到HDMI2.1转换器&#xff0c;设计用于将USBType-C源或DP1.4源连接到HDMI2.1收发器。 该LT8711GX集成了一个符合DP1.4标准的接收器和一个符合HDMI2.1标准的发射器。此外&#xff0c;还包括一个CC控制器&#xff0c;用于CC通信&#xff0…

使用kali Linux启动盘轻松破解Windows电脑密码

破解分析文章仅限用于学习和研究目的&#xff1b;不得将上述内容用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。谢谢&#xff01;&#xff01; 效果展示&#xff1a; 使用kali Linux可以轻松破解Windows用户及密码 准备阶段&#xff1a; &#xff08…

ASP.NET Core 使用Log4net

1. Nuget安装log4net&#xff0c;图里的两个 2.项目根目录下添加log4net.config.添加下面的代码: <?xml version"1.0" encoding"utf-8"?> <configuration><!-- This section contains the log4net configuration settings --><log…

C语言之常用内存函数以及模拟实现

目录 前言 一、memcpy的使用和模拟实现 二、memmove的使用和模拟实现 三、memset的使用和模拟实现 四、memcmp的使用和模拟实现 总结 前言 本文主要讲述C语言中常用的内存函数&#xff1a;memcpy、memmove、memset、memcmp。内容不多&#xff0c;除了了解如何使用&#x…

细说MCU的ADC模块单通道连续采样的实现方法

目录 一、工程依赖的硬件及背景 二、设计目的 三、建立工程 1、配置GPIO 2、选择时钟源和Debug 3、配置ADC 4、配置系统时钟和ADC时钟 5、配置TIM3 6、配置串口 四、代码修改 1、重定义TIM3中断回调函数 2、启动ADC及重写其回调函数 3、定义用于存储转换结果的数…