C++ 多态原理
用一个题目引入:
现有代码:
class Base
{
public:
virtual void func()
{
cout << "Base:func()" << endl;
}
protected:
int _a=1;
char _b='x';
};
void test1()
{
Base obj;
cout << sizeof(obj) << endl;
}
32位平台上输出的是12,按照我们之前学习的内容可知,类的大小实际上按照对齐规则计算类中成员变量的大小,那么这里应该是8;
输出12的原因:类含有虚函数,那么会类中会有一个指针(_vfptr),这个指针是一个指向函数指针数组的指针,也叫虚函数表指针或者虚表指针;指针的大小是4,指针指向一个数组,数组里面存放的是虚函数的地址,这个数组至少存放一个虚函数的地址;
这个指针不一定放在成员变量前面,这个根据平台而定;
一、多态是如何实现的?
在满足多态条件之后,程序运行到指向的对象的虚表中找到对应函数的地址进行调用;
父类有父类的虚表,子类有子类的虚表;
###代码演示:
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-打折" << endl; }
};
class Soldier : public Person {
public:
virtual void BuyTicket() { cout << "买票-优先" << endl; }
};
void Func(Person* ptr)
{
// 这⾥可以看到虽然都是Person指针Ptr在调⽤BuyTicket
// 但是跟ptr没关系,⽽是由ptr指向的对象决定的。
ptr->BuyTicket();
}
void test2()
{
Person p1;
Student st1;
Soldier so1;
Func(&p1);
Func(&st1);
Func(&so1);
}
二、虚表详析
1、对于基类,基类中的虚表存放基类所有虚函数的地址;
2、对于派生类,首先它继承基类中的虚函数表,两个虚函数表指针值不同,这个指针也指向基类中的那份虚函数表,那么派生类虚函数表中就存放有基类虚函数表中相同的函数地址,若是对其中某些虚函数进行了重写,那么派生类虚表的虚函数的地址就会改变;最后派生类虚表还有派生类自己的虚函数的地址;
###代码示例:
class A
{
public:
virtual void func1()
{
cout << "A::func1()" << endl;
}
virtual void func2()
{
cout << "A::func2()" << endl;
}
void func3()
{
cout << "A::func3()" << endl;
}
};
class B :public A
{
public:
};
class C :public A
{
public:
virtual void func1()
{
cout << "C::func1()" << endl;
}
virtual void func2()
{
cout << "C::func2()" << endl;
}
virtual void func3()
{
cout << "C::func3()" << endl;
}
};
void test3()
{
A a1;
B b1;
C c1;
}
首先A是一个基类,那么它的虚函数表中有两个虚函数;然后是b1,B类中没有内容,但是它继承A,那么就继承了A中的虚表,但是两个虚表指针不同(也就是_vfptr),但是指向的虚表相同,所以a1和b1虚表中函数地址都相同;C也继承了A,但是C中对虚函数进行了重写,所以虚表中的函数地址也不同了,这里的c1 func3是要显示的,这里出错了。
3、虚函数和普通函数一样存放到代码段,虚函数表中存的只是这些虚函数的地址;
4、在vs下,虚函数表存放到代码段(常量区)
###代码示例:
class Base {
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
void func5() { cout << "Base::func5" << endl; }
protected:
int a = 1;
};
class Derive : public Base
{
public:
// 重写基类的func1
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func1" << endl; }
void func4() { cout << "Derive::func4" << endl; }
protected:
int b = 2;
};
void test4()
{
int i = 0;
static int j = 1;
int* p1 = new int;
const char* p2 = "xxxxxxxx";
printf("栈:%p\n", &i);
printf("静态区:%p\n", &j);
printf("堆:%p\n", p1);
printf("常量区:%p\n", p2);
Base b;
Derive d;
Base* p3 = &b;
Derive* p4 = &d;
printf("Person虚表地址:%p\n", *(int*)p3);
printf("Student虚表地址:%p\n", *(int*)p4);
printf("虚函数地址:%p\n", &Base::func1);
printf("普通函数地址:%p\n", &Base::func5);
}
虚表的地址和常量区的最近,所以vs下虚表是在常量区的;
三、动态绑定和静态绑定
1、静态绑定就是在对不满足多态条件的函数调用在编译时绑定,在编译时调用函数的地址;
2、动态绑定就是对满足多态条件的函数在运行时通过虚表调用函数的地址;