📚 当谈到虚函数时,通常是指在面向对象编程中的一种机制,它允许在派生类中重写基类的函数,并且能够通过基类指针或引用调用派生类中的函数。
目录
前言
🔥 虚函数
🔥 纯虚函数
🔥 两者区别
🔥 实践案例
总结
前言
虚函数使得面向对象编程中的多态性得以实现,能够更灵活地处理不同派生类的对象,提高代码的可扩展性和可维护性。
🔥 虚函数
虚函数(Virtual Function)是在面向对象编程中用于实现动态多态性的一种机制。通过将基类中的成员函数声明为虚函数,可以在派生类中重写(Override)这些函数,从而根据对象的实际类型确定调用的函数版本。
声明方式:在基类中用 virtual
关键字声明的函数称为虚函数。
class Base {
public:
virtual void display() {
// Base class implementation
}
};
多态调用:通过基类指针或引用调用虚函数时,实际调用的是指向对象的派生类版本(如果派生类重写了这个函数)。
动态绑定:在运行时根据对象的实际类型来确定调用的函数版本,而不是在编译时静态确定。
虚函数表(vtable):编译器通常通过添加一个指向虚函数表的指针来实现虚函数的机制。虚函数表存储了每个类的虚函数的地址。
代码示例:
#include <iostream>
// 基类 Base
class Base {
public:
// 虚函数 display,提供默认实现
virtual void display() {
std::cout << "Display function of Base class" << std::endl;
}
};
// 派生类 Derived
class Derived : public Base {
public:
// 重写基类的虚函数 display
void display() override {
std::cout << "Display function of Derived class" << std::endl;
}
};
int main() {
Base* basePtr = new Derived(); // 基类指针指向派生类对象
basePtr->display(); // 调用派生类中的 display 函数
delete basePtr;
return 0;
}
Base
类中的display()
被声明为虚函数,并提供了默认实现。
Derived
类重写了display()
函数,改变了默认行为。在主函数中,通过基类指针
basePtr
调用display()
函数时,实际调用的是Derived
类中的版本。
🔥 纯虚函数
纯虚函数(Pure Virtual Function)是一个在基类中声明的虚函数,但没有在基类中提供实现。它通过在函数声明的结尾处使用 = 0
来标记:
在很多情况下,基类生成对象很不合理。为了解决这个问题,引入了纯虚函数的概念,将函
数定义为纯虚函数,派生类中必须重写实现纯虚函数。对于实现了纯虚函数的子类,该纯虚
函数在子类中就变成了虚函数。
声明方式:
class Base {
public:
virtual void display() = 0; // Pure virtual function
};
无法实例化类:包含纯虚函数的类被称为抽象类(Abstract Class),不能直接创建实例对象。
强制派生类实现:派生类必须实现基类中的纯虚函数,否则它们也会成为抽象类,无法实例化。
代码示例:
#include <iostream>
// 抽象基类 AbstractBase
class AbstractBase {
public:
// 纯虚函数 display,没有默认实现
virtual void display() = 0;
};
// 派生类 Derived 实现抽象基类
class Derived : public AbstractBase {
public:
// 实现抽象基类中的纯虚函数 display
void display() override {
std::cout << "Display function of Derived class" << std::endl;
}
};
int main() {
// AbstractBase baseObj; // 不能实例化抽象类
Derived derivedObj; // 可以实例化派生类
AbstractBase* basePtr = &derivedObj; // 抽象基类指针指向派生类对象
basePtr->display(); // 调用派生类中实现的 display 函数
return 0;
}
AbstractBase
类中的display()
被声明为纯虚函数,没有提供默认实现,使得AbstractBase
成为抽象类,不能实例化。
Derived
类继承自AbstractBase
,必须实现AbstractBase
中的纯虚函数display
。在主函数中,派生类
Derived
被实例化,而抽象基类AbstractBase
的指针basePtr
可以指向Derived
类对象,并调用其实现的display()
函数。
无论虚函数还是纯虚函数,定义中都不能有 static 关键字。因为 static 关键字修饰的内容在编译前就要确定,而虚函数、纯虚函数是在运行时动态绑定的。
🔥 两者区别
虚函数 允许在派生类中重写函数,但可以有默认实现。它是可选的,可以在基类中提供实现。
纯虚函数 没有默认实现,派生类必须提供实现。它使得基类成为抽象类,不能实例化。
🔥 实践案例
假设我们有一个基类 Shape
,它定义了所有形状的基本属性和行为。我们希望能够计算各种形状的面积,但具体的面积计算方法因形状而异,因此我们可以使用虚函数和纯虚函数来达到这个目的。
🎯 首先,定义 Shape
类作为抽象基类,其中包含一个纯虚函数 area()
用于计算形状的面积:
#include <iostream>
// Abstract base class Shape
class Shape {
public:
// 纯虚函数用于计算面积
virtual double area() const = 0;
// 虚析构函数(对多态性很重要)
virtual ~Shape() {}
};
🎯 接着,我们可以创建不同类型的形状类(如矩形、圆形)作为 Shape
的派生类,并实现它们的具体面积计算方法。
创建一个矩形类 Rectangle
和一个圆形类 Circle
class Rectangle : public Shape {
private:
double width;
double height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
// 重写矩形的面积函数
double area() const override {
return width * height;
}
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
// 重写圆的面积函数
double area() const override {
return 3.14 * radius * radius;
}
};
🎯 我们可以使用这些类来计算具体形状的面积,而无需关心具体是哪种形状
int main() {
Rectangle rect(5, 3);
Circle circle(2.5);
// 使用 Shape 指针访问派生类 基类的指针指向了子类的对象
Shape *shape1 = ▭
Shape *shape2 = &circle;
// 使用虚函数计算并打印面积
std::cout << "Area of Rectangle: " << shape1->area() << std::endl;
std::cout << "Area of Circle: " << shape2->area() << std::endl;
return 0;
}
// 运行结果
Area of Rectangle: 15
Area of Circle: 19.625
通过使用虚函数
area()
和纯虚函数virtual double area() const = 0;
,我们实现了多态性,使得能够根据实际的对象类型来调用适当的面积计算方法。同时,基类Shape
的设计强制所有派生类实现area()
方法,确保了面积计算的统一性和规范性。
总结
在实际应用中,虚函数和纯虚函数结合使用,通常用来定义接口和基类的通用行为,同时强制派生类实现特定的行为,从而实现一种规范化的设计模式。