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,一经查实,立即删除!

相关文章

洛谷 P1032 [NOIP2002 提高组] 字串变换

P1032 [NOIP2002 提高组] 字串变换 - 洛谷 | 计算机科学教育新生态 题目来源 洛谷 题目内容 [NOIP2002 提高组] 字串变换 题目背景 本题不保证存在靠谱的多项式复杂度的做法。测试数据非常的水&#xff0c;各种做法都可以通过&#xff0c;不代表算法正确。因此本题题目和…

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;为企业提…

LeetCode:3101. 交替子数组计数(Java 找规律)

目录 3101. 交替子数组计数 题目描述&#xff1a; 实现代码与解析&#xff1a; 简洁版&#xff1a; 原理思路&#xff1a; 3101. 交替子数组计数 题目描述&#xff1a; 给你一个二进制数组nums 。如果一个子数组中 不存在 两个 相邻 元素的值 相同 的情况&#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;最常用且形式最为简单的是简单选择排序。…

FFmpeg常用命令手册

官方文档&#xff1a;ffmpeg Documentation 常规选项 -i input_url 输入文件或者输入流的路径 Main 选项 -f fmt (input/output) 强制指定输入或输出文件的格式&#xff0c; 常见的格式名称包括flv&#xff0c;mp4、hls、mpegts、avi、mov -c[:stream_specifier] codec (i…

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

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

【HICE】DNS反向解析

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

聊天服实现设计

聊天服拓扑关系 聊天服独立于游戏服&#xff0c;客户端直连聊天服客户端按 PlayerID 散列&#xff0c;连接聊天服有涉及扣道具才能发言的等&#xff08;必须依赖游戏服功能的&#xff09;&#xff0c;先走游戏服&#xff0c;其他均可以直接走聊天服 聊天频道 - 世界频道 客户…

论文辅导 | 基于多尺度分解的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 "…

默认导出(default)和命名导出

1.默认导出 优点&#xff1a; 简洁的导入语法&#xff1a; 导入时不需要使用花括号&#xff0c;可以直接重命名。单一职责&#xff1a; 模块导出一个主要功能或对象时&#xff0c;默认导出更符合逻辑。 适用场景&#xff1a; 模块只有一个导出&#xff1a; 如一个组件、一个…

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;的属性和方法。继承的目的是实现代码的重用和设计的层次化。 子类通常…