虚函数和虚表详细总结
一、虚函数 (Virtual Functions)
1. 定义
虚函数是基类中使用 virtual
关键字声明的成员函数,支持动态多态性。通过基类指针或引用调用虚函数时,会根据实际对象类型选择调用相应的函数实现。
2. 声明和定义
-
虚函数的声明:
class Base
{
public:
virtual void show(); // 声明虚函数
}; -
在派生类中重写虚函数:
class Derived : public Base
{
public:
void show() override; // 使用 override 关键字可选
};
3. 动态绑定
通过基类指针或引用调用虚函数时,C++ 编译器会进行动态绑定,决定在运行时调用哪个版本的函数。
Base* b = new Derived();
b->show(); // 输出:Derived class show() called.
//动态绑定
4. 纯虚函数
- 定义:没有实现的虚函数,用于定义接口,使用
= 0
声明。
class AbstractBase
{
public:
virtual void show() = 0; // 纯虚函数
};
包含纯虚函数的类称为抽象类,无法实例化。
二、虚表 (Vtable)
1. 定义
虚表是由编译器为每个包含虚函数的类生成的结构,包含该类所有虚函数的指针。
2. 虚表的结构
- 每个包含虚函数的类都有一个虚表,当调用的类为虚类时,会访问这个表,寻找具体实现。
- 如果子类未重写某个虚函数,则虚表中会指向父类的实现。
- 如果子类重写了某个虚函数,则虚表中会指向该类的实现.
3. 虚表的示例
class Base
{
public:
virtual void show() { std::cout << "Base class show()" << std::endl; }
};
class Derived : public Base {
// No override
};
int main() {
Base* b = new Derived();
b->show(); // Calls Base::show()
delete b;
return 0;
}
4. 内存布局
Base
的虚表:[0] -> Base::show()
Derived
的虚表(未重写的情况下):[0] -> Base::show() // 指向父类的实现
三、虚指针 (vptr)
- 每个对象实例中若包含虚函数,则会有一个虚指针,指向该对象所属类的虚表。
- 虚指针通常是对象的第一个成员。
虚指针为普通的指针,32位下4个字节,64位下8个字节。
四、虚函数的特点
-
动态多态性:通过基类指针或引用调用子类的实现,在运行时决定函数调用。
-
基类指针和引用:允许使用基类类型来引用和操作派生类对象。
-
封装和保护:基类不需要了解具体的派生类实现。
-
*!构造与析构的:构造时不调用派生类的虚函数,析构时确保派生类析构函数被调用。
-
性能开销:
- 空间开销:每个类需要存储虚表和每个对象需要一个虚指针。
- 时间开销:调用虚函数时需要通过虚表查找函数地址,相比于普通函数调用有额外的开销。
四、虚函数的使用场景
-
实现接口:通过纯虚函数定义接口类。
-
多态行为:在需要不同对象间以相同方式处理时(如图形、游戏对象等)。
-
框架设计:在库或框架设计中,通过虚函数提供扩展点,让用户自定义行为。
五、问题
1.哪些函数不能是虚函数
- 构造函数:无法被声明为虚函数。
- 内联函数:不支持虚函数的特性。
- 静态函数:由于不依赖于对象实例,无法是虚函数。
- 友元函数:不属于类的成员,不支持虚函数。