ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

一篇带你搞定C++多态

一篇带你搞定C++多态 多态1.多态是啥2.多态咋用3.多态的原理是啥呢1.多态是啥定义简单来讲多态是多种形态。分类编译时多态静态多态和运行时多态动态多态。静态多态就是指之前的函数重载和模板通过传入不同类型的参数来完成不同的任务。动态多态在执行某一个函数时不同对象所产生的不同的函数行为。2.多态咋用多态的构成条件多态必须是继承关系下的一个类对象去调用同一个函数所产生不同的行为。多态的实现条件1.函数参数必须是基类的指针或引用问题为啥必须是积累的指针或引用呢只有基类的指针/引用才能既可以指向基类又可以指向派生类实现多态效果。2.被调用的函数必须是虚函数要完成函虚函数重写/覆盖。问题1虚函数是啥在类成员函数前面加上virtual就是虚函数。非成员函数不能加virtual。问题二: 虚函数重写/覆盖又是啥首先你要保证要调用的函数必须在基类中是虚函数其次在派生类中也同样写一个与基类虚函数相同的虚函数相同是指函数名返回类型参数类型一致不要求缺省值一致内容也不一致毕竟要实现多种效果嘛。多态的例子#includeiostreamusingnamespacestd;//定义一个基类 -人classPerson{public://参观学校函数virtualvoidvisit(inta0){cout非本校师生需购买门票参观学校endl;}protected:charname[20];intage;chargender[5];};//定义一个派生类 - 学生classStudent:publicPerson{public://虚函数重写virtualvoidvisit(inta1){cout你是本校学生可以进入endl;}};voidFunc(Person*p){p-visit();}intmain(){//创建一个基类对象Person p;Func(p);//创建一个派生类对象Student s;Func(s);return0;}经典例题classA{public:virtualvoidfunc(intval1){std::coutAvalstd::endl;}virtualvoidtest(){func();}};classB:publicA{public:voidfunc(intval0){std::coutB-valstd::endl;}};intmain(intargc,char*argv[]){B*pnewB;p-test();return0;}解答首先创建了一个派生类指针然后调用test()成员函数参数列表隐式会将派生类指针传给传参进入派生类中寻找test()函数发现没有找到于是就去它所继承的父类中寻找父类中的test()函数中隐式参数是A*进入函数体内部调用func()函数此时我们可以看到在父类和子类中都有func()函数且实现了虚函数重写同时test()隐式参数是基类的指针构成多态。多态只看你原本创建的类是基类还是派生类在题中创建的是派生类那么就会使用基类的函数名函数参数及缺省值派生类的函数体。最后结果为B-1。为啥不是B-0?这就是这道题的坑点了。虚函数重写实质上是函数体的重写。而为啥虚函数的参数列表不能是子类中的呢如果想要是子类中的那么你就得将调用函数func()的参数中的参数类型改为子类的指针或引用那么就不会构成多态了。可能有人会说那么子类中的虚函数的参数缺省值不就没用了吗对✅没有用了这就是出题人给的陷阱所以要注意⚠️。多态中的“异类”为啥说是“异类”呢因为我们之前都说构成多态的条件之一就是虚函数重写/覆盖重写要求三相同 - 返回类型、函数名、参数类型。但是有两个就比较例外协变和析构函数。协变协变就是构成多态的虚函数的返回类型可以不同但是基类必须返回基类的指针或引用派生类必须是派生类的指针或引用。这种操作就是协变。演示//定义一个基类 -人classPerson{public://参观学校函数virtualPerson*visit(inta0){cout非本校师生需购买门票参观学校endl;coutaendl;returnnullptr;}protected:charname[20];intage;chargender[5];};//定义一个派生类 - 学生classStudent:publicPerson{public://虚函数重写virtualStudent*visit(intb1){cout你是本校学生可以进入endl;coutbendl;returnnullptr;}};voidFunc(Person*p){p-visit();}intmain(){//创建一个基类对象Person p;Func(p);//创建一个派生类对象Student s;Func(s);return0;}析构函数析构函数表面上是“异类”但从底层来讲它不是异类。classA{public:virtual~A(){cout~A()endl;}};classB:publicA{public:virtual~B(){cout~B()endl;deletep2;p2nullptr;}protected:int*p2newint(2);};intmain(){A*p1newA;A*p2newB;deletep1;deletep2;return0;}为啥析构函数的函数名也不相同为啥也能构成多态呢首先delete函数底层是先析构再调用operator delete()而在调用析构函数时传的参数类型都是基类指针然后析构函数在底层函数名都会变为destructor(),因此在底层上来说析构函数也满足多态的条件。面试题为啥基类中的析构函数要设计成虚函数那我们上面那个例子来说如果基类中不加virtual那么delete p2 时仅仅只会释放A的资源B中的资源并不会释放造成内存泄漏。override与final有时候我们实现多态时会出现写错函数名等情况我们就需要override来检查该函数是否构成多态有时候我们不想让这个函数构成多态我们就需要加上final让该函数不能构成多态。演示//情况一classCar{public:virtualvoiddrive(){coutCarendl;}};classBaoma{public://拼写错误//不加override,编译不会报错,只是输出的结果不一样。//加上override,编译报错error C3668: “Baoma::dirve”: 包含重写说明符“override”的方法没有重写任何基类方法virtualvoiddirve()override{coutBaoma-driveendl;}};intmain(){Car a;Car b;a.drive();b.drive();return0;}//情况二classCar{public://我不想让这个函数成为多态加finalvirtualvoiddrive()final{coutCarendl;}};classBenchi:publicCar{public://error C3248: Car::drive: 声明为 final 的函数不能由 Benchi::drive 重写virtualvoiddrive(){coutBenchi-Carendl;}};intmain(){Car*anewCar;Car*bnewBenchi;a-drive();b-drive();return0;}重点重载、隐藏、重写的对比。重载1.在同一个作用域内2.函数名相同参数列表不同返回值类型可相同也可不同。隐藏1.两函数所在的对象之间是继承关系。2.函数名相同函数参数列表可以不同。3.父类子类成员变量相同也构成隐藏关系。重写1.两函数所在的对象之间是继承关系。2.函数名相同参数列表相同协变例外。3.函数都被virtual修饰。总的来说纯虚函数纯虚函数是在虚函数后面加上0就成为纯虚函数。纯虚函数不需要定义实现只需要声明如果要实现也可以不过最后都会被重写。抽象类包含纯虚函数的类叫作抽象类。抽象类不能够被实例化继承抽象类的子类必须重写虚函数否则子类也会成为抽象类不能被实例化。//情况一实例化抽象类classA{public:virtualvoidfunc()0{coutfunc1endl;}};classB:publicA{virtualvoidfunc(){coutB::func()endl;}};intmain(){//error C2259: “A”: 无法实例化抽象类//A a;B b;return0;}//情况二子类没有重写纯虚函数classA{public:virtualvoidfunc()0{coutfunc1endl;}};classB:publicA{//error C2259 : “B”: 无法实例化抽象类voidfunc1(){coutB::func()endl;}};intmain(){//error C2259: “A”: 无法实例化抽象类//A a;B b;return0;}3.多态的原理是啥呢多态咋实现以下面代码为例调用func()时与ptr是啥无关与ptr指向的对象有关。如果是Person,那么就去虚函数表中调用Person::Buytickets;如果是Student,那么就去虚函数表中调用Student::Buytickets;如果是Soldier,那么就去虚函数表中调用Soldier:: Buytickets。classPerson{public:virtualvoidBuytickets(){coutPerson::Buyticketsendl;}protected:string name;};classStudent:publicPerson{public:virtualvoidBuytickets()override{coutStudent::Buyticketsendl;}protected:intid;};classSoldier:publicPerson{public:virtualvoidBuytickets(){coutSoldier::Buyticketsendl;}protected:intcodename;};voidfunc(Person*ptr){ptr-Buytickets();}intmain(){Person*pnewPerson;Person*snewStudent;Person*dnewSoldier;func(p);func(s);funs(d);deletep;deletes;deleted;return0;}我们可以看到这中都有_vfptr这是虚函数数组指针该指针指向一个虚函数表指针数组-该类中有多少虚函数虚函数表就会有多少个函数指针同时末尾也会跟着这里存放着虚函数的地址。我们也可以看到子类继承的父类也继承了父类的虚函数表但是与父类的虚函数表并不是同一个表因为地址不一样。虚函数表大总结:1.基类和派生类都有各自独立的虚函数表互不干扰。2.派生类继承基类派生类继承了基类的虚函数表但与父类不是同一个表。3.继承下来后派生类只有一个虚函数表如果派生类又重写虚函数那么就会覆盖原来的基类的虚函数的地址。总的来说派生类中虚函数表里有基类的虚函数基类中被重写的虚函数派生类中自己的虚函数。4.重点虚函数存在哪里虚函数和普通的函数一样都存在代码中只是虚函数的地址又被存在虚函数表中一份。5.重点虚函数表存在哪里答案是VS下存在静态区中。classBase{public:virtualvoidfunc(){cout1endl;}};classSon:publicBase{public:virtualvoidfunc(){cout2endl;}};voidtest(){cout6endl;}intmain(){inti0;int*p1newint[4];staticintj0;printf(栈区%p\n,i);printf(堆区%p\n,p1);printf(静态区%p\n,j);printf(代码段%p,test);coutendl;Base b;Son s;Son*pss;printf(函数表%p\n,*(int*)ps);printf(虚函数%p\n,Base::func);delete[]p1;return0;}结果 栈区006FF89C 堆区00971D20 静态区0039D474 代码段0039127B 函数表0039AB44 虚函数0039148D
返回列表