【C++】深入理解C++虚函数与纯虚函数

文章目录

  • 一、虚函数(Virtual Function)
    • 1.1 定义和作用
    • 1.2 实现原理
    • 1.3 示例代码
    • 1.4 虚函数的重写
      • 定义
      • 规则
      • 注意事项
      • 示例
    • 1.5 基类和派生类的虚函数表
      • **示例理解**
  • 二、纯虚函数(Pure Virtual Function)
    • 2.1 定义和作用
    • 2.2 示例代码
  • 三、总结

在C++面向对象编程中,多态性是其三大特性之一(封装、继承和多态)。为了实现多态性,C++引入了虚函数(Virtual Function)和纯虚函数(Pure Virtual Function)的概念。本文将深入探讨虚函数和纯虚函数的原理和应用,帮助读者更好地理解它们在C++中的作用。

一、虚函数(Virtual Function)

1.1 定义和作用

虚函数是在基类中使用关键字 virtual 声明的成员函数,它允许派生类对其进行重写(Override),实现运行时多态。当通过基类指针或引用调用虚函数时,实际调用的是对象类型对应的派生类中的函数,这个过程称为动态绑定(Dynamic Binding)或晚绑定(Late Binding)。

1.2 实现原理

虚函数的实现原理基于虚函数表(Virtual Table,简称VTable)。每个使用虚函数的类都有一个虚函数表,该表是一个函数指针数组,存储了指向类的虚函数的指针。类的每个实例都包含一个指向其虚函数表的指针(vptr),通过这个指针可以找到并调用正确的虚函数实现。

当派生类覆盖(重写)基类的虚函数时,派生类的虚函数表中相应位置的函数指针会被更新为指向派生类中的函数。如果派生类没有重写虚函数,则派生类的虚函数表中会保留指向基类虚函数的指针。

1.3 示例代码

#include <iostream>
using namespace std;class Base {
public:virtual void show() {cout << "Base class show" << endl;}
};class Derived : public Base {
public:void show() override {cout << "Derived class show" << endl;}
};int main() {Base* b = new Derived();b->show(); // 输出:Derived class showdelete b;return 0;
}

1.4 虚函数的重写

虚函数的重写(Override)是面向对象编程中实现多态性的一种方式。虚函数允许派生类根据需要改变或扩展基类中的行为。这里,我们将详细探讨虚函数的重写,包括它的定义、规则以及一些注意事项。

定义

虚函数重写指的是派生类中提供一个函数版本,该版本与基类中具有相同名称、相同返回类型和相同参数列表的虚函数相匹配。通过这种方式,派生类可以提供自己特定的实现,替换或扩展基类的行为。

规则

  1. 函数签名必须匹配:要重写基类中的虚函数,派生类中的函数必须具有相同的名称、返回类型和参数列表。
  2. 基类函数必须是虚函数:只有虚函数可以被重写。如果基类中的函数不是虚函数,派生类中相同签名的函数会隐藏(而非重写)基类中的函数。
  3. 访问权限可以不同:虚函数在派生类中的访问级别(public、protected、private)可以与基类中的不同,但这会影响到函数的访问性。
  4. 使用 override 关键字(C++11及以上):虽然不是强制的,但建议在派生类中重写虚函数时使用 override 关键字,这有助于编译器检查函数签名是否正确匹配,避免潜在的错误。

注意事项

  • 析构函数应该是虚的:如果一个类有可能被继承,并且通过基类指针来删除派生类对象,那么基类的析构函数应该是虚的。这确保了通过基类指针删除派生类对象时,能够正确地调用派生类的析构函数。
  • 构造函数不能是虚函数:在C++中,构造函数不能被声明为虚函数。因为构造函数是用来创建对象的,而虚函数的调用需要通过对象的虚函数表,这在对象构造阶段还未完全建立。
  • 使用 final 关键字防止进一步重写:在某些情况下,你可能希望禁止进一步重写某个虚函数。C++11引入了final关键字,可以用来阻止派生类重写特定的虚函数。

示例

#include <iostream>class Base {
public:virtual void print() const {std::cout << "Base class print function" << std::endl;}virtual ~Base() {} // 虚析构函数
};class Derived : public Base {
public:void print() const override { // 使用override确保正确重写std::cout << "Derived class print function" << std::endl;}
};int main() {Base* b = new Derived();b->print(); // 输出:Derived class print functiondelete b; // 正确调用派生类析构函数return 0;
}

在上述示例中,Derived 类重写了 Base 类中的 print 函数,并且基类的析构函数被声明为虚函数,确保了通过基类指针删除派生类对象时能够正确调用派生类的析构函数。

通过理解和正确应用虚函数的重写,可以充分利用C++的多态性,设计出灵活且易于维护的面向对象程序。

1.5 基类和派生类的虚函数表

当涉及到继承时,虚函数表(vtable)的处理方式会稍微复杂一些,但关键点在于每个类都有自己的虚函数表,而不是只有一个。这意味着,如果有派生类继承自基类,并且这些类中包含虚函数,那么每个类将拥有各自独立的虚函数表。下面我们来详细解释这个过程。

  1. 基类:在基类中,编译器会为其创建一个虚函数表,这个表包含了基类中所有虚函数的地址。如果派生类没有覆盖(重写)这些虚函数,派生类对象的虚函数表会复制基类虚函数表中相应的条目。

  2. 派生类:当派生类覆盖(重写)基类中的虚函数时,派生类的虚函数表中对应位置的函数指针会被更新为指向派生类中的函数实现。如果派生类引入了新的虚函数,这些新的虚函数也会被加入到派生类的虚函数表中。

  3. 多重继承:在多重继承的情况下,每个基类都会有自己的虚函数表。派生类对象会包含多个虚函数表指针,每个指针指向对应基类的虚函数表。如果派生类覆盖了某个基类的虚函数,那么相关基类虚函数表中的条目会被更新为指向派生类中的实现。

示例理解

考虑以下示例:

class Base {
public:virtual void func1() { /* 实现 */ }virtual void func2() { /* 实现 */ }
};class Derived : public Base {
public:void func1() override { /* 新实现 */ }virtual void func3() { /* 新虚函数 */ }
};
  • Base 类有自己的虚函数表,包含 func1func2
  • Derived 类有自己的虚函数表,其中 func1 的条目会被更新为指向 Derived::func1func2 保持不变(因为它没有被Derived重写),并且会添加一个新的条目指向 func3

二、纯虚函数(Pure Virtual Function)

2.1 定义和作用

纯虚函数是在基类中声明但不实现的虚函数,其声明方式是在函数声明的结尾处添加 = 0。类中如果包含至少一个纯虚函数,则该类成为抽象类(Abstract Class),不能实例化对象。

纯虚函数的主要作用是定义接口规范,强制要求派生类必须实现这些函数,从而实现接口的统一和标准化。

2.2 示例代码

#include <iostream>
using namespace std;class Shape {
public:virtual void draw() = 0; // 纯虚函数
};class Circle : public Shape {
public:void draw() override {cout << "Drawing a circle" << endl;}
};int main() {Shape* shape = new Circle();shape->draw(); // 输出:Drawing a circledelete shape;return 0;
}

三、总结

虚函数和纯虚函数是C++实现多态性的关键机制。通过虚函数,可以实现基类指针或引用调用派生类的成员函数;而纯虚函数则定义了一个接口规范,使得派生类必须实现特定的函数。这两种机制在C++面向对象编程中发挥着至关重要的作用。

理解虚函数和纯虚函数的工作原理及其在C++中的应用,对于深入学习和掌握面向对象编程具有重要意义。希望本文能够帮助读者更好地理解这一概念,提升C++编程能力。

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

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

相关文章

2、事件机制、DOM操作、jquery对尺寸操作、jquery添加和删除

一、事件机制 1、事件源.事件类型(事件处理程序) $(this)中的this不能加引号 $(#box).click(function () {$(this).css(background-color,blue)//点击颜色变为蓝色 })2、事件源.on/bind(事件类型&#xff0c;事件处理程序) $("#box").on(dbclick,function () {$(…

适配器模式在微服务的巧妙应用

适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许不兼容的接口之间可以一起工作。适配器模式通常用于将一个类的接口转换成客户端期望的另一种接口&#xff0c;从而使原本因接口不兼容而不能一起工作的类可以一起工作。 适配器模式的…

使用Haproxy搭建Web群集

Hapraxy是目前比较流行的一种群集调度工具&#xff0c;同类群集调度工具有很多&#xff0c;如LVS 和Nginx。相 比较而言&#xff0c;LVS.性能最好&#xff0c;但是搭建相对复杂:Nginx的 upstream 模块支持群集功能&#xff0c;但是对群集节 点健康检查功能不强&#xff0c;性能…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的番茄成熟度检测系统(Python+PySide6界面+训练代码)

摘要&#xff1a;开发番茄成熟度检测系统对于提高农业产量和食品加工效率具有重大意义。本篇博客详细介绍了如何利用深度学习构建一个番茄成熟度检测系统&#xff0c;并提供了完整的实现代码。该系统基于强大的YOLOv8算法&#xff0c;并结合了YOLOv7、YOLOv6、YOLOv5的对比&…

腾讯云幻兽帕鲁服务器使用Linux和Windows操作系统,对用户的技术要求有何不同?

腾讯云幻兽帕鲁服务器使用Linux和Windows操作系统对用户的技术要求有何不同&#xff1f; 首先&#xff0c;从操作界面的角度来看&#xff0c;Windows操作系统相对简单易操作&#xff0c;适合那些偏好使用图形化界面操作的用户。而Linux操作系统则需要通过命令行完成&#xff0…

百度搜索引擎SEO优化方法

随着互联网的不断发展&#xff0c;搜索引擎已经成为人们获取信息、产品和服务的主要途径之一。而在中国&#xff0c;百度作为最大的搜索引擎&#xff0c;其影响力不可忽视。了解并掌握百度SEO关键词优化方法&#xff0c;对于提升网站在搜索引擎中的排名至关重要。 关键词选择&a…

数据结构——跳表

简单介绍跳表 跳表&#xff08;Skip List&#xff09;是一种可以进行对数级别查找的数据结构&#xff0c;它通过在数据中构建多级索引来提高查询效率。跳表是一种基于链表的随机化数据结构&#xff0c;其本质是由多个链表组成&#xff0c;每个链表中的元素都是原始链表中的元素…

图论 - Trie树(字符串统计、最大异或对)

文章目录 前言Part 1&#xff1a;Trie字符串统计1.题目描述输入格式输出格式数据范围输入样例输出样例 2.算法 Part 2&#xff1a;最大异或对1.题目描述输入格式输出格式数据范围输入样例输出样例 2.算法 前言 本篇博客将介绍Trie树的常见应用&#xff0c;包括&#xff1a;Trie…

C++ 使用 nlohmann::json存储json文件

C 使用 nlohmann::json存储json文件 nlohmann::json 概述JSON 存储的示例以追加的方式存储json文件 nlohmann::json 概述 nlohmann::json 是 C 中一个流行的 JSON 库&#xff0c;由 Niels Lohmann 开发。它提供了一个简单而强大的 API&#xff0c;用于解析、构建、操作和序列化…

电子电气架构——车载以太网协议栈

电子电气架构——车载以太网协议栈 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 没有人关注你。也无需有人关注你。你必须承认自己的价值&#xff0c…

MySQL入门------数据库与SQL概述

目录 前言 一、数据库相关概念 二、数据模型 1.关系型数据库&#xff08;RDBMS&#xff09; 三、MySQL数据库 1.下载和安装 2.配置环境变量 四、SQL 1.SQL通用语法 2.SQL分类 前言 从本期开始&#xff0c;我们开始学习数据库的相关理论和实践知识&#xff0c;从入门…

jupyter 用pyecharts进行数据分析

一、jupyter和pyecharts下载和打开 因为我是用的pycharm&#xff0c;所以我直接在pycharm项目终端中下载pip install jupyter,pip install pyecharts 在你下载的项目路径中输入jupyter notebook 之后会进入页面 Jupyter 具体使用参考这个链接&#xff1a;Jupyter Notebook基本…

Pygame教程01:初识pygame游戏模块

Pygame是一个用于创建基本的2D游戏和图形应用程序。它提供了一套丰富的工具&#xff0c;让开发者能够轻松地创建游戏和其他图形应用程序。Pygame 支持许多功能&#xff0c;包括图像和声音处理、事件处理、碰撞检测、字体渲染等。 Pygame 是在 SDL&#xff08;Simple DirectMed…

常用设计模式详解

设计模式 1.UML图 统一建模语言是用来设计软件的可视化建模语言。定义了用例图、类图、对象图、状态图、活动图、时序图、协作图、构件图、部署图等 9 种图。 1.1类图 1.1.1类的表示方式 在UML类图中&#xff0c;类使用包含类名、属性(field) 和方法(method) 且带有分割线…

基本正则表达式

基本正则表达式 正则命令功能&#xff3e;尖角号&#xff0c;用于模式的最左侧&#xff0c;如“^oldbpy"&#xff0c;匹配以oldboy单词开头的行$美元符&#xff0c;用于模式的最右侧&#xff0c;如"oldboy$"&#xff0c;表示以oldboy单词结尾的行^$组合符&…

Java基于springboot的厨艺交流平台的设计与实现代码

摘 要 使用旧方法对厨艺交流信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在厨艺交流信息的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时纠正等问题。 这次开发的厨艺交流平台功…

如何优雅的删除undo表空间

前言 因磁盘空间不足&#xff0c;需要将undo表空间迁移到其它的存储空间 本文介绍如何优雅的删除undo表空间&#xff0c;并在新的存储空间中创建新的undo表空间 详细操作步骤如下&#xff1a; 1、查看默认undo表空间 SQL>show parameter undo NAME …

Redis的主从搭建

1.准备两台机器&#xff0c;安装好redis 2.修改从服务器的redis配置 slaveof <masterip> <masterport>两个参数 masterip 主的ip 主的端口号 masterport 3. 启动redis 1.先启动主机redis 2.再启用从机redis 主机redis日志打印 从机redis 日志打印

【python】1.python3.12.2和pycharm社区版的安装指南

欢迎来CILMY23的博客喔&#xff0c;本篇为【python】1.python3.12.2和pycharm社区版的安装指南&#xff0c;感谢观看&#xff0c;支持的可以给个一键三连&#xff0c;点赞关注收藏。 目录 一、python3.12.2的下载与安装 1.1下载 1.2安装 二、pycharm的安装 2.1下载安装 2…

Bootstrap的使用

目录 js的引入&#xff1a; 1.行内式 2.嵌入式 3.外链式 Bootstrap:的引入 注意事项&#xff1a; 条件注释语句&#xff1a; 栅格系统&#xff1a; 列嵌套&#xff1a; 列偏移&#xff1a; 列排序&#xff1a; 响应式工具&#xff1a; Bootstrap的字体图标的使用&a…