标签:并且 operator nes strong pen win10 lines main d3d
运行环境:win10+vs2015,数据以小端字节序存储
多态,顾名思义“多种状态”。
首先,要想知道多态的运行了机制,首先要了解一下对象的类型。
对象的类型:
举个例子:
class Base{};
class Deri1:public Base{};
class Deri2:public Base{};
int main(){
Deri1* p1 = new Deri1;//p1的静态类型是Deri1*,动态类型是Deri1*
Base* pBase = p1; //pBase的静态类型是Base*,动态类型是Deri1*
Deri2* p2 = new Deri2;
pBase = p2; //pBase的动态类型是Deri2*
}
由上可知,多态就是一个指针拥有多种动态类型。
多态分为两种:
静态多态
编译器在编译期间完成的,编译器更具函数实参的类型(可能会进行隐式类型转换),可推断出要调用哪个函数,如果有对应的函数就调用该函数,没有就会报错
动态多态
动态绑定:在程序执行期间(非编译期)判断所引用函数的实际类型,通过其实际类型调用相应的方法
动态绑定条件:
1. 必须是虚函数
2. 通过基类类型的引用或者指针调用虚函数
使用virtual关键字修饰类的成员函数,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定
例如
class Base {
public:
virtual void fun() {
cout << "Base::fun()" << endl;
}
};
class Deri :public Base {
public:
virtual void fun() {
cout << "Deri::fun()" << endl;
}
};
int main() {
Base* b=new Base;//此时指针b的动态类型是Base*,调用的是基类的fun()函数
b->fun();
b = new Deri;//此时指针b的动态类型是Deri*,调用的是派生类的fun()函数
b->fun();
return 0;
}
运行结果:
Base::fun()
Deri::fun()
这种就是只有程序运行到这里的时候才知道调用哪个函数,称为动态多态。
但是有引发了一个问题,基类和派生类的函数重名了,他们之间的关系是什么呢?
继承体系同名成员函数的关系
- 重载:在同一作用域,函数名相同、参数不同,返回值可以不同
- 重写(覆盖):不在同一作用域(分别在基类和派生类),函数名相同、参数相同、返回值相同(协变例外),基类函数必须有virtual关键字,访问修饰符可以不同
- 重定义(隐藏):在不同作用域中(分别在基类和派生类)、函数名相同、在基类和派生类中只要不构成重写的就是重定义
纯虚函数
在虚成员函数的形参后面写上“=0”,则成员函数为纯虚函数,包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象,纯虚函数在派生类中重定义以后,派生类才能实例化出对象。
虚表:
只要类中包含有虚函数,则这个类的对象就有一个虚表指针,指向虚表
虚表指针:是在类对象的头4个字节存入虚表的地址,里面包括该类的所有虚函数的地址,且由定义的顺序由声明顺序依次排序存于虚表中。在派生类中,前面是基类的虚函数,后面是派生类的虚函数。
派生类虚表的生成:
通过基类的引用或指针调用虚函数时,调用基类还是派生类的虚函数,要根据运行时所引用(指针)实际引用(指向)的类型确定;调用非虚函数时,则无论基类指向的是何种类型,都调用的是基类的函数
多态的内存结构:
单继承分为单虚继承和单非虚继承。
单非虚继承的内存结构没有偏移量表,其他内存分布与单虚继承相同
这里具体讨论单虚继承
单虚继承分为派生类重写基类虚函数、派生类新添虚函数、派生类既重写又新添虚函数。
例如:
class Base {
public:
virtual void fun() {
cout << "Base::fun()" << endl;
}
int b_data;
};
class Deri :public Base {
public:
virtual void fun() {
cout << "Deri::fun()" << endl;
}
int v_data;
};
int main() {
Deri* d = new Deri;
d->b_data = 1;
d->v_data = 2;
return 0;
}
该代码执行后的d所指向地址空间为
0x0123EDD0 50 9b 13 01 //指向虚函数表
0x0123EDD4 01 00 00 00 //基类成员变量
0x0123EDD8 02 00 00 00 //自己的成员变量
将继承改成虚继承后,即使用virtual关键字将程序改为:
class Deri :virtual public Base {
public:
virtual void fun() {
cout << "Deri::fun()" << endl;
}
int v_data;
};
d所指向空间的内存分布为
0x00C1D370 58 9b ef 00 //跟进这个内存可知其存放的是派生类对基类和自己的偏移量表
0x00EF9B58 00 00 00 00 //对自己的偏移量
0x00EF9B5C 08 00 00 00 //对基类的偏移量
0x00C1D374 02 00 00 00
0x00C1D378 50 9b ef 00 //跟进可知其指向的是一张虚表,由于满足重写的条件,所以只有一个函数指针
0x00EF9B50 7f 13 ef 00
0x00EF9B54 00 00 00 00
0x00C1D37C 01 00 00 00
如果派生类的成员函数与基类的成员函数名不同,即
class Deri :virtual public Base {
public:
virtual void fun1() {
cout << "Deri::fun1()" << endl;
}
int v_data;
};
运行后,派生类的内存分布:
0x009DEFB8 50 9b 31 00 //指向一个虚函数表
0x00319B50 83 14 31 00
0x00319B54 00 00 00 00
0x009DEFBC 88 9c 31 00 //指向偏移量表
0x00319C88 fc ff ff ff //对自己的偏移量
0x00319C8C 08 00 00 00 //对基类的偏移量
0x009DEFC0 02 00 00 00
0x009DEFC4 5c 9b 31 00 //指向一个虚函数表
0x00319B5C 69 10 31 00
0x00319B60 00 00 00 00
0x009DEFC8 01 00 00 00
重写基类函数并且自己新添虚函数
class Deri :virtual public Base {
public:
virtual void fun() {
cout << "Deri::fun()" << endl;
}
virtual void fun1() {
cout << "Deri::fun1()" << endl;
}
int v_data;
};
运行后派生类的内存结构
0x00B6BEF0 50 9b 2a 00 //指向虚函数的地址
0x002A9B50 83 14 2a 00
0x002A9B54 00 00 00 00
0x00B6BEF4 88 9c 2a 00 //偏移量表
0x002A9C88 fc ff ff ff
0x002A9C8C 08 00 00 00
0x00B6BEF8 02 00 00 00
0x00B6BEFC 5c 9b 2a 00 //虚函数表
0x002A9B5C 88 14 2a 00
0x002A9B60 00 00 00 00
0x00B6BF00 01 00 00 00
多继承
多继承也可以分为
例如:
class Base1 {
public:
virtual void fun() {
cout << "Base1::fun()" << endl;
}
int b1_data;
};
class Base2 {
public:
virtual void fun() {
cout << "Base2::fun()" << endl;
}
int b2_data;
};
class Deri :public Base1,public Base2 {
public:
int v_data;
};
int main() {
Deri* d = new Deri;
d->b1_data = 1;
d->b2_data = 2;
d->v_data = 3;
return 0;
}
程序运行后的d的内存结构
0x0111E6A8 a0 9c ff 00 //第一继承类虚表指针
0x00FF9CA0 a1 14 ff 00
0x00FF9CA4 00 00 00 00
0x0111E6AC 01 00 00 00
0x0111E6B0 94 9c ff 00 //第二继承类虚表指针
0x00FF9C94 9c 14 ff 00
0x00FF9C98 00 00 00 00
0x0111E6B4 02 00 00 00
0x0111E6B8 03 00 00 00
添加virtual关键字后,变成虚继承
class Deri :virtual public Base1,virtual public Base2 {
public:
int v_data;
};
运行后派生类的空间结构
0x007ADD48 88 9d a6 00 //派生类偏移量表
0x00A69D88 00 00 00 00
0x00A69D8C 08 00 00 00
0x007ADD4C 03 00 00 00
0x007ADD50 a0 9c a6 00 //第一继承虚函数表
0x00A69CA0 a1 14 a6 00
0x00A69CA4 00 00 00 00
0x007ADD54 01 00 00 00
0x007ADD58 94 9c a6 00 //第二继承虚函数表
0x00A69C94 9c 14 a6 00
0x00A69C98 00 00 00 00
0x007ADD5C 02 00 00 00
在派生类里面加一个与基类同名的函数
class Deri :virtual public Base1,virtual public Base2 {
public:
virtual void fun() {
cout << "Deri::fun()" << endl;
}
int v_data;
};
运行后d的内存空间结构
0x0089F318 88 9d a9 00 //指向派生类偏移量表
0x00A99D88 00 00 00 00
0x00A99D8C 08 00 00 00
0x0089F31C 03 00 00 00
0x0089F320 a0 9c a9 00 //第一继承虚函数表
0x00A99CA0 88 14 a9 00
0x00A99CA4 00 00 00 00
0x0089F324 01 00 00 00
0x0089F328 94 9c a9 00 //第二继承虚函数
0x00A99C94 8d 14 a9 00
0x00A99C98 00 00 00 00
0x0089F32C 02 00 00 00
将派生类改为虚继承且新添虚函数后
class Deri :virtual public Base1,virtual public Base2 {
public:
virtual void fun() {
cout << "Deri::fun()" << endl;
}
virtual void fun1() {
cout << "Deri::fun1()" << endl;
}
int v_data;
};
0x00BABE98 a0 9c 2f 00 //指向派生类虚表
0x002F9CA0 83 14 2f 00
0x002F9CA4 00 00 00 00
0x00BABE9C 94 9d 2f 00 //指向偏移量表
0x002F9D94 fc ff ff ff
0x002F9D98 08 00 00 00
0x00BABEA0 03 00 00 00
0x00BABEA4 94 9c 2f 00 //第一继承虚表
0x002F9C94 88 14 2f 00
0x002F9C98 00 00 00 00
0x00BABEA8 01 00 00 00
0x00BABEAC 8c 9d 2f 00 //第二继承虚表
0x002F9D8C 8d 14 2f 00
0x002F9D90 00 00 00 00
0x00BABEB0 02 00 00 00
菱形继承
菱形继承是一种特殊的继承,如图所示
未使用虚继承的菱形继承,但是会产生二义性
class A {
public:
virtual void fun() {
cout << "A::fun()" << endl;
}
int a_data;
};
class Base1:public A {
public:
virtual void fun() {
cout << "Base1::fun()" << endl;
}
int b1_data;
};
class Base2:public A {
public:
virtual void fun() {
cout << "Base2::fun()" << endl;
}
int b2_data;
};
class Deri :public Base1,public Base2 {
public:
int v_data;
};
int main() {
Deri* d = new Deri;
d->Base1::a_data = 4;
d->Base2::a_data = 5;
d->b1_data = 1;
d->b2_data = 2;
d->v_data = 3;
return 0;
}
运行后d的内存结构
0x00F1D300 84 9b 14 01
0x01149B84 4c 14 14 01
0x01149B88 00 00 00 00
0x00F1D304 04 00 00 00
0x00F1D308 01 00 00 00
0x00F1D30C 90 9b 14 01
0x01149B90 44 12 14 01
0x01149B94 00 00 00 00
0x00F1D310 05 00 00 00
0x00F1D314 02 00 00 00
0x00F1D318 03 00 00 00
使用虚函数且派生类添加自己的虚函数,则变成
class A {
public:
virtual void fun() {
cout << "A::fun()" << endl;
}
int a_data;
};
class Base1:virtual public A {
public:
virtual void fun() {
cout << "Base1::fun()" << endl;
}
int b1_data;
};
class Base2:virtual public A {
public:
virtual void fun() {
cout << "Base2::fun()" << endl;
}
int b2_data;
};
class Deri :public Base1,public Base2 {
public:
virtual void fun() {
cout << "Deri::fun()" << endl;
}
int v_data;
};
生成d的内存空间:
0x0078D3C0 9c 9b e6 00
0x00E69B9C 00 00 00 00
0x00E69BA0 14 00 00 00
0x0078D3C4 01 00 00 00
0x0078D3C8 c8 9c e6 00
0x00E69CC8 00 00 00 00
0x00E69CCC 0c 00 00 00
0x0078D3CC 02 00 00 00
0x0078D3D0 03 00 00 00
0x0078D3D4 7c 9b e6 00
0x00E69B7C 92 14 e6 00
0x00E69B80 00 00 00 00
0x0078D3D8 05 00 00 00
每层继承都有新添加的虚函数,也重写了基类的虚函数
class A {
public:
virtual void fun() {
cout << "A::fun()" << endl;
}
int a_data;
};
class Base1:virtual public A {
public:
virtual void fun() {
cout << "Base1::fun()" << endl;
}
virtual void fun2() {
cout << "Base1::fun2()" << endl;
}
int b1_data;
};
class Base2:virtual public A {
public:
virtual void fun() {
cout << "Base2::fun()" << endl;
}
virtual void fun3() {
cout << "Base2::fun3()" << endl;
}
int b2_data;
};
class Deri :public Base1,public Base2 {
public:
virtual void fun() {
cout << "Deri::fun()" << endl;
}
virtual void fun4() {
cout << "Deri::fun4()" << endl;
}
int v_data;
};
运行后d的内存空间
0x00BA9818 d4 9b 02 01
0x01029BD4 4c 14 02 01
0x01029BD8 01 14 02 01
0x00BA981C f8 9b 02 01
0x01029BF8 fc ff ff ff
0x01029BFC 18 00 00 00
0x00BA9820 01 00 00 00
0x00BA9824 e4 9b 02 01
0x01029BE4 29 14 02 01
0x01029BE8 00 00 00 00
0x00BA9828 04 9c 02 01
0x01029C04 fc ff ff ff
0x01029C08 0c 00 00 00
0x00BA982C 02 00 00 00
0x00BA9830 03 00 00 00
0x00BA9834 f0 9b 02 01
0x01029BF0 8e 13 02 01
0x01029BF4 00 00 00 00
0x00BA9838 05 00 00 00
上面的例子只是说明了一些分析方法的思想,还有很多种情况没有讨论,但是只要知道了这一种的分析方法,就可以得到其他的内存结构分布。
综上所述,派生类的结构与其继承的关系有关,但是整体位置思想是不变的,即:虚函数表–>偏移量表–>成员数据。
其他的都是一些基类与派生类的叠放次序问题。
如果想知道函数指针指向的的地址是什么,可用函数指针来访问,具体代码如下:
typedef void(*vpf)();
void PriFun(){
Deri* d=new Deri();
vpf* p = (vpf*)(*(int*)d);
while (*p != 0) {
(*p)();
p++;
}
}
该段代码可以查看派生类的第一张虚表里的函数,稍作修改即可查看派生类所有的虚表。
总结:
标签:并且 operator nes strong pen win10 lines main d3d
原文地址:http://blog.csdn.net/baidu_27435045/article/details/52988825