一、多态原理
1、下面这个结果是多少?
class A
{
public:
virtual void func()
{
cout << "func()" << endl;
}
private:
int _a = 1;
};
int main()
{
printf("%d\n", sizeof(A));
return 0;
}
是 4?8?还是多少?打印结果如下:
为什么是 8 呢?,通过调试看一下,如下:
通过调试可以看到里面不只是有 _a 成员,还多了一个成员 __vfptr,这个成员就是虚函数表指针,为什么会有虚函数表指针呢?因为虚函数的地址要存放到虚函数表中!并且含有虚函数的类中至少有一个虚函数表指针!
2. 上面的代码修改一下,如下
class A
{
public:
virtual void Func1()
{
cout << "A::Func1()" << endl;
}
virtual void Func2()
{
cout << "A::Func2()" << endl;
}
void Func3()
{
cout << "A::Func3()" << endl;
}
private:
int _a = 1;
};
class B : public A
{
public:
virtual void Func1()
{
cout << "B::Func1()" << endl;
}
private:
int _b = 2;
};
int main()
{
A a;
B b;
return 0;
}
调试看一下内部结构是什么,如下:
通过上面的调试可以看到:
(1) 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。
(2) 基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
(3)另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表。
(4)虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。
(5)总结一下派生类的虚表生成:
a.先将基类中的虚表内容拷贝一份到派生类虚表中
b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
(6)那么虚函数存在哪的?虚表存在哪的?
虚函数存在虚表?虚表存在对象中?
不对!!!虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。那么虚表存在哪的呢?实际我们去验证一下会发现vs下是存在代码段的。
通过上面的例子,下面正式看一下多态原理!!
上一篇文章讲了多态的条件,满足多态条件的时候,传给父类指针或者引用的时候,会完成不一样的调用,那么下面解释一下为什么,如下:
class Person {
public:
virtual void BuyTicket() { cout << "买票全价" << endl; }
};
class Student : public Person
{
public:
virtual void BuyTicket() { cout << "买票半价" << endl; }
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person q;
Func(q);
Student st;
Func(st);
return 0;
}
因为 q 传给 p 引用的时候,p 是指向 q 对象的,所以发生了切片,但是又因为都是Person这个类的,所以调用了自己的方法。
st 传给 p 引用的时候,p 是指向 st 对象的,student 又继承了Person类,并且重写了虚函数,形成了多态,但是 st 对象里面的虚函数是重写了之后的!而不是继承下来的虚函数!可以调试查看一下,如下:
虽然发生切片, p 指向 st 对象中 Person 前面的地址,但是因为是方法是重写的,所以调用的是student 的方法!
下面汇编代码看一下,如下:
多态的时候 call 的时候,call 的是寄存器!具体细节不展开讲了。
总之多态调用的时候,运行时去指向对象的虚表中找虚函数地址,进行调用!
那么不构成多态是什么呢?
class Person {
public:
virtual void BuyTicket() { cout << "买票全价" << endl; }
};
class Student : public Person
{
public:
//virtual void BuyTicket() { cout << "买票半价" << endl; }
virtual void Buy() { cout << "买票半价" << endl; }
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person q;
Func(q);
Student st;
Func(st);
return 0;
}
我随便破坏一个条件已经不构成多态,那么调用的时候是什么样呢? 如下:
可以看到 call 的直接是函数地址!
因为不满足多态的话就是普通函数调用,编译链接时候,就确认了函数地址,运行时直接调用。
二、单继承和多继承的虚函数表。
// 单继承
// 打印虚函数表
class Person
{
public:
virtual void BuyTicket()
{
cout << "Person::BuyTicket()" << endl;
}
virtual void func_person()
{
cout << "Person::func_person()" << endl;
}
};
class Student : public Person
{
public:
virtual void BuyTicket()
{
cout << "Student::BuyTicket()" << endl;
}
// 监视窗口查看 s 的虚表 里面查看不到 func_student() 函数
virtual void func_student()
{
cout << "Person::func_student()" << endl;
}
};
typedef void(*VFTABLE)();
void PRINT_VFTABLE(VFTABLE table[], int n)
{
//for (int i = 0; table[i] != nullptr; i++) // vs 在后面默认给的 0 ; 所以可以用 table[i] != nullptr; linux 需要显示写打印几个
for (int i = 0; i < n; i++)
{
printf("table[%d]->%p::", i, table[i]);
VFTABLE pf = table[i];
pf(); // 函数指针+() 调用自己对应的函数
}
cout << endl;
}
//int main()
//{
// Student s;
//
// Person p;
//
// //PRINT_VFTABLE((VFTABLE*)*(int*)&s);
// PRINT_VFTABLE((VFTABLE*)*(int*)&s, 3); // 子类有 3 个 显示写打印 3 个
// PRINT_VFTABLE((VFTABLE*)*(int*)&p, 2); // 父类有 2 个 显示写打印 2 个
//
// return 0;
//}
// 多继承
// 打印虚函数表
class Base1
{
public:
virtual void func1()
{
cout << "Base1::func1()" << endl;
}
virtual void func_Base1()
{
cout << "Base1::func_Base1()" << endl;
}
};
class Base2
{
public:
virtual void func1()
{
cout << "Base2::func1()" << endl;
}
virtual void func_Base2()
{
cout << "Base2::func_Base2()" << endl;
}
};
class Base3 : public Base1, public Base2
{
public:
virtual void func1()
{
cout << "Base3::func1()" << endl;
}
virtual void func_Base3()
{
cout << "Base3::func_Base3()" << endl;
}
};
typedef void(*VFTABLE)();
void PRINT_VFTABLE(VFTABLE table[])
{
for (int i = 0; table[i] != nullptr; i++)
{
printf("table[%d]->%p->", i, table[i]);
VFTABLE pf = table[i];
pf();
}
cout << endl;
}
int main()
{
Base3 b3;
Base1 b1 = b3;
Base2 b2 = b3;
PRINT_VFTABLE((VFTABLE*)*(int*)&b1);
PRINT_VFTABLE((VFTABLE*)*(int*)&b2);
PRINT_VFTABLE((VFTABLE*)*(int*)&b3);
return 0;
}
上面写了一个函数打印虚函数表,可以尝试调试打印看一下。