虚函数表

mac2026-06-20  1

虚函数表

C++中的虚函数的作用主要是实现了多态的机制。

多态:就是用父类型的指针指向其子类的实例。

虚函数是通过一张虚函数表(Virtual Table)实现的。在这个表中,主要是一个类的虚函数地址表,这张表解决了继承和覆盖等问题。就像一张地图一样,指明了指针实际所应该调用的函数。

举个例子:

class Base{ public: virtual void f() {cout << "Base::f" << endl;}; virtual void g() {cout << "Base::g" << endl;}; virtual void h() {cout << "Base::h" << endl;}; } typedef void(*Fun)(void); Base b; cout << "虚函数表地址" << (int*)(&b) << endl; // 获取虚函数表的地址 cout << "虚函数表第一个函数的地址"<< (int*)*(int*)(&b) << endl; // 获取虚函数表第一个函数的地址 pFun = (Fun)*((int*)*(int*)(&b)); pFun(); // 等价于Base::f() (Fun)*((int*)*(int*)(&b + 0)); //Base::f() (Fun)*((int*)*(int*)(&b + 1)); //Base::g() (Fun)*((int*)*(int*)(&b + 2)); //Base::h()

1、 单继承无虚函数覆盖

class Base{ public: virtual void func1(); virtual void func2(); virtual void func3(); } class Derived : public Base{ public: virtual void func4(); virtual void func5(); virtual void func6(); }

从虚函数表中我们可以看出:

(1)虚函数按照声明顺序依次放入表中。

(2)父类的虚函数放在子类的虚函数前面。

2、单继承又虚函数覆盖

class Base{ public: virtual void func1(); virtual void func2(); virtual void func3(); } class Derived: public Base{ public: virtual void func1(); virtual void func5(); virtual void func6(); }

从虚函数表中我们可以看出:

(1)覆盖的 f() 函数被放到了虚表中原来父类虚函数的位置。

(2) 没有被覆盖的依次存放。

3、 多继承无函数覆盖图解

class Base1{ public: virtual void func1(); virtual void func2(); virtual void func3(); } class Base2{ public: virtual void func1(); virtual void func2(); virtual void func3(); } class Base3{ public: virtual void func1(); virtual void func2(); virtual void func3(); } class Derived: public Base1, Base2, Base3{ public: virtual void func4(); virtual void func5(); virtual void func6(); }

我们可以看到: 1) 每个父类都有自己的虚表。 2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

4、 多继承有函数覆盖图解

class Base1{ public: virtual void func1(); virtual void func2(); virtual void func3(); } class Base2{ public: virtual void func1(); virtual void func2(); virtual void func3(); } class Base3{ public: virtual void func1(); virtual void func2(); virtual void func3(); } class Derived: public Base1, Base2, Base3{ public: virtual void func1(); virtual void func5(); virtual void func6(); }

我们可以看到: 1) 每个父类都有自己的虚表,被覆盖的放在父类原来的位置。 2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

5、 虚函数常见问题

5.1、 虚函数表的代价

(1)带有虚函数的每个类会产生一个虚函数表,用来存储虚成员函数的指针。

(2)带有虚函数的每个类都会有一个指向虚函数表的指针。

5.2、 那些函数不能是虚函数

(1)构造函数:对象的虚函数表指针需要通过构造函数初始化。

(2)内联函数:内联函数可以在编译阶段进行函数体的替换,而虚函数需要在运行期间进行确定。

(3)静态函数:静态函数不属于对象而属于类,因为静态成员函数没有this指针,所以无法访问对象的虚表指针,也就无法访问类的虚函数表,将静态函数设置成虚函数也就没有任何意义,所以c++语法不支持将静态函数设置成虚函数。

(4)友元函数:友元函数不属于类,也不能被继承,没有继承特性的函数没有虚函数的说法

(5)类外的普通函数:类外普通函数不是类的成员函数,同样不具备继承特性,也就没有虚函数的说法

5.3 虚函数和纯虚函数的区别

1)纯虚函数只有定义,没有实现,虚函数既有定义,又有实现

2)含有纯虚函数的类不能定义对象,含有虚函数的类可以定义对象

5.4、 虚析构函数的作用?父类的析构函数为什么一定要设置成虚函数?

父类虚析构函数就是为了避免内存泄漏,防止子类内存得不到释放造成内存泄漏。

1.当父类的析构函数不声明成虚析构函数时,当子类继承父类,父类指针指向子类对象,delete掉父类指针,只会调动父类的析构函数,而不会调用子类的析构函数,从而造成子类对象内存泄漏

2.当父类的析构函数声明成虚析构函数时,当子类继承父类,父类指针指向子类对象,delete掉父类指针,先调动父类的析构函数,然后调用子类的析构函数,不存在子类对象内存泄漏的问题

只要存在继承关系,则父类的析构函数必须定义成虚函数!

5.5、 构造函数和析构函数中为什么不可以调用虚函数?

1.构造子类对象时,首先调用父类构造函数初始化对象的父类部分,在执行父类的构造函数时,对象的子类部分都是未初始化的,实际上此时对象还不是一个子类对象。

2.析构子类对象时,先析构子类部分,然后按照构造顺序逆序析构父类部分。

所以在运行子类的构造和析构函数时,对象都是不完整的,为了适应这种不完整,编译器视对象类型为当前构造或析构函数所在类的类型,由此造成的结构就是:在父类的构造或析构函数中,会将子类对象当作父类对象看待。

如果我们在父类的构造或析构函数中调用虚函数,调用的往往是当前类的虚函数,达不到多态的效果,跟普通函数调用没有区别。

最新回复(0)