class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _base = 1;
};
int main()
{
Base b;
return 0;
}
通过观察测试我们发现b对象中除了
_base
**成员,还多一个**_vfptr
放在前面(注意有些平台可能会放到对象的最后面,跟平台有关),对象中的这个指针我们叫做虚函数表指针 (v代表 virtual,f代表 function)。 一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中(其实是一个函数指针数组),虚函数表也简称虚表
利用下面的代码进一步讨论:
class Base
{
public:
virtual void Func1()
{
cout << "虚函数Base::Func1()" << endl;
}
virtual void Func2()
{
cout << "虚函数Base::Func2()" << endl;
}
void Func3()
{
cout << "不虚Base::Func3()" << endl;
}
private:
int _base = 1;
};
class Derive : public Base
{
public:
virtual void Func1()
{
cout << "虚函数Derive::Func1()" << endl;
}
private:
int _derive = 2;
};
int main()
{
Base bb;
Derive dd;
return 0;
}
在构造函数的初始化列表中,虚表指针(vptr)会被赋值为该类的虚函数表的首地址。这样,在对象的构造期间,虚表指针就已经指向了正确的虚函数表,从而确保在对象的构造期间就可以正确调用虚函数
Derive::Func1
**,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法Func2
**继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表**class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person& p)
{
p.BuyTicket();
}
void test4()
{
Person Mike;
Func(Mike);
Student Johnson;
Func(Johnson);
}
int main()
{
test4();
return 0;
}
其实这里还是利用了切割,编译器看到的都是父类,不过指向子类时里的父类是切割过去的而已,里面的虚表也是子类覆盖后的,找到的地址也是子类的虚函数的
两个问题:
比如:函数重载
在单继承和多继承关系中,下面来主要研究的是派生类对象的虚表模型,因为基类的虚表模型没什么需要特别研究的
class A {
public:
virtual void func1()
{
cout << "A::func1" << endl;
}
virtual void func2()
{
cout << "A::func2" << endl;
}
private:
int _a;
};
class B :public A {
public:
virtual void func1()
{
cout << "B::func1" << endl;
}
//这里B自己又多加了两个虚函数
virtual void func3()
{
cout << "B::func3" << endl;
}
virtual void func4()
{
cout << "B::func4" << endl;
}
private:
int _b;
};
int main()
{
B bb;
return 0;
}
我们也可利用下面函数来打印虚表内容进行验证
typedef void(*vfptr)();
void printvf(vfptr* ptr)
{
for (int i = 0; ptr[i] != nullptr; ++i)//以空指针结束
{
// 依次打印虚表各元素
printf(" 第%d个虚函数地址 :%p", i+1, ptr[i]);
// 把虚表各元素赋值给函数指针f
vfptr f = ptr[i];
// 调用函数
f();
}
cout << endl;
}
int main()
{
B bb;
printvf((vfptr*)(*(int*)&bb));
//先&bb地址出来,因为vs下虚表指针在最前面四个字节,利用强转为int*取到前四个字节
//然后*解引用得到地址,再强转为vfptr* ,进行调用函数
return 0;
}
class Base1
{
public:
virtual void func1() { cout << "Base1::func1" << endl; }
virtual void func2() { cout << "Base1::func2" << endl; }
private:
int b1;
};
class Base2
{
public:
virtual void func1() { cout << "Base2::func1" << endl; }
virtual void func2() { cout << "Base2::func2" << endl; }
private:
int b2;
};
class Derive : public Base1, public Base2
{
public:
virtual void func1() { cout << "Derive::func1" << endl; }
//自己又加上两个fun3,会在哪个虚表里填上呢
virtual void func3() { cout << "Derive::func3" << endl; }
private:
int d1;
};
int main()
{
Derive dd;
Base1* b1 = ⅆ
Base1* b2 = ⅆ
printvf((vfptr*)(*(int*)b1));
printvf((vfptr*)(*(int*)b2));
return 0;
}
是有两个虚表的,一个基类一个
观察上图可以看出:多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中
好啦,这次知识的内容就先到这里啦!多态在笔试当中选择题经常考察,在面试中也会问。以后大概率会对这部分进行梳理,感谢大家支持!!!