#include <iostream> using namespace std; 问题导出: //1 函数重写print //2赋值兼容性原则(把子类对象赋给父类指针或者引用) //3函数重写遇上赋值兼容性原则 //这个就是面向对象的新需求 //针对这个函数void howToPrintf(Parent *base),我希望是 //如果传来父类对象,那么应该执行父类函数 //如果传来子类对象,那么执行子类函数 但是不管如何调用总是调用父类方法。。。 //多态 //c++编译器给我们提供的多态方案是 虚函数
class Parent { public: Parent(int a = 0) { this->a = a; } virtual void print() { cout<<"父类a"<<a<<endl; } protected: private: int a; };
class Child : public Parent { public: Child(int b = 0) { this->b = b; } virtual void print() { cout<<"子类b"<<b<<endl; } protected: private: int b; }; void howToPrintf(Parent *base) { base->print(); //同样一句话,能实现多种功能;有多种形态 } void howToPrintf2(Parent &base) { base.print(); } void main() { Parent p1; //正常 //p1.print(); Child c1; //c1.print();//正常 Parent *base = NULL; base = &p1; //base->print(); //只调用父类方法 base = &c1; //base->print(); //没有打印子类的函数,只调用父类方法 // p2 是c1的别名 ,是c1本身 Parent &p2 = c1; //p2.print();//只调用父类方法
//函数参数 howToPrintf(&p1); //只调用父类方法 howToPrintf(&c1); //只调用父类方法 system("pause"); } |
静态联编:编译时,编译器自动根据指针的类型判断指向什么样的对象 当加上virtual关键字时候,编译器会动态联编(当编译器调用该方法时,如果发现该方法是虚函数,则会进行迟绑定) |
多态案例 |
#include <iostream> using namespace std; class HeroFighter { public: virtual int AttackPower() { return 10; } }; class EnemyFighter { public: int DestoryPower() { return 15; } };
class HeroAdv2Fighter : public HeroFighter { public: int AttackPower() { //HeroFighter::AttackPower()*2; return 20; } protected: private: };
//一个模型 如果你把这个函数做成框架那。。。。。。。 void ObjFighter(HeroFighter *pBase, EnemyFighter *pEnemy) { if (pBase->AttackPower() > pEnemy->DestoryPower()) { printf("主角win\n"); } else { printf("主角挂了\n"); } } void main81() { HeroFighter h1; EnemyFighter e1; HeroAdv2Fighter hAdvF;
if (h1.AttackPower() > e1.DestoryPower()) { printf("主角win\n"); } else { printf("主角挂了\n"); } if (hAdvF.AttackPower() > e1.DestoryPower()) { printf("主角win\n"); } else { printf("主角挂了\n"); } system("pause"); } void main() { HeroFighter h1; EnemyFighter e1; HeroAdv2Fighter hAdvF; ObjFighter(&h1, &e1); ObjFighter(&hAdvF, &e1); system("pause"); } |
面向对象三大概念意义: 继承:避免重复造轮子,代码复用 封装:防止外界修改类内部属性,安全,突破了面向过程的局限 多态:相同外部接口,不同内部实现,简化代码(配合继承,可以实现调用未来的代码,更适合功能扩展) |
间接赋值的三个条件: 1 定义两个变量 1个实参1个形参 2 建立关联 实参取地址传给形参 3 *p (p是实参地址) 间接修改实参值 动态多态成立三个条件: 1 要有继承 2 要有重写virtual函数 3 要有父类指针指向子类 |
重载(overload)和覆盖(override)都是实现多态的手段,其中重载 是静态多态实现,在程序编译时实现;覆盖 是动态多态实现,在程序运行时实现。 重载:重载函数定义在全局或某个类中,要求同名但参数个数或类型不同的函数,当在父子关系中出现函数名相同但参数不同的函数,不论是否有virtual,其基类的方法都会被覆盖,调用时需在父类的成员函数前加T::进行区分(T是父类名) 覆盖:1两个函数必须出现在不同域中(如父子类) 2 函数名 参数 返回值 必须都相同 |
#include <cstdlib> #include <iostream> using namespace std; //重载只放在在一个类里面 ,在编译期间就确定 class Parent01 { public: Parent01() { cout<<"Parent01:printf()..do"<<endl; } public: void aaaaa() { ; } void func() { cout<<"Parent01:void func()"<<endl; }
void func(int i) { cout<<"Parent:void func(int i)"<<endl; }
virtual void func(int i, int j) { cout<<"Parent:void func(int i, int j)"<<endl; } }; //重写 父子类之间,函数三要素(函数名、函数参数、函数返回类型)完全一样 //重写又分为两种 //如果父类中有virtual关键字,这种父子之间的关系叫做虚函数重写,这种情况下发生多态 (动态链编 迟绑定) //如果父类中没有virtual关键字,这种父子之间的关系 重定义 (静态链编) class Child01 : public Parent01 {
public:
//此处2个参数,和子类func函数是什么关系 void func(int i, int j) { cout<<"Child:void func(int i, int j)"<<" "<<i + j<<endl; } //我想在子类中重载父类的func函数 ====》 //此处3个参数的,和子类func函数是什么关系 void func(int i, int j, int k) { cout<<"Child:void func(int i, int j, int k)"<<" "<<i + j + k<<endl; } };
void run01(Parent01* p) { p->func(1, 2); }
int main() { Parent01 p;
p.func(); p.func(1); p.func(1, 2); Child01 c; //c.func(); //注意1 c.Parent01::func(); //c.aaaaa(); //c.func(1, 2); run01(&p); run01(&c); system("pause"); return 0; } //问题1:child对象继承父类对象的func,请问这句话能运行吗?why //c.func(); //1子类里面的func无法重载父类里面的func //2当父类和子类有相同的函数名、变量名出现,发生名称覆盖 //3//c.Parent::func(); //问题2 子类的两个func和父类里的三个func函数是什么关系? |
类之间的3中关系: Has-A:包含(组合),A类中有B类作为其数据成员 Use-A:关联,A类部分的使用B类,B类没有做A类的成员,但是做为其方法的形参或者友元 Is-A:继承,关系具有传递性,不具对称性
|
面试题1:请你谈谈对多态的理解?怎么实现的 答:相同的语句,内部实现不同,在不同的类中穿梭的时候,表现形式不同,多态有两种实现形式,静态实现(重载),动态实现(重写),多态是函数指针 |
多态实现原理: 1当类中声明虚函数时,编译器会在类中生成一个虚函数表 2虚函数表是一个存储类成员函数指针的数据结构 3虚函数表是由编译器自动生成和维护的 4 Virtual成员函数会被编译器放入虚函数表中 5存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针) 如何证明vptr指针存在? 每个只带函数不带成员的类sizeof为1(函数不占对象内存),给函数加上virtual后sizeof变成4,证明存在指针 |
面试题2:是否可以将类的每个成员函数都声明为虚函数,为什么? 通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多 |
面试题3:构造函数中能调用虚函数,实现多态吗?why? 答:不能,对象在创建的时,由编译器对VPTR指针进行初始化 只有当对象的构造完全结束后VPTR的指向才最终确定 父类对象的VPTR指向父类虚函数表 子类对象的VPTR指向子类虚函数表 构造函数中调用多态函数,不能实现多态 |
父子对象指针混搭 |
将父类或子类放入数组,最好使用数组方法遍历,不要通过指针++来遍历,因为可能步长不一样 |
#include "iostream" using namespace std; //指针也是一种数据类型,指针数据的数据类型是指,它所指的内存空间的数据类型 //最后一点引申 指针的步长内存空间类型来定 class Parent01 { protected: int i; int j; public: virtual void f() { cout<<"Parent01::f"<<endl; } }; class Child01 : public Parent01 { public: int k; public: Child01(int i, int j) { printf("Child01:...do\n"); } virtual void f() { printf("Child01::f()...do\n"); } }; void howToF(Parent01 *pBase) { pBase->f(); } //指针的步长 在c++领域仍然有效,父类指针的步长和子类指针的步长不一样 //多态是靠迟绑定实现的(vptr+函数指针实现) int main06() { int i = 0; Parent01* p = NULL; Child01* c = NULL; //不要把父类对象还有子类对象同事放在一个数组里面 Child01 ca[3] = {Child01(1, 2), Child01(3, 4), Child01(5, 6)}; //不要用父类指针做赋值指针变量,去遍历一个子类的数组。 p = ca; c = ca; p->f(); c->f(); //有多态发生 // p++; // c++; // p->f();//有多态发生 // c->f(); for (i=0; i<3; i++) { howToF(&(ca[i])); } system("pause"); return 0; } |
为什么需要虚析构函数? |
问题:当父类析构函数不加virtual时,无法表现多态,因此构造时能构造子类和父类,析构时只能析构子类或父类(根据类型判断) 如果你想通过基类析构函数,可以释放后面子类的对象,南那么要给基类析构函数加上virtual关键字 解决方法:给父类析构函数加virtual |
多态原理分析
最新推荐文章于 2021-03-13 22:53:40 发布