面向对象的三大特性
1、封装 突破了函数的概念
把客观事物封装成抽象的类,通过访问控制符把自己的成员变量和函数提供给可信的类和对象来操作,对不可信的进行信息隐藏。
2、继承 实现了以前代码的复用
通过继承可以使用现有类的所有功能和成员变量(当然也需要看访问控制符),并在无需重新编写原来的类的情况下对这些功能进行扩展。
3、多态 实现了对后来的代码的复用
多态表现为同样的调用语句,有多种不同的调用方式
例如:80年代的人写的框架,90年代的框架什么都不需要变,只需要根据业务需要继承以前的父类,重写父类以前的方法,将子类对象指针(或引用)传入框架即可。
多态相关的知识
重载、重写、重定义联系和区别
重载:1、必须在一个类中
2、函数名字相同,参数列表不同(参数的类型、顺序、个数决定),返回类型可同可不同,virtual关键字可有可无.
3、在编译期间就确定了调用了具体的某个函数。
注意:子类不能重载父类的函数(如果子类和父类中的函数名字相同,子类将会把父类的名称覆盖,占用了父类函数的位置,此同名函数只有子类的函数可以调用,父类的将不能被调用(如果要调用必须添加域名)
重写:1、必须在两个类中且具有继承关系
2、方法名、参数个数和参数类型 都必须相同
3、 基类函数必须有virtual关键字
重定义(隐藏):
1、如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏
2、如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有vitual关键字,此时,基类的函数被隐藏
多态:符合重写规则,要有父类引用(指针)指向子类对象。
静态联编和动态联编
静态联编:在编译器编译阶段,已经决定了函数的调用(不写virtual关键字,是静态联编)
动态联编:在运行阶段,根据具体的对象(具体的类型),执行不同对象的函数,表现为多态
为什么需要多态
如果没有多态,C++编译器只能根据我们调用语句具体的对象类型,调用具体对象的方法(如父类对象只能调用父类的print方法,子类对象只能调用子类的print方法),
而不能对同样的调用语句,有多种不同的调用方式(父类对象可以调用子类的print方法)
代码如下(没有使用多态)
#includeusing namespace std;class Parent //父类{public: void print() { cout<<"parent print..."< print();}int main(){ Parent par; Child chld; playObj(&par); //调用函数传入父类对象指针 playObj(&chld); //调用函数传入子类对象指针 system("pause"); return 0;}
使用多态后代码(只是加了virtual关键字)
#includeusing namespace std;class Parent //父类{public: virtual void print() 多态,在重写的函数前加virtual关键字 { cout<<"parent print..."< print();}int main(){ Parent par; Child chld; playObj(&par); //调用函数传入父类对象指针 playObj(&chld); //调用函数传入子类对象指针 system("pause"); return 0;}
多态成立条件
1、要有继承 上例中类Child继承了类Parent
2、要有函数重写(虚函数) 上例中子类重写了父类的虚函数print()
3、要有父类指针(父类引用)指向子类对象 上例中Parent* p指向了子类对象chld
多态意义
实现了接口重用(统一的表达式),接口重用带来的好处是程序更易于扩展,代码重用更加方便,更具有灵活性。
接口是最有价值的资源,只要定义好了接口,可以省去很多人力物力去做重复的事情。
多态原理剖析
1、当类中声明虚函数时,编译器会在类中生成一个虚函数表
2、虚函数表是一个存储类成员函数指针的数据结构
3、当实例化对象时,如果此对象类存在虚函数时,则此对象中会生成一个指向虚函数表的指针(C++编译器给父类对象、子类对象提前布局vptr指针)
4、当进行playObj(animal *pAnimal)函数是,C++编译器不需要区分子类对象或者父类对象,只需要在pAnimal指针中,找vptr指针即可
代码如下:
#includeusing namespace std;class animal{public: virtual void cry() //声明虚函数时,编译器会在类中生成一个虚函数表 { cout<<"animal cry..."< cry(); //C++编译器不需要区分子类对象或者父类对象,只需要在pAnimal指针中,找vptr指针即可}int main(){ cat cc; //实例化对象cc,在对象中会生成一个指向虚函数表的vptr指针 dog dd; //实例化对象dd,在对象中会生成一个指向虚函数表的vptr指针 playObj(&cc); playObj(&dd); system("pause"); return 0;}
子类的vptr指针的分步初始化
1、当执行父类的构造函数时,子类的vptr指针指向父类的虚函数表
2、当父类的构造函数运行完毕后,会把子类的vptr指针指向子类的虚函数表
多态时,注意子类和父类的步长不一样。