当前位置: 首页 > news >正文

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

虚函数

    • 什么是虚函数?
    • 虚函数示例解析
      • 代码解析:
    • 使用虚函数的注意事项
      • 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 函数,实现了多态。

相关文章:

  • 3.31-4 性能面试题
  • Java heap space 问题解决
  • @Resource 和 @Autowired 的区别
  • 记一次排查与解决服务器线程/进程数超限的问题
  • phpStorm2021.3.3在windows系统上配置Xdebug调试
  • 鸿蒙NEXT开发字符工具类(ArkTs)
  • 大模型高质量rag构建:A Cheat Sheet and Some Recipes For Building Advanced RAG
  • Java 大视界 -- Java 大数据在智能农业无人机植保作业路径规划与药效评估中的应用(165)
  • 华为eNSP:单区域集成IS-IS
  • 六十天Linux从0到项目搭建(第二十三天)(命名管道)
  • 【大模型基础_毛玉仁】5.5 模型编辑应用
  • 第一章:初识ROS_《ROS机器人开发实践》
  • ISIS【路由协议讲解】-通俗易懂!
  • 216. 组合总和 III 回溯
  • 大小端存储的意思与区别
  • 相机镜头景深
  • 程序化广告行业(47/89):竞价指标剖析与流量对接要点
  • 脑机交互安全:如何防止恶意脑电波指令注入
  • 算法导论(动态规划)——路径问题
  • Laravel Trait 实现 统一JSON 响应格式
  • 网站建设制作流程/网站维护收费标准
  • 用ps如何做网站首页/网络推广员是干什么的
  • 重庆网站seo方法/网站seo检测工具
  • html企业网站源码/seo线下培训班
  • 小程序游戏定制开发/seo网站分析报告
  • 上海专业做网站的/中国国家培训网官网查询