多态(知识点下)
1. 实现方式
从底层的⻆度Func函数中ptr->BuyTicket(),是如何作为ptr指向Person对象调 Person::BuyTicket, ptr指向Student对象调⽤Student::BuyTicket的呢?通过下图我们可以看到,满⾜多态条件后,底层不再是编译时通过调⽤对象确定函数的地址,⽽是运⾏时到指向的对象的虚表中确定对应的虚函数的地址,这样就实现了指针或引⽤指向基类就调⽤基类的虚函数,指向派⽣类就调⽤派⽣类对应的虚函数。第⼀张图,ptr指向的Person对象,调⽤的是Person的虚函数;第⼆张图,ptr指向的Student对象,调⽤的是Student的虚函数。
2.动态绑定与静态绑定
• 对不满⾜多态条件(指针或者引⽤+调⽤虚函数)的函数调⽤是在编译时绑定,也就是编译时确定调⽤ 函数的地址,叫做静态绑定。
• 满⾜多态条件的函数调⽤是在运⾏时绑定,也就是在运⾏时到指向对象的虚函数表中找到调⽤函数 的地址,也就做动态绑定。
cpp// ptr是指针+BuyTicket是虚函数满⾜多态条件。 // 这⾥就是动态绑定,编译在运⾏时到ptr指向对象的虚函数表中确定调⽤函数地址 ptr->BuyTicket(); 00EF2001 mov eax,dword ptr [ptr] 00EF2004 mov edx,dword ptr [eax] 00EF2006 mov esi,esp 00EF2008 mov ecx,dword ptr [ptr] 00EF200B mov eax,dword ptr [edx] 00EF200D call eax // BuyTicket不是虚函数,不满⾜多态条件。 // 这⾥就是静态绑定,编译器直接确定调⽤函数地址 ptr->BuyTicket(); 00EA2C91 mov ecx,dword ptr [ptr] 00EA2C94 call Student::Student (0EA153Ch)
3. 虚函数表
一、虚函数表基本特性
- 虚表内容:存储类所有虚函数的地址。
- 共享规则:同类型对象共用同一张虚表;不同类型对象拥有独立虚表(基类、派生类各有自己的虚表)。
- 本质:虚表是一个存储虚函数指针的函数指针数组。
- 结束标记:VS 编译器会在数组末尾存放
0x00000000作为结束标记; g++ 无此标记(C++ 标准未规定,由编译器实现)。二、派生类与虚表、虚指针
- 派生类构成:分为两部分 → 继承的基类部分 + 自身新增成员。
- 虚指针规则
- 基类部分自带虚函数表指针(vptr);
- 派生类不会额外生成新的虚指针;
- 派生类中基类部分的虚指针 ≠ 基类对象的虚指针(二者相互独立)。
- 虚表覆盖规则
- 派生类重写(override)基类虚函数时:派生类虚表中对应位置的函数地址会被覆盖为派生类自己的函数地址。
- 派生类虚表完整内容
① 未被重写的基类虚函数地址
②****被重写的基类虚函数 → 替换为派生类重写后的地址
③****派生类新增的虚函数地址
5.虚函数表存储地址
继承与多态复习汇总
派生类的析构函数往往还需要连同父类析构函数一起调用,同时清除父类的资源
维度 继承(is-a) 包含(has-a) 核心含义 逻辑上的 "父子关系",子类复用基类的定义 物理上的 "内存存在",对象里实实在在有这块数据 针对对象 针对类,描述类之间的关系 针对对象,描述对象的内存布局 基类非静态成员变量 子类继承了这些成员(但私有成员不能直接访问) 派生类对象一定包含这些成员变量(会生成基类子对象,占内存) 基类静态成员变量 子类会继承对它的访问权限(比如公有静态成员子类也能访问) 派生类对象不包含它(静态成员属于类,不属于任何对象) 基类私有成员 子类继承了,但不能直接访问 派生类对象包含它(内存里有,只是你不能直接用)
















