欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

c++ 基础系列]虚函数和虚表

最编程 2024-04-14 09:30:51
...

                          【c++基础系列】虚函数&虚表

    

   大家好,我是Lampard~~

   欢迎来到c++基础系列的博客

   前文再续,书接上一回。今天和大家讲解一下c++中的虚函数&虚表

 

  虚函数

  我们刚才实例化对象的时候,示例一直猪是这样写的:

Pig* pig = new Pig;

  但因为其是继承Animal动物类的,所以说我们其实也可以这样写:

Animal* pig = new Pig;

  这种写法一般情况下没有毛病,但是如果子类重写了基类的方法时,就会出现问题:调用的还是基类的方法。

 原因在于c++编译器遵循快的原则,在Animal中找到函数则直接就调用了,不再从子类中寻找

 为了避免这种情况,我们可以在基类的方法定义时,加上一个virtual的关键字,这样的话,若子类没有重写这个方法,那么就会调用父类的方法,若子类重写了该方法,那么就会调用子类的方法。

 这就是虚函数,能够让程序的行为符合你预期的实现,同样是c++多态性(编译时,动态)的一部分。

纯虚函数:

实现纯虚函数(抽象方法)很简单,只要在虚函数的基础上=0就可以了。它的目的是让子类才实现这个方法,而基类就先不定义这个函数体。

而它有以下两个限制:1.纯虚函数子类必须重写,2.含抽象方法的类就是抽象类,而抽象类则不可以实例化

也就是说,如果你对Animal类的eat方法写成这个样子

virtual void eat() = 0;

 那么animal就是一个抽象类,不能被实例化。而从实际意义上说,你可以示例话一只猪,一只狗,实例化一个动物就是很不合理的

 

虚表

那么是怎么实现虚函数这个机制的呢?

这个技术的核心是虚函数表,类的对象内部会有指向类内部的虚表地址的指针(__vptr字段记录)。通过这个指针调用虚函数。虚表是属于类的,而不是属于某个具体的对象,一个类只需要一个虚表即可。同一个类的所有对象都使用同一个虚表。

单继承

这种情况下,派生类中仅有一个虚函数表。这个虚函数表和基类的虚函数表不是一个表(无论派生类有没有重写基类的虚函数),但是如果派生类没有重写基类的虚函数的话,基类和派生类的虚函数表指向的函数地址都是相同的。

class A1
{
public:
    A1(int _a1 = 1) : a1(_a1) { }
    virtual void f() { cout << "A1::f" << endl; }
    virtual void g() { cout << "A1::g" << endl; }
    virtual void h() { cout << "A1::h" << endl; }
    ~A1() {}
private:
    int a1;
};
class C : public A1
{
public:
    C(int _a1 = 1, int _c = 4) :A1(_a1), c(_c) { }
private:
    int c;
};

类C没有重写A的虚函数,所以虚函数表内部的情况如下: 

_vptr指向的地址(虚表的地址)不一样,但是里面的虚函数地址还是指向同一个。

那么如果子类重写了积累的虚函数会怎么样呢?

class C : public A1
{
public:
    C(int _a1 = 1, int _c = 4) :A1(_a1), c(_c) { }
    virtual void f() { cout << "C::f" << endl; }
    virtual void g() { cout << "C::g" << endl; }
    virtual void h() { cout << "C::h" << endl; }
private:
    int c;
};

那么子类虚表中所记录的虚函数地址就会发生变化。已经和基类不是指向同一个函数了。

如果C中写了一些别的虚函数,那么这些虚函数将排在父类的后面

多继承

多继承情况下,派生类中有多个虚函数表,虚函数的排列方式和继承的顺序一致。派生类重写函数将会覆盖所有虚函数表的同名内容,派生类自定义新的虚函数将会在第一个类的虚函数表的后面进行扩充。

 

好,今天的分享就到这里,祝各位功力渐长平步青云,谢谢大家~~