虚函数
在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数称为虚函数,语法格式:
virtual 成员函数名()
重写:
- 必须是虚函数
- 子类定义了一个和父类完全相同的成员函数,包括返回值类型、函数名、参数列表
- 重写也称为覆盖
上代码镇楼:
class A {
public:
virtual void func()
{
cout << "A::func()" << endl;
}
};
class B :public A {
public:
virtual void func()
{
cout << "B::func()" << endl;
}
};
int main()
{
A* p=new B;
p->func();//输出"B::func()",子类的func覆盖掉了父类的func
system("pause");
return 0;
}
虚表
1. 虚表也称为虚函数表,顾名思义就是用来存放虚函数地址的一个抽象表格。这个表格是我们人为抽象出来的。
一个对象可能有多个虚函数,所以虚表的本质是一个指针数组,用来存放虚函数地址的数组。这个数组的最后一个元素是0.
编译器会为包含虚函数的类加上一个成员变量,是一个指向该虚函数表的指针(常被称为vptr),每一个由此类别派生出来的类,都有这么一个vptr。虚表指针是从属于对象的。也就是说,如果一个类含有虚表,则该类的所有对象都会含有一个虚表指针,并且该虚表指针指向同一个虚表。
2. 虚表的内容是依据类中的虚函数声明次序填入函数指针。派生类会继承基类的虚表(以及所有其他可以继承的成员),当我们在派生类中改写虚函数时,虚表就受了影响;表中的元素所指的函数地址将不再是基类的函数地址,而是派生类的函数地址。
3.打印虚表:
有一句话,看到的往往不是真的,我们在监视窗口看的好像就是这么一回事,但是万一不对呢,为了解决这个困扰,我们将虚函数打印出来。怎么打呢?前面说过,虚表的本质是一个指针数组,里面存放的是放进去的虚函数地址,并且最后一个元素是0,我们就像打印数组一样打印虚表,遇0结束。
typedef void(*FUNC)();//函数指针
void PrintVTable(int* VTable)
{
cout << " 虚表地址>" << VTable << endl;
for (int i = 0; VTable[i] != 0; ++i)
{
printf(" 第%d个虚函数地址 :0X%x->", i, VTable[i]);
FUNC f = (FUNC)VTable[i];
f();//调用对应函数
}
cout << endl;
}
class Base {
public:
Base()
:_a(1)
{}
virtual void func1()
{
cout << "Base::func1()" << endl;
}
virtual void func2()
{
cout << "Base::func2()" << endl;
}
private:
int _a;
};
int main()
{
Base b;
int* vtable = (int*)(*(int*)&b);
PrintVTable(vtable);
system("pause");
return 0;
}
/*输出结果:
虚表地址>00F79B64
第0个虚函数地址 :0Xf710dc->Base::func1()
第1个虚函数地址 :0Xf7139d->Base::func2()
*/
虚表在几种继承下的情况
1.单继承:(无重写)
class Base {
public:
Base()
:_a(1)
{}
virtual void func1()
{
cout << "Base::func1()" << endl;
}
virtual void func2()
{
cout << "Base::func2()" << endl;
}
private:
int _a;
};
class Derive :public Base {
public:
Derive()
:_b(2)
{}
virtual void func3()
{
cout << "Derive::func3()" << endl;
}
virtual void func4()
{
cout << "Derive::func4()" << endl;
}
private:
int _b;
};
int main()
{
Base b;
Derive d;
system("pause");
return 0;
}
2.单继承:(有重写)
重写子类的虚函数会覆盖掉父类对应的虚函数,所以在虚表中也会发生相应的变化。
class Base {
public:
Base()
:_a(1)
{}
virtual void func1()
{
cout << "Base::func1()" << endl;
}
virtual void func2()
{
cout << "Base::func2()" << endl;
}
private:
int _a;
};
class Derive :public Base {
public:
Derive()
:_b(2)
{}
virtual void func2()
{
//重写了父类base的func2函数
cout << "Derive::func2()" << endl;
}
virtual void func3()
{
cout << "Derive::func3()" << endl;
}
private:
int _b;
};
int main()
{
Base b;
Derive d;
system("pause");
return 0;
}
那么对于上述代码,我们现在就能很轻松的明白Base和Derive的大小:
cout << sizeof(Base) << endl;//8
cout << sizeof(Derive) << endl;//12
3.多继承(非菱形继承)
对于多继承关系而言,情况又变的复杂:
class A {
public:
virtual void func1()
{
cout << "A::func1()" << endl;
}
int _a;
};
class B {
public:
virtual void func2()
{
cout << "B::func2()" << endl;
}
int _b;
};
class C :public B, public A {
public:
virtual void func1()//重写父类A的虚函数func1
{
cout << "C::func1()" << endl;
}
virtual void func3()
{
cout << "C::func3()" << endl;
}
int _c;
};
int main()
{
A a;
B b;
C c;
c._a = 1;
c._b = 2;
c._c = 3;
//求:
cout << sizeof(a) << endl;
cout << sizeof(b) << endl;
cout << sizeof(c) << endl;
system("pause");
return 0;
}
对于多继承,我们首先要明白几个概念:
- 如果父类都有虚表,那么子类会继承所有父类的虚表,并且将自己的虚表指针放在第一个被继承的父类的虚表中。
- 有几个含有虚表的父类,子类就有几个虚表,比如上述例子子类C继承了父类B、A,并且两个父类都有虚表,所以子类C就有两个虚表,即两个虚表指针。
如果子类和父类的虚函数构成重写,则多继承的重写规则和单继承一样,即子类的虚函数会在虚表中覆盖掉父类对应的虚函数。子类重写了哪个父类的函数,子类就将这个函数存放在那个父类的虚表中。
画图对上述代码解析:
回过头我们对上述代码的问题就能迎刃而解:
cout << sizeof(a) << endl;//8
cout << sizeof(b) << endl;//8
cout << sizeof(c) << endl;//20
4.多继承(菱形继承)
对于菱形继承而言,为了解决二义性的问题,我们引入了虚继承,即用虚基表来解决了二义性,所以在此我们需要分析虚基表和虚表。
首先我们要严格区分开虚基表和虚表,两者毫无关系可言。虚基表是虚继承产生的,虚表是多态产生的(即必须有虚函数)。
对于菱形继承,它也有几点规则,掌握了规则就好分析问题:
- 子类有几个拥有虚表的父类,就拥有几张虚表
- 有几个虚继承同一个类的父类,子类就有几个虚基表指针
- 因为是虚继承,对于共同的父类,子类将这个父类的虚表放在公共区域。
class A//8
{
public:
virtual void fun1()
{
printf("A::virtual void fun(int n)\n");
}
int _a;
};
class B :virtual public A//虚继承
{
public:
virtual void fun2()//8+8+4
{
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 C, public B
{
public://大小:虚继承情况下基类A的大小(虚表指针+int(_a))+B的大小(int(_b)+虚基表指针+虚表指针)
//+C的大小(int(_c)+虚基表指针+虚表指针)+int(_d)
int _d;
};
int main()
{
A a;
B b;
C c;
D 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;
}
简单来说,对于菱形继承来说,子类的对象模型可以简单划分为: