C++:虚函数、虚表和纯虚函数

1.虚函数

(1)定义:

在成员函数前加virtual,则这个成员函数被称为虚函数

(2)虚函数重写(又叫覆盖)

子类定义了一个与父类中完全相同的虚函数,则称子类重写(或覆盖)了父类的虚函数
完全相同是指函数名、返回值、形参列表都相同
③有一种情况特殊:协变协变是指子类和父类中的虚函数的返回值为分别为子类指针和父类指针

//虚函数重写实例如下:
//完全相同,fun函数构成重写
class A
{
public:
    virtual void fun(int n)
    {
        printf("A::virtual void fun(int n)\n");
    }
private:
    int _a;
};
class B :public A
{
public:
    virtual void fun(int n)
    {
        printf("B::virtual void fun(int n)\n");
    }
private:
    int _b;
};
//协变
class A
{
public:
    virtual A* fun(int n)
    {
        printf("A::virtual void fun(int n)\n");
    }
private:
    int _a;
};
class B :public A
{
public:
    virtual B* fun(int n)
    {
        printf("B::virtual void fun(int n)\n");
    }
private:
    int _b;
};

2.虚表

(1)定义:

存放虚函数的地址的表,称为虚表
这里的地址并非函数的真正地址,而是call指令的跳转地址,后面再解释,先作为一个简单了解

(2)虚表的本质

虚表是一种数据结构,它实质是一个指针数组,存放虚函数的地址,它最后一个元素存放0

(3)使用内存窗口验证虚表的存在和结构

#include<iostream>
using namespace std;

class A
{
public:
    A()
        :_a(1)
    {}
    virtual void fun(int n)
    {
        printf("A::virtual void fun(int n)\n");
    }
private:
    int _a;
};

int main()
{
    A a;
    printf("%p\n", &a);
    printf("sizeof(A):%d\n",sizeof(A));
    return 0;
}

这里写图片描述
这里写图片描述

(4)使用函数打印虚表,验证虚表的存在和结构

#include<iostream>
using namespace std;

typedef void(*VFunc)();//函数指针

class A
{
public:
    virtual void fun()
    {
        printf("A::virtual void fun(int n)\n");
    }
private:
    int _a;
};

void PrintVTable(int* vtable)
{
    int i = 0;
    //将虚函数表的内容打印,除最后一个元素
    //并将每个元素(即虚函数指针)通过VFunc调用一次
    for (i = 0; vtable[i] != 0; i++)
    {
        printf("vtable[%d]:%p\n", i, vtable[i]);
        VFunc f = (VFunc)vtable[i];
        f();
    }
    //验证虚函数表的最后一个元素为0
    printf("vtable[%d]:%p\n", i, vtable[i]);
}

int main()
{
    A a;
    PrintVTable((int*)(*(int*)&a));
    return 0;
}

这里写图片描述

(5)总结:对象的结构和虚表示意图

这里写图片描述

3.虚表在继承中的情况

(1)单继承:不构成重写的情况

#include<iostream>
using namespace std;

class A
{
//成员都设为public
public:
    virtual void fun1()//函数名为fun1,与类B中的不同,不构成重写
    {
        printf("A::virtual void fun(int n)\n");
    }
    int _a;
};

class B :public A
{
//成员都设为public
public:
    virtual void fun2()//函数名为fun2,与类A中的不同,不构成重写
    {
        printf("B::virtual void fun(int n)\n");
    }
    int _b;
};
int main()
{
    A a;
    B b;
    a._a = 1;
    b._a = 2;
    b._b = 3;
    printf("%p\n", &a);
    printf("%p\n", &b);
    return 0;
}

这里写图片描述
这里写图片描述
这里写图片描述

(2)单继承:构成重写的情况

#include<iostream>
using namespace std;

class A
{
public:
    virtual void fun1()//函数fun1与类B中的完全相同,构成重写
    {
        printf("A::virtual void fun(int n)\n");
    }
    int _a;
};

class B :public A
{
public:
    virtual void fun1()//函数fun1与类A中的完全相同,构成重写
    {
        printf("B::virtual void fun(int n)\n");
    }
    int _b;
};

void PrintVTable(int* vtable)
{
    int i = 0;
    for (i = 0; vtable[i] != 0; i++)
    {
        printf("vtable[%d]:%p\n", i, vtable[i]);
        VFunc f = (VFunc)vtable[i];
        f();
    }
    printf("vtable[%d]:%p\n", i, vtable[i]);
}

int main()
{
    A a;
    B b;
    a._a = 1;
    b._a = 2;
    b._b = 3;
    printf("%p\n", &a);
    printf("%p\n", &b);
    return 0;
}

这里写图片描述
这里写图片描述
这里写图片描述

(3)多继承:非菱形继承

#include<iostream>
using namespace std;

class A
{
public:
    virtual void fun1()
    {
        printf("A::virtual void fun(int n)\n");
    }
    int _a;
};

class B
{
public:
    virtual void fun2()
    {
        printf("B::virtual void fun(int n)\n");
    }
    int _b;
};

class C:public A,public B
{
public:
    int _c;
};

int main()
{
    A a;
    B b;
    C c;
    a._a = 1;
    b._b = 2;
    c._a = 3;
    c._b = 4;
    c._c = 5;
    printf("%p\n", &a);
    printf("%p\n", &b);
    printf("%p\n", &c);
    return 0;
}

这里写图片描述
这里写图片描述
这里写图片描述

(4)多继承:菱形继承

#include<iostream>
using namespace std;
class A
{
public:
    virtual void fun1()
    {
        printf("A::virtual void fun(int n)\n");
    }
    int _a;
};

class B:virtual public A//虚继承
{
public:
    virtual void fun2()
    {
        printf("B::virtual void fun(int n)\n");
    }
    int _b;
};

class C :virtual public A//虚继承
{
public:
    virtual void fun3()
    {
        printf("C::virtual void fun(int n)\n");
    }
    int _c;
};

class D :public B, public C
{
public:
    int _d;
};

int main()
{
    A a;
    B b;
    C c;
    D d;
    a._a = 1;
    b._a = 2;
    b._b = 3;
    c._a = 4;
    c._c = 5;
    d._a = 6;
    d._b = 7;
    d._c = 8;
    d._d = 9;
    printf("%p\n", &a);
    printf("%p\n", &b);
    printf("%p\n", &c);
    printf("%p\n", &d);
    return 0;
}

这里写图片描述

4.“虚表在继承中的情况”的总结/概括

1.单继承:不构成重写
父类和子类各自有个虚表,子类的虚表内包括父类虚表内所有的指针
2.单继承:构成重写
父类和子类各自有个虚表,子类的虚表内的指针与父类虚表内都不相同
这也是多态能根据指针指向的对象类型决定调用的成员虚函数的原因
3.多继承:非菱形继承
①如果父类都有虚表,则子类会继承所有父类的虚表,并且,先继承哪个父类,子类的虚函数指针就会放在哪个父类对应的虚表中
②考虑构成重写的情况时,多继承的重写和单继承的重写规则一样
4.多继承:菱形继承
(1)虚函数虚继承
如果是虚继承的话,子类的虚表内容只包含自己的虚函数指针,把父类的虚函数指针放在了公共区,并把这个公共区的地址放在了对象的里
(2)菱形继承
①有几个“含有虚表的父类”,子类就有几个虚表
②有几个“虚继承同一个类的父类”,子类就有几个虚基表指针
③子类还包含一个”虚函数虚继承产生的公共区的指针“

5.纯虚函数

(1)定义

在虚函数后面赋值0,eg:virtual void fun()=0;//只声明不实现
该虚函数被称为纯虚函数

(2)抽象类/接口类

含有纯虚函数的类,被称为抽象类或接口类,抽象类/接口类不能实例化出对象

(3)抽象类/接口类的意义

抽象类的存在,使得子类必须重写纯虚函数,只有重写纯虚函数后,子类才可以实例化出对象。

猜你喜欢

转载自blog.csdn.net/w_y_x_y/article/details/80036144