在C++中,虚函数和抽象基类是实现多态性的重要机制。多态性允许通过基类的指针或引用来调用派生类的函数,从而实现代码的灵活性和可扩展性。
一、虚函数
虚函数通过动态绑定(Dynamic Binding)在运行时决定调用哪个函数版本,而不是在编译时确定。这使得程序可以更加灵活,能够根据对象的实际类型调用相应的函数。
1、常规用法
虚函数就是在函数声明时使用关键字virtual
修饰的成员函数。在派生类中可以选择重写这个虚函数。如果基类指针或引用指向派生类对象,调用该函数时将根据对象的实际类型来选择合适的函数版本。
#include <iostream>
using namespace std;
class Base {
public:
//虚函数定义
virtual void virtualFunction() {
cout << "Base virtualFunction" << endl;
}
// 普通成员函数
void normalFunction() {
cout << "Base normalFunction" << endl;
}
};
class Derived : public Base {
public:
// 重写基类的虚函数,动态绑定
void virtualFunction() override {
cout << "Derived virtualFunction" << endl;
}
// 重写基类的普通函数,是静态绑定
void normalFunction() {
cout << "Derived normalFunction" << endl;
}
};
int main() {
// 基类指针指向派生类对象
Base* basePtr = new Derived();
basePtr->virtualFunction(); // 输出:Derived virtualFunction
basePtr->normalFunction(); //输出:Base normalFunction
return 0;
}
在 main()
函数中通过基类指针 basePtr
调用 virtualFunction()
函数时,发生了动态绑定。虽然 basePtr
的静态类型是 Base*
,但它实际上指向一个 Derived
对象,因此在运行时会调用 Derived
类的 virtualFunction()
。
具体过程:
-
编译时:编译器看到
basePtr->virtualFunction()
调用时,识别到virtualFunction
是一个虚函数。因此,编译器会在生成的代码中插入对虚函数表(vtable)的查找。 -
运行时:程序运行时,根据
basePtr
实际指向的对象类型(即Derived
对象),从虚函数表(vtable)中找到Derived
类的virtualFunction
函数的地址,并调用它。
2、注意点
-
使用场景:如果函数在不同派生类中行为不同,使用虚函数;否则无需使用虚函数。
-
使用规则:
- 派生类重写虚函数时,函数名、参数类型、顺序、个数以及返回类型必须与基类中的虚函数一致。否则,会被认为是重载,而非覆盖。
- 虚函数必须是类的成员函数,不能是友元函数或静态函数。
-
动态多态性与虚函数表(vtable):动态多态性通过基类指针或引用调用虚函数实现。运行时,程序根据对象的实际类型通过虚函数表找到并调用正确的函数版本。
-
构造函数与析构函数:构造函数不能是虚函数,但析构函数应声明为虚函数。这确保通过基类指针删除派生类对象时能正确调用派生类的析构函数,避免资源泄漏。
-
性能考虑:虚函数调用比普通函数略慢,因为需要通过虚函数表间接调用。
二、 纯虚函数
概述: 纯虚函数(Pure Virtual Function)是虚函数的一种特殊形式,它在基类中不提供任何实现,只定义接口。纯虚函数强制派生类必须实现该函数,否则派生类本身也会成为抽象类,无法实例化。
定义与用法: 纯虚函数通过在虚函数声明中使用= 0
的方式表示。
class Base {
public:
virtual void show() = 0; // 纯虚函数
};
class Derived : public Base {
public:
void show() override { // 实现纯虚函数
std::cout << "Derived show() called" << std::endl;
}
};
int main() {
Base* bp = new Derived();
bp->show(); // 输出:Derived show() called
}
- 纯虚函数使得类成为抽象类,抽象类不能直接实例化。只有在派生类实现了所有纯虚函数后,派生类才能实例化。
- 如果一个派生类没有实现所有继承的纯虚函数,那么这个派生类也将是一个抽象类。
三、抽象基类
抽象基类(Abstract Base Class, ABC)是包含一个或多个纯虚函数的类。它不能直接实例化,通常用于定义接口,规定派生类必须实现的功能。抽象基类是一种设计模式,用于确保所有派生类遵循相同的接口规范。
定义与用法: 抽象基类通常作为接口使用,定义了一系列派生类必须实现的功能。
#include <iostream>
using namespace std;
class AbstractBase {
public:
virtual void operation1() = 0; // 纯虚函数,抽象基类
virtual void operation2() = 0;
};
class DerivedAbstract : public AbstractBase {
public:
// 只重写了一个纯虚函数,因此本类仍然是抽象类
virtual void operation1() override {
cout << "DerivedAbstract operation1" << endl;
}
};
class ConcreteDerived : public DerivedAbstract {
public:
// 重写所有继承的纯虚函数,本类不是抽象类
virtual void operation1() override {
cout << "ConcreteDerived operation1" << endl;
}
virtual void operation2() override {
cout << "ConcreteDerived operation2" << endl;
}
};
int main() {
// AbstractBase base; // 报错:不能实例化抽象类
// DerivedAbstract derivedAbstract; // 报错:不能实例化抽象类
ConcreteDerived concreteDerived; // 可以实例化,因为所有纯虚函数已实现
AbstractBase* basePtr = &concreteDerived;
basePtr->operation1(); // 输出:DerivedAbstract operation1
basePtr->operation2(); // 输出:ConcreteDerived operation2
return 0;
}
抽象基类的特点:
1、抽象基类至少有一个纯虚函数;
2、抽象基类不能创建对象;
3、抽象基类要求派生类必须重写纯虚函数,以此来规范派生类的接口(函数);
4、派生类必须重写抽象基类中的所有纯虚函数
,否则也是抽象类,无法实例化对象。