嵌入式八股文学习——虚函数相关知识学习
虚函数
- 什么是虚函数?
- 虚函数示例解析
- 代码解析:
- 使用虚函数的注意事项
- 1. 虚函数的声明与定义
- 2. 派生类中的虚函数
- 哪些函数不能声明为虚函数
- 1. 普通函数(非成员函数)
- 2. 构造函数
- 3. 内联成员函数
- 4. 静态成员函数
- 5. 友元函数
- 总结
- 纯虚函数和抽象类
- 示例
- 输出结果
- 作用
- 使用场景
- 注意事项
- 示例代码解释
什么是虚函数?
虚函数是C++中实现多态性的关键机制。当指向基类的指针在操作它的多态类对象时,可以根据指向的不同类对象动态调用其相应的函数,这个函数就是虚函数。
在基类中定义虚函数后,可以在派生类中对虚函数进行重新定义,并且可以通过基类指针或引用,在程序运行阶段动态地选择调用基类和不同派生类中的同名函数。如果派生类中没有对虚函数重新定义,则它继承其基类的虚函数。
虚函数示例解析
让我们来分析一下给出的虚函数示例程序:
#include "stdafx.h"
#include<iostream>
using namespace std;class Base
{
public:virtual void Print() // 基类虚函数{printf("This is Class Base!\n");}
};class Derived1 :public Base
{
public:void Print() // 派生类1重写虚函数{printf("This is Class Derived1!\n");}
};class Derived2 :public Base
{
public:void Print() // 派生类2重写虚函数{printf("This is Class Derived2!\n");}
};int main()
{Base Cbase;Derived1 Cderived1;Derived2 Cderived2;// 直接调用对象的方法Cbase.Print();Cderived1.Print();Cderived2.Print();cout << "---------------" << endl;// 通过基类指针调用方法Base *p1 = &Cbase;Base *p2 = &Cderived1;Base *p3 = &Cderived2;p1->Print();p2->Print();p3->Print();
}
程序输出:
This is Class Base!
This is Class Derived1!
This is Class Derived2!
---------------
This is Class Base!
This is Class Derived1!
This is Class Derived2!
代码解析:
- 首先,我们定义了一个基类
Base,其中包含一个虚函数Print()。 - 然后,我们定义了两个派生类
Derived1和Derived2,它们都继承自Base并重写了Print()函数。 - 在
main()函数中,我们创建了三个对象:Cbase、Cderived1和Cderived2。 - 当我们直接调用对象的
Print()方法时,每个对象都调用了自己类中定义的Print()函数。 - 然后,我们创建了三个基类指针,分别指向三个不同的对象。
- 关键在于:当我们通过基类指针调用
Print()方法时,由于Print()是虚函数,程序会根据指针实际指向的对象类型来调用相应的函数。这就是多态的实现。
使用虚函数的注意事项
1. 虚函数的声明与定义
注意点: 只需要在声明函数的类体中使用关键字 virtual 将函数声明为虚函数,而定义函数时不需要使用关键字 virtual。
具体例子:
// 正确的做法
class Animal {
public:virtual void makeSound(); // 在声明时使用virtual关键字
};// 定义时不需要使用virtual关键字
void Animal::makeSound() {cout << "Some generic animal sound" << endl;
}
在类外部定义虚函数时,不需要重复 virtual 关键字。如果类的声明和定义都在类体内,则只需要一次 virtual 关键字即可:
class Animal {
public:virtual void makeSound() { // 声明并定义,仅需一次virtual关键字cout << "Some generic animal sound" << endl;}
};
2. 派生类中的虚函数
注意点: 当将基类中的某一成员函数声明为虚函数后,派生类中的同名函数自动成为虚函数。
具体例子:
class Animal {
public:virtual void makeSound() {cout << "Some generic animal sound" << endl;}
};class Dog : public Animal {
public:// 此处无需使用virtual关键字,该函数自动成为虚函数void makeSound() {cout << "Woof!" << endl;}
};class Cat : public Animal {
public:// 同样,此处也无需使用virtual关键字void makeSound() {cout << "Meow!" << endl;}
};int main() {Animal* animals[3];animals[0] = new Animal();animals[1] = new Dog();animals[2] = new Cat();for(int i = 0; i < 3; i++) {animals[i]->makeSound(); // 动态绑定,调用对应派生类的函数}// 释放内存for(int i = 0; i < 3; i++) {delete animals[i];}return 0;
}
输出:
Some generic animal sound
Woof!
Meow!
尽管在 Dog 和 Cat 类中没有使用 virtual 关键字,但 makeSound() 函数仍然被视为虚函数,并且能够通过基类指针正确调用对应的派生类函数。
哪些函数不能声明为虚函数
1. 普通函数(非成员函数)
无法声明为虚函数的原因:普通函数只能被重载(overload),不能被重写(override)。虚函数的目的是实现运行时多态,而普通函数没有关联的对象,无法在运行时根据对象类型做出不同的行为。
示例:
#include <iostream>
using namespace std;// 错误 - 普通函数不能被声明为虚函数
// virtual void globalFunction() { cout << "Global function" << endl; }// 正确 - 普通函数可以重载但不能是虚函数
void globalFunction() { cout << "Global function" << endl; }
void globalFunction(int x) { cout << "Overloaded global function: " << x << endl; }int main() {globalFunction();globalFunction(10);return 0;
}
如果尝试将普通函数声明为虚函数,编译器会报错,因为虚函数需要一个虚函数表,而虚函数表必须与对象实例关联。
2. 构造函数
无法声明为虚函数的原因:构造函数的作用是初始化对象,在构造函数被调用时,对象尚未完全创建,虚函数表也尚未设置完成。此外,从语义上讲,构造函数是为了明确初始化对象成员,而虚函数是为了在不完全了解细节的情况下处理对象。
示例:
#include <iostream>
using namespace std;class Base {
public:// 错误 - 构造函数不能是虚函数// virtual Base() { cout << "Base constructor" << endl; }// 正确 - 普通构造函数Base() { cout << "Base constructor" << endl; }
};class Derived : public Base {
public:Derived() { cout << "Derived constructor" << endl; }
};int main() {Derived d; // 先调用Base构造函数,再调用Derived构造函数return 0;
}
输出:
Base constructor
Derived constructor
构造函数的调用顺序是由继承关系决定的,从基类到派生类,这与虚函数的动态绑定机制不兼容。
3. 内联成员函数
无法声明为虚函数的原因:内联函数的目的是在编译时直接展开代码,减少函数调用开销,而虚函数是在运行时动态绑定的,这两个概念在实现上存在冲突。
示例:
#include <iostream>
using namespace std;class Base {
public:// 声明为虚函数的内联函数 - 编译器会忽略inline关键字virtual inline void show() { cout << "Base show" << endl; }
};class Derived : public Base {
public:inline void show() override { cout << "Derived show" << endl; }
};int main() {Base* ptr = new Derived();ptr->show(); // 调用Derived::show(),内联特性被忽略delete ptr;return 0;
}
输出:
Derived show
在实际编译中,如果一个函数同时被声明为virtual和inline,编译器会忽略inline特性,优先考虑虚函数的动态绑定特性。所以技术上可以同时使用这两个关键字,但实际上内联特性不会生效。
4. 静态成员函数
无法声明为虚函数的原因:静态成员函数属于类而非对象,所有对象共享同一份代码。虚函数通过对象的虚函数表实现动态绑定,而静态成员函数没有this指针,无法访问虚函数表。
示例:
#include <iostream>
using namespace std;class Base {
public:// 错误 - 静态成员函数不能是虚函数// static virtual void staticFunction() { cout << "Base static function" << endl; }// 正确 - 普通静态成员函数static void staticFunction() { cout << "Base static function" << endl; }
};class Derived : public Base {
public:// 这是一个独立的静态函数,不是重写static void staticFunction() { cout << "Derived static function" << endl; }
};int main() {Base::staticFunction(); // 调用Base的静态函数Derived::staticFunction(); // 调用Derived的静态函数Base* ptr = new Derived();// 通过指针调用静态函数,实际上调用的是指针类型对应的类的静态函数ptr->staticFunction(); // 调用Base的静态函数delete ptr;return 0;
}
输出:
Base static function
Derived static function
Base static function
静态成员函数的调用在编译时就已确定,不会发生运行时的动态绑定。
5. 友元函数
无法声明为虚函数的原因:友元函数不是类的成员函数,而是被授予访问类的私有成员的外部函数。由于友元关系不能被继承,友元函数没有重写的概念,因此无法声明为虚函数。
示例:
#include <iostream>
using namespace std;class Base {
private:int value = 10;// 错误 - 友元函数不能是虚函数// virtual friend void friendFunction(Base& obj) { cout << "Value: " << obj.value << endl; }// 正确 - 普通友元函数friend void friendFunction(Base& obj);
};void friendFunction(Base& obj) {cout << "Base value: " << obj.value << endl;
}class Derived : public Base {
private:int derivedValue = 20;// 这是一个新的友元函数,不是重写friend void friendFunction(Derived& obj);
};void friendFunction(Derived& obj) {cout << "Derived value: " << obj.derivedValue << endl;// 也可以通过Base类的友元函数访问基类部分的私有成员friendFunction(static_cast<Base&>(obj));
}int main() {Base b;Derived d;friendFunction(b); // 调用Base的友元函数friendFunction(d); // 调用Derived的友元函数return 0;
}
输出:
Base value: 10
Derived value: 20
Base value: 10
友元函数的调用是在编译时确定的,基于参数的静态类型,不会发生动态绑定。
总结
不能声明为虚函数的函数包括:
- 普通函数(非成员函数)- 缺少对象上下文
- 构造函数 - 对象尚未创建完成,无法动态绑定
- 内联成员函数 - 编译时展开与运行时绑定冲突(虽然可以同时使用关键字,但inline会被忽略)
- 静态成员函数 - 缺少this指针,无法访问虚函数表
- 友元函数 - 不是类的成员,无法继承也无法重写
纯虚函数和抽象类
纯虚函数是一种特殊的虚函数,它没有具体的实现,通常用于声明接口规范。其格式如下:
virtual 返回值类型 函数名(形参列表) = 0;
virtual关键字表示这是一个虚函数。= 0表示这是一个纯虚函数,即没有具体的实现。
示例
以下是一个包含纯虚函数的基类和派生类的示例:
#include <iostream>
using namespace std;// 基类
class Base {
public:virtual void Print() = 0; // 纯虚函数
};// 派生类1
class Derived1 : public Base {
public:void Print() override { // 实现纯虚函数cout << "This is Class Derived1!" << endl;}
};// 派生类2
class Derived2 : public Base {
public:void Print() override { // 实现纯虚函数cout << "This is Class Derived2!" << endl;}
};int main() {Derived1 Cderived1;Derived2 Cderived2;Cderived1.Print(); // 调用派生类1的PrintCderived2.Print(); // 调用派生类2的Printcout << "---------------" << endl;Base *p1 = &Cderived1;Base *p2 = &Cderived2;p1->Print(); // 调用派生类1的Printp2->Print(); // 调用派生类2的Printreturn 0;
}
输出结果
This is Class Derived1!
This is Class Derived2!
---------------
This is Class Derived1!
This is Class Derived2!
作用
- 定义接口规范:纯虚函数主要用于定义接口规范,而将具体的实现留给派生类。它确保所有派生类都必须实现该函数,从而保证了多态的实现。
- 实现多态:通过纯虚函数,可以实现多态。在运行时,根据对象的实际类型调用对应的函数实现。
- 抽象类:包含纯虚函数的类称为抽象类。抽象类不能生成对象,但可以作为接口类,用于统一管理派生类对象。
使用场景
- 抽象类:当基类中无法提供一个通用的实现,或者某些功能必须由派生类具体实现时,可以将函数定义为纯虚函数。
- 多态:通过纯虚函数实现多态,可以在运行时根据对象的实际类型调用对应的函数实现。
注意事项
- 抽象类不能实例化:包含纯虚函数的类称为抽象类,抽象类不能生成对象。
- 派生类必须实现纯虚函数:如果派生类没有实现基类中的纯虚函数,则派生类也是抽象类,无法实例化。
- 纯虚函数不能被调用:纯虚函数没有具体的实现,因此不能直接调用。
示例代码解释
在上述代码中:
Base类中的Print函数是一个纯虚函数,它没有具体的实现。Derived1和Derived2是Base的派生类,它们分别实现了Print函数。- 在
main函数中,通过基类指针调用派生类的Print函数,实现了多态。
相关文章:
嵌入式八股文学习——虚函数相关知识学习
虚函数 什么是虚函数?虚函数示例解析代码解析: 使用虚函数的注意事项1. 虚函数的声明与定义2. 派生类中的虚函数 哪些函数不能声明为虚函数1. 普通函数(非成员函数)2. 构造函数3. 内联成员函数4. 静态成员函数5. 友元函数总结 纯虚…...
rk3586开发版新增系统调用(Android13)
一、前言 最近想学一下kernel和hal,所以买了一块板子,带了个摄像头和屏幕,1100,学习投资了。这个Android内核定一个系统调用感觉是真的麻烦,主要是有一层bionic C,一开始不熟悉的时候还是花了点时间去配置。 二、kernel修改 include/uapi/asm-generic…...
OCR第三个方案:PP-OCRv4的初步探索
一、PP-OCR历史简要回顾 先请出PP-OCR官网,理解上有出入的,以官网为准。 1.1 PP-OCR系列历史 PP-OCRv1(2020):首创3.5M超轻量模型,奠定两阶段架构基础(检测方向分类识别)PP-OCRv2…...
物联网开发项目:AS608+ESP32S3+Vue构建指纹识别系统(二)——ESP32部分
一、前言 接着上一篇文章介绍的关于AS608模块的介绍以及关于指纹特征库的提取与导入分析,如果亲自上手了的话,那么对于Arduino IDE和AS608的基本操作已经熟悉了。 在这一个月之中,抛开中途有事耽误了,终于是基本上完成了我们整个项…...
程序化广告行业(46/89):竞价结算规则、底价策略与内部排名解析
程序化广告行业(46/89):竞价结算规则、底价策略与内部排名解析 大家好!在之前的几篇博客中,我们已经深入探讨了程序化广告的多个重要方面,从基础概念到实际操作流程。我写这些博客的目的,就是希…...
ICLR 2025 Spotlight:让机器人实现「自主进化」,蚂蚁数科、清华提出具身协同框架 BodyGen
最近,全球 AI 和机器学习顶会 ICLR 2025 公布了论文录取结果:由蚂蚁数科与清华大学联合团队提出的全新具身协同框架 BodyGen 成功入选 Spotlight(聚光灯/特别关注)论文。 论文出自蚂蚁数科与清华大学兴军亮老师团队合作的科研项目…...
第十九章:Python-pyttsx3 库实现文本转语音功能
前言 在开发语音交互应用或需要文本转语音功能的项目时,pyttsx3 是一个非常实用的 Python 库。它支持离线语音合成,无需联网即可将文本转换为语音。本文将详细介绍 pyttsx3 的功能、用法以及常见问题的解决方法,并通过示例代码帮助你快速上手…...
Unity 2022.3.x部分Android设备播放视频黑屏问题
Android平台视频兼容性问题很多…类似的黑屏问题真的很头大,总结一些常见问题: 1. 视频文件不支持压缩 如果使用AssetBundle加载视频,这个AssetBundle压缩格式要选None。有人可能会说最新版Unity已经支持bundle压缩下播放视频,稳…...
vLLM 部署 openai whisper 模型实现语音转文字
vLLM 部署 openai whisper 模型实现语音转文字 1. 安装 vLLM2. 启动 openai whisper 模型 1. 安装 vLLM pip install vllm vllm[audio] --pre --extra-index-url https://wheels.vllm.ai/nightly --upgrade2. 启动 openai whisper 模型 CUDA_VISIBLE_DEVICES0 \ VLLM_WORKER_…...
【Zabbix技术系列文章】第④篇——Zabbix 数据可视化
在当今数字化运维时代,面对海量的监控数据,如何从中快速获取有价值的信息至关重要。Zabbix 的数据可视化功能为我们提供了直观、高效的解决方案,它能将复杂的监控数据转化为清晰易懂的图表和仪表盘,助力运维人员迅速发现问题、分析…...
表格数据导出为Excel
环境及插件配置:(理论上vue2应该也可以使用,没有试验过) "vue": "^3.2.36", "webpack": "^5.94.0", "webpack-cli": "^5.1.4", "file-saver": "^2.…...
Faster-Whisper —— 为语音识别加速的利器
Faster-Whisper —— 为语音识别加速的利器 在语音识别技术迅速发展的今天,OpenAI 的 Whisper 模型因其强大的多语言识别能力和优异的准确率而受到广泛关注。然而,高精度模型往往伴随着高昂的计算开销和较长的推理时间,这对于需要实时或大规…...
SvelteKit 最新中文文档教程(16)—— Service workers
前言 Svelte,一个语法简洁、入门容易,面向未来的前端框架。 从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1: Svelte …...
Flutter项目之构建打包分析
目录: 1、准备部分2、构建Android包2.1、配置修改部分2.2、编译打包 3、构建ios包3.1、配置修改部分3.2、编译打包 1、准备部分 2、构建Android包 2.1、配置修改部分 2.2、编译打包 执行flutter build apk命令进行打包。 3、构建ios包 3.1、配置修改部分 3.2、编译…...
24、网络编程基础概念
网络编程基础概念 网络结构模式MAC地址IP地址子网掩码端口网络模型协议网络通信的过程(封装与解封装) 网络结构模式 C/S结构,由客户机和服务器两部分组成,如QQ、英雄联盟 B/S结构,通过浏览器与服务器进程交互…...
Mentalab Explore Pro携手 Wearanize + 数据集,推动睡眠科学研究
在神经科学和睡眠研究的领域,精确监测大脑活动是获取深入见解的关键。传统多导睡眠监测(PSG)设备虽然提供了详尽的数据,但其操作的复杂性和成本限制了其在更广泛场景中的应用。可穿戴技术的兴起提供了一种新的数据收集方式&#x…...
基于 RK3588 的 YOLO 多线程推理多级硬件加速引擎框架设计(代码框架和实现细节)
一、前言 接续上一篇文章,这个部分主要分析代码框架的实现细节和设计理念。 基于RK3588的YOLO多线程推理多级硬件加速引擎框架设计(项目总览和加速效果)-CSDN博客https://blog.csdn.net/plmm__/article/details/146542002?spm1001.2014.300…...
element-ui图片查看器
element-ui图片查看器 调用案例: <el-image-viewerv-if"showViewer":on-close"()>{showViewerfalse}":url-list"imgList" />export default {components: {Banner,el-image-viewer:()>import(element-ui/packages/image/…...
VoIP技术及其与UDP的关系详解
随着互联网的飞速发展,基于IP的语音通信技术(Voice over Internet Protocol,简称VoIP)已经成为现代通信的重要支柱。从Skype到Zoom,从企业电话系统到智能音箱,VoIP以其低成本、高灵活性和强大的扩展性逐渐取…...
Java中如何保证高并发的数据安全
在Java中保证高并发的数据安全,可以从以下几个方面入手: 1. 锁机制 • synchronized:Java内置的锁机制,用于同步方法或代码块,简单易用,但灵活性较低。 • ReentrantLock:提供了比synchronize…...
DeepSeek原生稀疏注意力(Native Sparse Attention, NSA)算法介绍
李升伟 整理 DeepSeek 提出的原生稀疏注意力(Native Sparse Attention, NSA)算法是一种创新的注意力机制,旨在解决大语言模型(LLM)在处理长序列数据时的计算瓶颈问题。NSA 通过结合算法优化和硬件对齐设计,…...
Java基础知识总结(1.8)——Java 注解(持续更新)
更新时间:2025-03-31 Web后端专栏:CSDN专栏——理论-Web后端技术博客总目录:计算机技术系列博客——目录页 8.1 注解的概念 8.1.1 定义与作用 Java注解(Annotation)是Java语言自JDK1.5版本引入的核心特性࿰…...
【Yolov8部署】 VS2019+opencv+onnxruntime 环境下部署目标检测模型
文章目录 前言一、导出yolov8模型为onnx文件二、VS2019中环境配置三、源码与实际运行 前言 本文主要研究场景为工业场景下,在工控机与工业相机环境中运行的视觉缺陷检测系统,因此本文主要目的为实现c环境下,将yolov8已训练好的检测模型使用o…...
论文阅读:Dual Anchor Graph Fuzzy Clustering for Multiview Data
论文地址:Dual Anchor Graph Fuzzy Clustering for Multiview Data | IEEE Journals & Magazine | IEEE Xplore 代码地址:https://github.com/BBKing49/DAG_FC 摘要 多视角锚图聚类近年来成为一个重要的研究领域,催生了多个高效的方法。然而&#…...
Lambda 表达式是什么以及如何使用
目录 📌 Kotlin 的 Lambda 表达式详解 🎯 什么是 Lambda 表达式? 🔥 1. Lambda 表达式的基本语法 ✅ 示例 1:Lambda 基本写法 ✅ 示例 2:使用 it 关键字(单参数简化) ✅ 示例 3…...
乐橙R10 AI智能锁:以「技术减法」终结智能家居「参数内卷」
1 行业迷思:当「技术内卷」背离用户真实需求 “三摄猫眼”、“0.3秒人脸解锁”、“DeepSeek大模型”……智能锁行业的营销话术日益浮夸,但用户体验却陷入“功能冗余”与“操作复杂”的泥潭。 一位用户在社交平台直言:“我的智能锁有六个摄像…...
如何使用 FastAPI 构建 MCP 服务器
哎呀,各位算法界的小伙伴们!今天咱们要聊聊一个超酷的话题——MCP 协议!你可能已经听说了,Anthropic 推出了这个新玩意儿,目的是让 AI 代理和你的应用程序之间的对话变得更顺畅、更清晰。不过别担心,为你的…...
基于Python的Django框架的手机购物商城管理系统
标题:基于Python的Django框架的手机购物商城管理系统 内容:1.摘要 随着互联网的快速发展,手机购物逐渐成为人们日常生活中不可或缺的一部分。本研究的目的是开发一个基于Python的Django框架的手机购物商城管理系统,以提高购物商城的管理效率和用户体验。…...
【UE5.3.2】初学1:适合初学者的入门路线图和建议
3D人物的动作制作 大神分析:3D人物的动作制作通常可以分为以下几个步骤: 角色绑定(Rigging):将3D人物模型绑定到一个骨骼结构上,使得模型能够进行动画控制。 动画制作(Animation):通过控制骨骼结构,制作出人物的各种动作,例如走路、跳跃、打斗等。 动画编辑(Ani…...
当 EcuBus-Pro + UTA0401 遇上 NSUC1500
文章目录 1.前言2.EcuBus-Pro简介2.1 官方地址2.2 概览 3.纳芯微NSUC1500简介3.1 NSUC1500概述3.2 产品特性 4.测试环境5.基础功能5.1 数据发送5.2 数据监控 6.自动化功能6.1 脚本创建6.2 脚本编辑6.3 脚本编辑与测试 7.音乐律动7.1 导入例程7.2 效果展示 ECB工程 1.前言 最近…...
