面向对象C++——多态
来源:互联网 发布:日程计划表软件 编辑:程序博客网 时间:2024/05/18 00:23
※多态
*「静态多态(早绑定)」
在编译期间完成,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用哪个函数,如 果有对应的函数就调用该函数,否则编译器出现错误。
#include <iostream>using namespace std;int Add(int left, int right){return left+right;}char Add(char left, char right){return left+right;}float Add(float left, float right){return left+right;}int main(){cout<<Add(10, 20)<<endl;//调用int Add(int, int)cout<<Add('1', '2')<<endl;//调用char Add(char, char)cout<<Add(1.2f, 3.4f)<<endl;//调用float Add(float, float)system("pause");return 0;}*运行结果:
*「动态多态(晚绑定)」
在程序执行期间完成,编译器根据所引用对象的实际类型调用相应的方法。
※C++中实现多态的方式
#include <iostream>using namespace std;class A{public:virtual void a1();virtual void a2();private:int _a;};class B:public A{public:void a1();private:int _b;};int main(){ B b;system("pause");return 0;}
*A类中的a1和a2函数声明为虚函数,编译器为A类准备了一张虚表:
*B类继承自A类,所以B类也有一张虚表:
*主函数执行到B b;编译器在分配空间时,除了成员int _a和int _b之外,还分配了一个空间来存放虚指针,指向 B类的虚表:
※纯虚函数
*定义:在成员函数的形参后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(或接口类), 抽象类不能实例化出对象。纯虚函数在派生类中重新定义后,派生类才能实例化处对象。
#include <iostream>using namespace std;class Person{public:virtual void Display() = 0;//纯虚函数protected:string _name;//姓名};class Student:public Person{};
※动态绑定的条件
*必须为虚函数
*通过基类类型的引用或者指针调用虚函数
#include <iostream>using namespace std;class CBase{public:virtual void FunTest1(int _iTest){cout<<"CBase::FunTest1()"<<endl;}void FunTest2(int _iTest){cout<<"CBase::FunTest2()"<<endl;}virtual void FunTest3(int _iTest1){cout<<"CBase::FunTest3()"<<endl;}virtual void FunTest4(int _iTest){cout<<"CBase::FunTest4()"<<endl;}};class CDerived:public CBase{public:virtual void FunTest(int _iTest){cout<<"CDerived::FunTest1()"<<endl;}virtual void FunTest2(int _iTest){cout<<"CDerived::FunTest2()"<<endl;}void FunTest3(int _iTest1){cout<<"CDerived::FunTest3()"<<endl;}virtual void FunTest4(int _iTest1, int _iTest2){cout<<"CDerived::FunTest4()"<<endl;}};int main(){CBase* pBase = new CDerived;pBase->FunTest1(0);pBase->FunTest2(0);pBase->FunTest3(0);pBase->FunTest4(0);//pBase->FunTest4(0, 0);system("pause");return 0;}* 运行结果:
*分析:FunTest1在基类中是虚函数,在派生类中也对其进行了重写;
FunTest2在基类中没有定义成虚函数,所以第二行打印了基类函数体的内容;
FunTest3打印了派生类函数体的内容,说明派生类重写基类的虚函数时不需要加virtual关键字;
FunTest4形参个数不同,派生类没有对基类进行重写;
*总结:1.派生类重写基类的虚函数实现多态,要求函数名,参数列表,返回值完全相同(协变除外);
2.基类中定义了虚函数,在派生类中该函数始终保持虚函数的特征;
3.只有类的非静态成员函数才能定义成虚函数,静态成员函数不可以;
4.如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用;
5.构造函数不能定义为虚函数,赋值运算符的重载最好也不要定义成虚函数;
6.不要在构造函数和析构函数中调用虚函数,因为在构造函数和析构函数中对象是不完整的;
7.最好将基类的析构函数声明为虚函数(因为派生类的析构函数和基类的析构函数名称不同,但是会构 成覆盖);
8.虚表是所有类对象实例共用的;
※虚表剖析
#include <iostream>using namespace std;class Base{public:Base(){_data = 10;cout<<"this:"<<this<<endl;}virtual ~Base(){}private:int _data;};int main(){Base b;cout<<sizeof(b)<<endl;system("pause");return 0;}*运行结果:
*分析:根据之前学习的知识,这里的Base类应该占4个字节空间,而实际的输出结果是8。原因是析构函数被定 义为虚函数,有虚函数的类会多四个字节,存放一个地址,这个地址指向虚表。
*将虚表具体分类:
①单继承:没有虚函数覆盖
#include <iostream>using namespace std;class Base{public:Base(){_a = 10;}virtual void FunTest0(){cout<<"Base::FunTest0()"<<endl;}virtual void FunTest1(){cout<<"Base::FunTest1()"<<endl;}virtual void FunTest2(){cout<<"Base::FunTest2()"<<endl;}private:int _a;};class Derived:public Base//没有对基类进行重写(没有覆盖){public:virtual void FunTest4(){cout<<"Derived::FunTest4()"<<endl;}virtual void FunTest5(){cout<<"Derived::FunTest5()"<<endl;}virtual void FunTest6(){cout<<"Derived::FunTest6()"<<endl;}};typedef void(*FUN_TEST)();void FunTest(){Base b;cout<<"Base vfptr:"<<endl;FUN_TEST* funtest1 = (FUN_TEST*)(*(int*)&b); //取虚表内的指针赋给一个函数指针变量//(int*)&b得到虚表地址//解引用得到指针地址while (*funtest1){(*funtest1)();cout <<(int*)funtest1<< endl; //打印地址++funtest1; //函数指针向后偏移}cout<<endl;Derived d;cout<<"Derived vfptr:"<<endl;FUN_TEST* funtest2 = (FUN_TEST*)(*(int*)&b); while (*funtest2){(*funtest2)();cout << (int*)funtest2 << endl; ++funtest2; }}int main(){FunTest();system("pause");return 0;}
②单继承:有虚函数覆盖
#include <iostream>using namespace std;class Base{public:Base(){_a = 10;}virtual void FunTest0(){cout<<"Base::FunTest0()"<<endl;}virtual void FunTest1(){cout<<"Base::FunTest1()"<<endl;}virtual void FunTest2(){cout<<"Base::FunTest2()"<<endl;}private:int _a;};class Derived:public Base//对FunTest0()和FunTest1()进行了重写(有覆盖){public:virtual void FunTest0(){cout<<"Derived::FunTest0()"<<endl;}virtual void FunTest1(){cout<<"Derived::FunTest1()"<<endl;}virtual void FunTest3(){cout<<"Derived::FunTest3()"<<endl;}};typedef void(*FUN_TEST)();void FunTest(){Base b;cout<<"Base vfptr:"<<endl;FUN_TEST* funtest1 = (FUN_TEST*)(*(int*)&b); //取虚表内的指针赋给一个函数指针变量//(int*)&b得到虚表地址//解引用得到指针地址while (*funtest1){(*funtest1)();cout <<(int*)funtest1<< endl; //打印地址++funtest1; //函数指针向后偏移}cout<<endl;Derived d;cout<<"Derived vfptr:"<<endl;FUN_TEST* funtest2 = (FUN_TEST*)(*(int*)&b); while (*funtest2){(*funtest2)();cout << (int*)funtest2 << endl; ++funtest2; }}int main(){FunTest();system("pause");return 0;}
*总结——派生类虚表的生成方式:
(1)首先是拷贝基类的虚函数表;
(2)如果派生类重写了虚表的某个函数,就覆盖同位置的基类虚函数;
(3)最后是派生类自己新定义的函数。
③多继承:没有虚函数覆盖
#include <iostream>using namespace std;class Base0{public:Base0(){_data = 0xB0;}virtual void PrintB0(){cout<<"_data = "<<_data<<"Base0()::PrintB0"<<endl;}private:int _data;};class Base1{public:Base1(){_data = 0xB1;}virtual void PrintB1(){cout<<"_data = "<<_data<<"Base1()::PrintB1"<<endl;}private:int _data;};class Base2{public:Base2(){_data = 0xB2;}virtual void PrintB2(){cout<<"_data = "<<_data<<"Base2()::PrintB2"<<endl;}private:int _data;};class Derived:public Base0, public Base1, public Base2{public:Derived(){_data = 0xD0;}virtual void PrintD(){cout<<"_data = "<< _data<<"Derived::PrintD()"<<endl;}private:int _data;};typedef void(*VFTABLE_FUN)();void FunTest(){Derived d;Base0& b0 = d;VFTABLE_FUN* vfp0 = (VFTABLE_FUN*)(*(int*)&b0);cout <<"Base0 vfptr:"<< endl;while (*vfp0){(*vfp0)(); ++vfp0; }cout << endl;Base1& b1 = d;VFTABLE_FUN* vfp1 = (VFTABLE_FUN*)(*(int*)&b1);cout <<"Base1 vfptr:"<< endl;while (*vfp1){(*vfp1)();++vfp1;}cout << endl;Base2& b2 = d;VFTABLE_FUN* vfp2 = (VFTABLE_FUN*)(*(int*)&b2);cout <<"Base1 vfptr:"<< endl;while (*vfp2){(*vfp2)();++vfp2;}cout << endl;VFTABLE_FUN* vfp3 = (VFTABLE_FUN*)(*(int*)&d);cout <<"Derived vfptr:"<< endl;while (*vfp3){(*vfp3)();++vfp3;}}int main(){FunTest();system("pause");return 0;}*运行结果:
*分析:根据监控和内存可以看出,派生类的虚表中首先存放了基类Base0的部分,接着是Base1, Base2, 最后是自己的内容;
④多继承:有虚函数覆盖
#include <iostream>using namespace std;class Base0{public:Base0(){_data = 0xB0;}virtual void Print(){cout<<"_data = "<<_data<<" Base0()::Print"<<endl;}private:int _data;};class Base1{public:Base1(){_data = 0xB1;}virtual void Print(){cout<<"_data = "<<_data<<" Base1()::Print"<<endl;}private:int _data;};class Base2{public:Base2(){_data = 0xB2;}virtual void Print(){cout<<"_data = "<<_data<<" Base2()::Print"<<endl;}private:int _data;};class Derived:public Base0, public Base1, public Base2{public:Derived(){_data = 0xD0;}virtual void Print(){cout<<"_data = "<< _data<<" Derived::Print()"<<endl;}private:int _data;};void FunTest(){Derived d;Base0& b0 = d;b0.Print();Base1& b1 = d;b1.Print();Base2& b2 = d;b2.Print();d.Print();}int main(){FunTest();system("pause");return 0;}
*运行结果:
*分析:同单继承有覆盖的原理相同,在派生类中进行重写的基类虚函数被覆盖。
- 面向对象C++——多态
- 面向对象—多态
- C#——面向对象之多态
- 【C#】——面向对象思想
- c++primer——面向对象编程
- 【C#】总结二——面向对象
- 0C——2.面向对象
- 面向对象C++——继承
- C#——面向对象 (一)
- C#——面向对象(二)
- Objective—C面向对象(上)
- 从面向过程到面向对象——在C中如何实现面向对象编程
- 【Objective-C】面向对象—类和对象
- C 语言面向对象-- 多态
- 第二章:C#.NET面向对象——面向对象程序设计1(面向对象程序设计语言的三大原则)
- 一步一步C#——2 概念性问题:类、对象、继承、多态、面向对象
- 面向对象——多态
- 面向对象——多态
- OSG学习笔记18
- js拖放api实现客户端excel的读取
- 利用CPU时钟生成伪随机数
- 第六章 面向对象面向对象程序设计
- L2-007. 家庭房产
- 面向对象C++——多态
- HTTP相关
- 主流脚本编程语言大比拼
- Floyd算法为什么只有5行
- python 反向shell 编程
- Log4j-详细教程
- iOS reason: '*** -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array'
- 数据库分页技术
- mysql拷贝表的几种方式