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


在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 show
    delete 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 function
    delete 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 circle
    delete shape;
    return 0;
}

三、总结

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

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

  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
C++中的虚函数是通过在基类中使用关键字virtual来声明的函数。虚函数可以在派生类中被重写,使得在运行时根据对象的类型动态调用正确的函数。虚函数的调用是基于对象的动态类型实现的,这意味着即使使用基类的指针或引用来调用虚函数,实际调用的仍然是对象的派生类中的函数。 纯虚函数是在基类中声明但没有实现的虚函数纯虚函数的声明以 "= 0" 结尾,表示该函数没有函数体。纯虚函数的存在使得基类成为抽象类,抽象类不能被实例化。派生类必须实现基类中的纯虚函数才能被实例化。 构造函数不能被声明为虚函数,因为虚函数的调用依赖于对象的类型,而在构造函数中对象的类型尚未确定。因此,构造函数无法使用虚函数的动态派发机制。 析构函数可以被声明为虚函数,当基类指针指向派生类对象并通过该指针删除对象时,如果析构函数不是虚函数,那么只会调用基类的析构函数而不会调用派生类的析构函数,从而导致派生类对象没有正确地被销毁。使用虚析构函数可以确保在删除对象时正确定位到派生类的析构函数,并按照正确的顺序销毁对象。 构造函数和析构函数都可以调用虚函数,但需要注意的是,在构造函数中调用虚函数时可能会导致意外的行为,因为在构造函数执行期间,对象的派生类部分尚未初始化。因此,最好在构造函数中避免调用虚函数。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [C++虚函数纯虚函数的问题总结](https://blog.csdn.net/weixin_44477424/article/details/124526204)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Q_hd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值