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

CSDN 2024-06-27 14:35:02 阅读 67

文章目录

一、虚函数(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 show

delete b;

return 0;

}

1.4 虚函数的重写

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

定义

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

规则

函数签名必须匹配:要重写基类中的虚函数,派生类中的函数必须具有相同的名称、返回类型和参数列表。基类函数必须是虚函数:只有虚函数可以被重写。如果基类中的函数不是虚函数,派生类中相同签名的函数会隐藏(而非重写)基类中的函数。访问权限可以不同:虚函数在派生类中的访问级别(public、protected、private)可以与基类中的不同,但这会影响到函数的访问性。使用 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)的处理方式会稍微复杂一些,但关键点在于每个类都有自己的虚函数表,而不是只有一个。这意味着,如果有派生类继承自基类,并且这些类中包含虚函数,那么每个类将拥有各自独立的虚函数表。下面我们来详细解释这个过程。

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

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

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

示例理解

考虑以下示例:

class Base {

public:

virtual void func1() { /* 实现 */ }

virtual void func2() { /* 实现 */ }

};

class Derived : public Base {

public:

void func1() override { /* 新实现 */ }

virtual void func3() { /* 新虚函数 */ }

};

Base 类有自己的虚函数表,包含 func1func2Derived 类有自己的虚函数表,其中 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++编程能力。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。