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:
    // 重写基类的虚函数 display
    void 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:
    // 实现抽象基类中的纯虚函数 display
    void 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() 方法,确保了面积计算的统一性和规范性。

总结

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

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

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

相关文章

用 Echarts 画折线图

https://andi.cn/page/621503.html

leetcode每日一题-3033. 修改矩阵

题目描述&#xff1a; 解题思路&#xff1a;简单题目&#xff0c;思路非常直接。对列进行遍历&#xff0c;记录下最大值&#xff0c;然后再遍历一遍&#xff0c;把-1替换为最大值。需要注意的是进行列遍历和行遍历是不同的。 官方题解&#xff1a; class Solution { public:v…

VRay渲染有什么技巧?渲染100邀请码1a12

渲染是视觉行业非常重要的一环&#xff0c;没有渲染就没有效果图&#xff0c;常用的渲染器有Vray&#xff0c;而Vray渲染有很多技巧&#xff0c;可以让渲染更快更省&#xff0c;下面我们总结下。 1、删除无用对象 检查场景&#xff0c;看是否有一些不需要渲染的物体和灯光&am…

将大型语言模型模块化打造协作智能体

B UILDING C OOPERATIVE E MBODIED A GENTS MODULARLY WITH L ARGE L ANGUAGE M ODELS 论文链接&#xff1a; https://arxiv.org/abs/2307.02485https://arxiv.org/abs/2307.02485 1.概述 在去中心化控制及多任务环境中&#xff0c;多智能体合作问题因原始感官观察、高昂…

绝区肆--2024 年AI安全状况

前言 随着人工智能系统变得越来越强大和普及&#xff0c;与之相关的安全问题也越来越多。让我们来看看 2024 年人工智能安全的现状——评估威胁、分析漏洞、审查有前景的防御策略&#xff0c;并推测这一关键领域的未来可能如何。 主要的人工智能安全威胁 人工智能系统和应用程…

el-date-picker 设置默认值为当前日期

this.listQuery.Date new Date().toISOString().substr(0, 10); <el-date-picker v-model"listQuery.Date" format"yyyy-MM-dd" value-format"yyyy-MM-dd" type"date" placeholder"选择日期" change"getList()&qu…

Java语言程序设计篇一

Java语言概述 Java语言起源编程语言最新排名名字起源Java语言发展历程Java语言的特点Java虚拟机垃圾回收Java语言规范Java技术简介Java程序的结构Java程序注意事项&#xff1a;注释编程风格练习 Java语言起源 1990年Sun公司提出一项绿色计划。1992年语言开发成功最初取名为Oak…

Blender新手入门笔记收容所(一)

基础篇 基础操作 视角的控制 控制观察视角&#xff1a;鼠标中键平移视图&#xff1a;Shift鼠标中键缩放视图&#xff1a;滚动鼠标中键滚轮 选中物体后&#xff1a;移动物体快捷键G&#xff0c;移动后单击鼠标就会定下来。 进入移动状态后&#xff1a;按Y会沿着Y轴移动进入移动…

成人高考本科何时报名-深职训学校帮您规划学习之路

你有想过继续深造自己的学历吗&#xff1f;也许你已经工作多年&#xff0c;但总觉得学历是一块心病&#xff0c;想要通过成人高考本科来提升自己。不用着急&#xff0c;今天我们来聊一聊成人高考本科的报名时间&#xff0c;以及深职训学校如何帮助你顺利完成报名。 深圳成人高…

2024上半年网络工程师考试《应用技术》试题一

阅读以下说明&#xff0c;回答问题。 【说明】 MPLS基于(1)进行转发&#xff0c;进行MPLS标签交换和报文转发的网络设备称为(2)&#xff0c;构成MPLS域(MPSDomain)。位于MPLS域边缘、连接其他网络的LSR称为(3),区域内部的LSR称为核心LSR(CoreLSR)IP报文进入MPLS网络时&#xf…

文件管理下:文件函数的学习

前言 Hello,小伙伴们你们的作者君又来了&#xff0c;上次我们简单介绍了文件的坐拥并简单提到了数据的读取&#xff0c;和C语言的默认流的作用&#xff0c;今天我将继续带领大家探索文件的奥秘&#xff0c;大家准别好了吗&#xff1f; 在内容开始之前还是按照惯例&#xff0c…

Alt与Tab切换窗口时将Edge多个标签页作为一个整体参与切换的方法

本文介绍在Windows电脑中&#xff0c;使用Alt与Tab切换窗口时&#xff0c;将Edge浏览器作为一个整体参与切换&#xff0c;而不是其中若干个页面参与切换的方法。 最近&#xff0c;需要将主要使用的浏览器由原本的Chrome换为Edge&#xff1b;但是&#xff0c;在更换后发现&#…

Python爬虫系列-让爬虫自己写爬虫(半自动化,代替人工写爬虫)

现在的PC、手机客户端等终端设备大量使用了网页前后端技术&#xff0c;另外主流的网站也会经常会更新&#xff0c;导致以前一个月更新一次爬虫代码&#xff0c;变成了天天需要更新代码&#xff0c;所以自动化爬虫技术在当前就显得特别重要&#xff0c;最近我也是在多次更新某个…

Java | Leetcode Java题解之第220题存在重复元素III

题目&#xff1a; 题解&#xff1a; class Solution {public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {int n nums.length;Map<Long, Long> map new HashMap<Long, Long>();long w (long) t 1;for (int i 0; i < n; i) {long i…

【Java探索之旅】初识多态_概念_实现条件

文章目录 &#x1f4d1;前言一、多态1.1 概念1.2 多态的实现条件 &#x1f324;️全篇总结 &#x1f4d1;前言 多态作为面向对象编程中的重要概念&#xff0c;为我们提供了一种灵活而强大的编程方式。通过多态&#xff0c;同一种操作可以应用于不同的对象&#xff0c;并根据对象…

【Python迭代器探秘】:揭秘迭代器与生成器的魔法,掌握高效循环的艺术

文章目录 一、迭代器的基本概念1.1 迭代器优点1.2 迭代器的编写方法1.3 python内置迭代器函数1.4 小结1.5 迭代器对象与迭代对象1.5.1 区别1. 迭代对象2. 迭代器对象3. 小结 1.5.2 方法区分 二、生成器基本概念1. 生成器函数2. 生成器表达式 一、迭代器的基本概念 迭代器是Pyt…

一.2.(2)基本共射放大电路组成、工作原理;

1.基本共射放大电路组成 共什么取决于输入输出&#xff0c;共剩下的那一极 2.工作原理 输入信号ui通过电容C1加到三极管的基 极&#xff0c;引起基极电流iB的变化&#xff0c;iB的变化又使集电极电流ic发生变 化&#xff0c;且ic的变化量是iB变化量的β倍。由于有集电极电压&…

【数据结构】05.双向链表

一、双向链表的结构 注意&#xff1a;这里的“带头”跟前面我们说的“头节点”是两个概念&#xff0c;带头链表里的头节点&#xff0c;实际为“哨兵位”&#xff0c;哨兵位节点不存储任何有效元素&#xff0c;只是站在这里“放哨的”。 “哨兵位”存在的意义&#xff1a;遍历循…

Django QuerySet对象,filter()方法

filter()方法 用于实现数据过滤功能&#xff0c;相当于sql语句中的where子句。 filter(字段名__exact10) 或 filter(字段名10)类似sql 中的 10 filter(字段名__gt10) 类似SQL中的 >10 filter(price__lt29.99) 类似sql中的 <29.99 filter(字段名__gte10, 字段名__lte20…

LightGlue: Local Feature Matching at Light Speed【文献阅读】

论文&#xff1a;LightGlue: Local Feature Matching at Light Speed 代码&#xff1a;https://github.com/cvg/LightGlue 作者&#xff1a;1 ETH Zurich__2 Microsoft Mixed Reality & AI Lab Abstract 提出的LightGlue是一个深度神经网络用于学习图像间的局部特征匹配。…