前言
虚函数使得面向对象编程中的多态性得以实现,能够更灵活地处理不同派生类的对象,提高代码的可扩展性和可维护性。
虚函数
虚函数(virtual function)是在面向对象编程中用于实现动态多态性的一种机制。通过将基类中的成员函数声明为虚函数,可以在派生类中重写(override)这些函数,从而根据对象的实际类型确定调用的函数版本。
声明方式:在基类中用 virtual
关键字声明的函数称为虚函数。
class base {
public:
virtual void display() {
// base class implementation
}
};
多态调用:通过基类指针或引用调用虚函数时,实际调用的是指向对象的派生类版本(如果派生类重写了这个函数)。
动态绑定:在运行时根据对象的实际类型来确定调用的函数版本,而不是在编译时静态确定。
虚函数表(vtable):编译器通常通过添加一个指向虚函数表的指针来实现虚函数的机制。虚函数表存储了每个类的虚函数的地址。
代码示例:
#include
// 基类 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
// 抽象基类 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
// 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()
方法,确保了面积计算的统一性和规范性。
总结
在实际应用中,虚函数和纯虚函数结合使用,通常用来定义接口和基类的通用行为,同时强制派生类实现特定的行为,从而实现一种规范化的设计模式。