嵌入式八股文学习——虚函数相关知识学习

news2025/4/2 23:30:12

虚函数

    • 什么是虚函数?
    • 虚函数示例解析
      • 代码解析:
    • 使用虚函数的注意事项
      • 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!

代码解析:

  1. 首先,我们定义了一个基类 Base,其中包含一个虚函数 Print()
  2. 然后,我们定义了两个派生类 Derived1Derived2,它们都继承自 Base 并重写了 Print() 函数。
  3. main() 函数中,我们创建了三个对象:CbaseCderived1Cderived2
  4. 当我们直接调用对象的 Print() 方法时,每个对象都调用了自己类中定义的 Print() 函数。
  5. 然后,我们创建了三个基类指针,分别指向三个不同的对象。
  6. 关键在于:当我们通过基类指针调用 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!

尽管在 DogCat 类中没有使用 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

在实际编译中,如果一个函数同时被声明为virtualinline,编译器会忽略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

友元函数的调用是在编译时确定的,基于参数的静态类型,不会发生动态绑定。

总结

不能声明为虚函数的函数包括:

  1. 普通函数(非成员函数)- 缺少对象上下文
  2. 构造函数 - 对象尚未创建完成,无法动态绑定
  3. 内联成员函数 - 编译时展开与运行时绑定冲突(虽然可以同时使用关键字,但inline会被忽略)
  4. 静态成员函数 - 缺少this指针,无法访问虚函数表
  5. 友元函数 - 不是类的成员,无法继承也无法重写

纯虚函数和抽象类

纯虚函数是一种特殊的虚函数,它没有具体的实现,通常用于声明接口规范。其格式如下:

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的Print
    Cderived2.Print(); // 调用派生类2的Print

    cout << "---------------" << endl;

    Base *p1 = &Cderived1;
    Base *p2 = &Cderived2;

    p1->Print(); // 调用派生类1的Print
    p2->Print(); // 调用派生类2的Print

    return 0;
}

输出结果

This is Class Derived1!
This is Class Derived2!
---------------
This is Class Derived1!
This is Class Derived2!

作用

  1. 定义接口规范:纯虚函数主要用于定义接口规范,而将具体的实现留给派生类。它确保所有派生类都必须实现该函数,从而保证了多态的实现。
  2. 实现多态:通过纯虚函数,可以实现多态。在运行时,根据对象的实际类型调用对应的函数实现。
  3. 抽象类:包含纯虚函数的类称为抽象类。抽象类不能生成对象,但可以作为接口类,用于统一管理派生类对象。

使用场景

  • 抽象类:当基类中无法提供一个通用的实现,或者某些功能必须由派生类具体实现时,可以将函数定义为纯虚函数。
  • 多态:通过纯虚函数实现多态,可以在运行时根据对象的实际类型调用对应的函数实现。

注意事项

  1. 抽象类不能实例化:包含纯虚函数的类称为抽象类,抽象类不能生成对象。
  2. 派生类必须实现纯虚函数:如果派生类没有实现基类中的纯虚函数,则派生类也是抽象类,无法实例化。
  3. 纯虚函数不能被调用:纯虚函数没有具体的实现,因此不能直接调用。

示例代码解释

在上述代码中:

  • Base 类中的 Print 函数是一个纯虚函数,它没有具体的实现。
  • Derived1Derived2Base 的派生类,它们分别实现了 Print 函数。
  • main 函数中,通过基类指针调用派生类的 Print 函数,实现了多态。

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

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

相关文章

rk3586开发版新增系统调用(Android13)

一、前言 最近想学一下kernel和hal,所以买了一块板子,带了个摄像头和屏幕,1100,学习投资了。这个Android内核定一个系统调用感觉是真的麻烦&#xff0c;主要是有一层bionic C&#xff0c;一开始不熟悉的时候还是花了点时间去配置。 二、kernel修改 include/uapi/asm-generic…

OCR第三个方案:PP-OCRv4的初步探索

一、PP-OCR历史简要回顾 先请出PP-OCR官网&#xff0c;理解上有出入的&#xff0c;以官网为准。 1.1 PP-OCR系列历史 PP-OCRv1&#xff08;2020&#xff09;&#xff1a;首创3.5M超轻量模型&#xff0c;奠定两阶段架构基础&#xff08;检测方向分类识别&#xff09;PP-OCRv2…

ICLR 2025 Spotlight:让机器人实现「自主进化」,蚂蚁数科、清华提出具身协同框架 BodyGen

最近&#xff0c;全球 AI 和机器学习顶会 ICLR 2025 公布了论文录取结果&#xff1a;由蚂蚁数科与清华大学联合团队提出的全新具身协同框架 BodyGen 成功入选 Spotlight&#xff08;聚光灯/特别关注&#xff09;论文。 论文出自蚂蚁数科与清华大学兴军亮老师团队合作的科研项目…

第十九章:Python-pyttsx3 库实现文本转语音功能

前言 在开发语音交互应用或需要文本转语音功能的项目时&#xff0c;pyttsx3 是一个非常实用的 Python 库。它支持离线语音合成&#xff0c;无需联网即可将文本转换为语音。本文将详细介绍 pyttsx3 的功能、用法以及常见问题的解决方法&#xff0c;并通过示例代码帮助你快速上手…

SvelteKit 最新中文文档教程(16)—— Service workers

前言 Svelte&#xff0c;一个语法简洁、入门容易&#xff0c;面向未来的前端框架。 从 Svelte 诞生之初&#xff0c;就备受开发者的喜爱&#xff0c;根据统计&#xff0c;从 2019 年到 2024 年&#xff0c;连续 6 年一直是开发者最感兴趣的前端框架 No.1&#xff1a; Svelte …

Flutter项目之构建打包分析

目录&#xff1a; 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地址子网掩码端口网络模型协议网络通信的过程&#xff08;封装与解封装&#xff09; 网络结构模式 C/S结构&#xff0c;由客户机和服务器两部分组成&#xff0c;如QQ、英雄联盟 B/S结构&#xff0c;通过浏览器与服务器进程交互&#xf…

Mentalab Explore Pro携手 Wearanize + 数据集,推动睡眠科学研究

在神经科学和睡眠研究的领域&#xff0c;精确监测大脑活动是获取深入见解的关键。传统多导睡眠监测&#xff08;PSG&#xff09;设备虽然提供了详尽的数据&#xff0c;但其操作的复杂性和成本限制了其在更广泛场景中的应用。可穿戴技术的兴起提供了一种新的数据收集方式&#x…

基于 RK3588 的 YOLO 多线程推理多级硬件加速引擎框架设计(代码框架和实现细节)

一、前言 接续上一篇文章&#xff0c;这个部分主要分析代码框架的实现细节和设计理念。 基于RK3588的YOLO多线程推理多级硬件加速引擎框架设计&#xff08;项目总览和加速效果&#xff09;-CSDN博客https://blog.csdn.net/plmm__/article/details/146542002?spm1001.2014.300…

【Yolov8部署】 VS2019+opencv+onnxruntime 环境下部署目标检测模型

文章目录 前言一、导出yolov8模型为onnx文件二、VS2019中环境配置三、源码与实际运行 前言 本文主要研究场景为工业场景下&#xff0c;在工控机与工业相机环境中运行的视觉缺陷检测系统&#xff0c;因此本文主要目的为实现c环境下&#xff0c;将yolov8已训练好的检测模型使用o…

论文阅读:Dual Anchor Graph Fuzzy Clustering for Multiview Data

论文地址:Dual Anchor Graph Fuzzy Clustering for Multiview Data | IEEE Journals & Magazine | IEEE Xplore 代码地址&#xff1a;https://github.com/BBKing49/DAG_FC 摘要 多视角锚图聚类近年来成为一个重要的研究领域&#xff0c;催生了多个高效的方法。然而&#…

乐橙R10 AI智能锁:以「技术减法」终结智能家居「参数内卷」

1 行业迷思&#xff1a;当「技术内卷」背离用户真实需求 “三摄猫眼”、“0.3秒人脸解锁”、“DeepSeek大模型”……智能锁行业的营销话术日益浮夸&#xff0c;但用户体验却陷入“功能冗余”与“操作复杂”的泥潭。 一位用户在社交平台直言&#xff1a;“我的智能锁有六个摄像…

如何使用 FastAPI 构建 MCP 服务器

哎呀&#xff0c;各位算法界的小伙伴们&#xff01;今天咱们要聊聊一个超酷的话题——MCP 协议&#xff01;你可能已经听说了&#xff0c;Anthropic 推出了这个新玩意儿&#xff0c;目的是让 AI 代理和你的应用程序之间的对话变得更顺畅、更清晰。不过别担心&#xff0c;为你的…

当 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.前言 最近…

【FreeRTOS】裸机开发与操作系统区别

&#x1f50e;【博主简介】&#x1f50e; &#x1f3c5;CSDN博客专家 &#x1f3c5;2021年博客之星物联网与嵌入式开发TOP5 &#x1f3c5;2022年博客之星物联网与嵌入式开发TOP4 &#x1f3c5;2021年2022年C站百大博主 &#x1f3c5;华为云开发…

Deepseek API+Python 测试用例一键生成与导出 V1.0.4 (接口文档生成接口测试用例保姆级教程)

接口文档生成接口测试用例保姆级教程 随着测试需求的复杂性增加,测试用例的设计和生成变得愈发重要。Deepseek API+Python 测试用例生成工具在 V1.0.4 中进行了全方位的优化和功能扩展,特别是对接口测试用例设计的支持和接口文档的智能解析处理。本文将详细介绍 V1.0.4 版本…

CET-4增量表

CET-4词表-增量表 注&#xff1a; 【1】所谓增量&#xff0c;是相对于高中高考之增量 即&#xff0c;如果你是在读大学生&#xff0c;高中英语单词过关了&#xff0c;准备考CET-4&#xff0c;那么侧重下面的增量词表的学习&#xff0c;也算是一条捷径吧 ^_^ 【2】本结果数据 官…

DeepSeek详解:探索下一代语言模型

文章目录 前言一、什么是DeepSeek二、DeepSeek核心技术2.1 Transformer架构2.1.1 自注意力机制 (Self-Attention Mechanism)(a) 核心思想(b) 计算过程(c) 代码实现 2.1.2 多头注意力 (Multi-Head Attention)(a) 核心思想(b) 工作原理(c) 数学描述(d) 代码实现 2.1.3 位置编码 (…

FOC 控制笔记【三】磁链观测器

一、磁链观测器基础 1.1 什么是磁链 磁链&#xff08;magnetic linkage&#xff09;是电磁学中的一个重要概念&#xff0c;指导电线圈或电流回路所链环的磁通量。单位为韦伯&#xff08;Wb&#xff09;&#xff0c;又称磁通匝。 公式为&#xff1a; 线圈匝数 穿过单匝数的…

SpringBoot项目读取自定义的配置文件

先说使用场景: 开发时在resource目录下新建一个 config 文件夹, 在里面存放 myconf.properties 文件, 打包后这个文件会放到与jar包同级的目录下, 如下图 关键点&#xff1a;自定义的文件名(当然后缀是.properties)&#xff0c;自定义的存放路径。 主要的要求是在打包后运行过…