媒介
本文主要给人人引见了关于C++中继续与多态的基本虚函数类的相干内容,分享出来供人人参考进修,下面话不多说了,来一同看看细致的引见吧。
虚函数类
继续中我们常常提到假造继续,如今我们来探讨这类的虚函数,虚函数类的成员函数前面加virtual关键字,则这个成员函数称为虚函数,不要小视这个虚函数,他能够处置惩罚继续中很多辣手的题目,而关于多态那他更重要了,没有它就没有多态,所以这个学问点异常重要,以及背面引见的虚函数表都极其重要,肯定要仔细的明白~ 如今最先观点虚函数就又引出一个观点,那就是重写(掩盖),当在子类的定义了一个与父类完整相同的虚函数时,则称子类的这个函数重写(也称掩盖)了父类的这个虚函数。这里先提一下虚函数表,背面会讲到的,重写就是将子类内里的虚函数内外的被重写父类的函数地点全都改成子类函数的地点。
纯虚函数
在成员函数的形参背面写上=0,则成员函数为纯虚函数。包括纯虚函数的类叫做抽象类(也叫接口类)
抽象类不能实例化出对象。纯虚函数在派生类中从新定义今后,派生类才实例化出对象。
看一个例子:
class Person { virtual void Display () = 0; // 纯虚函数 protected : string _name ; // 姓名 }; class Student : public Person {};
先总结一下观点:
1.派生类重写基类的虚函数完成多态,请求函数名、参数列表、返回值完整相同。(协变除外)
2.基类中定义了虚函数,在派生类中该函数始终保持虚函数的特征。
3.只要类的成员函数才定义为虚函数。
4.静态成员函数不能定义为虚函数。
5.如果在类外定义虚函数,只能在声明函数时加virtual,类外定义函数时不能加virtual。
6.不要在组织函数和析构函数内里挪用虚函数,在组织函数和析构函数中,对象是不完整的,能够会发作未定义的行动。
7.最好把基类的析构函数声明为虚函数。(why?别的析构函数比较迥殊,由于派生类的析构函数跟基类的析构函数称号不一样,然则组成掩盖,这里是由于编译器做了迥殊处置惩罚)
8.组织函数不能为虚函数,虽然能够将operator=定义为虚函数,然则最好不要将operator=定义为虚函数,由于轻易运用时轻易引发殽杂.
上面观点人人能够都邑问一句为何要如许? 这些内容在接下来的学问里都能找到答案~ 好了那末我们本日的主角虚函数上台!!!!
作甚虚函数表,我们写一个顺序,调一个看管窗口就晓得了。
下面是一个有虚函数的类:
#include<iostream> #include<windows.h> using namespacestd; class Base { public: virtual void func1() {} virtual void func2() {} private: inta; }; void Test1() { Base b1; } int main() { Test1(); system("pause"); return0; }
我们如今点开b1的看管窗口
这内里有一个_vfptr,而这个_vfptr指向的东西就是我们的主角,虚函数表。一会人人就晓得了,无论是单继续照样多继续甚至于我们的菱形继续虚函数表都邑有差别的形状,虚函数表是一个很风趣的东西。
我们来研究一下单继续的内存款式
仔细看下面代码:
#include<iostream> #include<windows.h> using namespace std; class Base { public: virtual void func1() { cout<< "Base::func1"<< endl; } virtual void func2() { cout<< "Base::func2"<< endl; } private: inta; }; class Derive:public Base { public: virtual void func1() { cout<< "Derive::func1"<< endl; } virtual void func3() { cout<< "Derive::func3"<< endl; } virtual void func4() { cout<< "Derive::func4"<< endl; } private: int b; };
关于Derive类来讲,我们以为它的虚内外会有什么?
起首子类的fun1()
重写了父类的fun1()
,虚内外存的是子类的fun1()
,接下来父类的fun2()
,子类的fun3()
, fun4()
都是虚函数,所以虚内外会有4个元素,离别为子类的fun1()
,父类fun2()
,子类fun3()
,子类fun4()
。然后我们调出看管窗口看我们想的究竟对不对呢?
我估计应当是看到fun1()
,fun2()
,fun3()
,fun4()
的虚函数表,然则呢这里看管窗口只要两个fun1()
, fun2()
,岂非我们错了????
这里并非如许的,只要本身可靠,我以为这里的编译器有题目,那我们就得本身探究一下了。 然则在探究之前我们必需来完成一个能够打印虚函数表的函数。
typedef void(*FUNC)(void); void PrintVTable(int* VTable) { cout<< " 虚表地点"<<VTable<< endl; for(inti = 0;VTable[i] != 0; ++i) { printf(" 第%d个虚函数地点 :0X%x,->", i,VTable[i]); FUNC f = (FUNC)VTable[i]; f(); } cout<< endl; } int main() { Derive d1; PrintVTable((int*)(*(int*)(&d1))); system("pause"); return0; }
下图来讲一下他的启事:
我们来运用这个函数,该函数代码以下:
//单继续 class Base { public: virtual void func1() { cout << "Base::func1" << endl; } virtual void func2() { cout << "Base::func2" << endl; } private: int a; }; class Derive :public Base { public: virtual void func1() { cout << "Derive::func1" << endl; } virtual void func3() { cout << "Derive::func3" << endl; } virtual void func4() { cout << "Derive::func4" << endl; } private: int b; }; typedef void(*FUNC)(void); void PrintVTable(int* VTable) { cout<< " 虚表地点"<<VTable<< endl; for(inti = 0;VTable[i] != 0; ++i) { printf(" 第%d个虚函数地点 :0X%x,->", i,VTable[i]); FUNC f = (FUNC)VTable[i]; f(); } cout<< endl; } int main() { Derive d1; PrintVTable((int*)(*(int*)(&d1))); //重点 system("pause"); return0; }
这里我就要讲讲这个传参了,注重这里的传参不好明白,应当细细的"品尝".
PrintVTable((int*)(*(int*)(&d1)));
起首我们肯定要拿到d1的首地点,把它强转成int*,让他读取到前4个字节的内容(也就是指向虚表的地点),再然后对谁人地点解援用,我们已拿到虚表的首地点的内容(虚表内里存储的第一个函数的地点)了,然则此时这个变量的范例解援用后是int,不能够传入函数,所以我们再对他举行一个int*的强迫范例转换,如许我们就传入参数了,最先函数执行了,我们一切都是在可控的状况下运用强转,运用强转你必需要迥殊清晰的晓得内存的散布构造。
末了我们来看看输出效果:
这里我们经由过程&d1的首地点找到虚表的地点,然后接见地点检察虚表的内容,考证我们本身写的这个函数是准确的。(这里VS另有一个bug,当你第一次打印虚表时顺序能够会崩溃,不要忧郁你从新生成处置惩罚方案,再运行一次就能够了。由于当你第一次打印是你虚表末了一个处所能够没有放0,所以你就有能够停不下来然后崩溃。)我们能够看到d1的虚表并非看管器内里打印的谁人模样的,所以有时刻VS也会有bug,不要太置信他人,照样本身可靠。哈哈哈,臭美一下~
我们来研究一下多继续的内存款式
探讨完了单继续,我们来看看多继续,我们照样经由过程代码调试的要领来探讨对象模子
看以下代码:
class Base1 { public: virtual void func1() { cout << "Base1::func1" << endl; } virtual void func2() { cout << "Base1::func2" << endl; } private: int b1; }; class Base2 { public: virtual void func1() { cout << "Base2::func1" << endl; } virtual void func2() { cout << "Base2::func2" << endl; } private: int b2; }; class Derive : public Base1, public Base2 { public: virtual void func1() { cout << "Derive::func1" << endl; } virtual void func3() { cout << "Derive::func3" << endl; } private: int d1; }; typedef void(*FUNC) (); void PrintVTable(int* VTable) { cout << " 虚表地点>" << VTable << endl; for (int i = 0; VTable[i] != 0; ++i) { printf(" 第%d个虚函数地点 :0X%x,->", i, VTable[i]); FUNC f = (FUNC)VTable[i]; f(); } cout << endl; } void Test1() { Derive d1; //Base2虚函数表在对象Base1背面 int* VTable = (int*)(*(int*)&d1); PrintVTable(VTable); int* VTable2 = (int *)(*((int*)&d1 + sizeof (Base1) / 4)); PrintVTable(VTable2); } int main() { Test1(); system("pause"); return 0; }
如今我们如今晓得会有两个虚函数表,离别是Base1和Base2的虚函数表,然则呢!我们的子类里的fun3()
函数怎么办?它是放在Base1里照样Base2里照样本身拓荒一个虚函数表呢?我们先调一下看管窗口:
看管窗口又不靠谱了。。。。完整没有找到fun3().那我们直接看打印出来的虚函数表。
如今很清晰了,fun3()
在Base1的虚函数表中,而Base1是先继续的类,好了如今我们记着这个结论,当触及多继续时,子类的虚函数会存在先继续的谁人类的虚函数内外。记着了!
我们如今来看多继续的对象模子:
如今我们来完毕一下上面我列的那末多观点如今我来一一的诠释为何要如许.
1.为何静态成员函数不能定义为虚函数?
由于静态成员函数它是一个人人同享的一个资本,然则这个静态成员函数没有this指针,而且虚函数变只要对象才能调到,然则静态成员函数不需要对象就能够挪用,所以这里是有争执的.
2.为何不要在组织函数和析构函数内里挪用虚函数?
组织函数当中不适合用虚函数的原因是:在组织对象的过程当中,还没有为“虚函数表”分派内存。所以,这个挪用也是违犯先实例化后挪用的原则析构函数当中不实用虚函数的原因是:平常析构函数先析构子类的,当你在父类中挪用一个重写的fun()
函数,虚函数表内里就是子类的fun()
函数,这时刻已子类已析构了,当你挪用的时刻就会挪用不到.
如今我在写末了一个学问点,为何只管最好把基类的析构函数声明为虚函数??
如今我们再来写一个例子,我们都晓得日常平凡一般的实例化对象然后再开释是没有一点题目的,然则如今我这里举一个惯例:
我们都晓得父类的指针能够指向子类,如今呢我们我们用一个父类的指针new一个子类的对象。
//多态 析构函数 class Base { public: virtual void func1() { cout << "Base::func1" << endl; } virtual void func2() { cout << "Base::func2" << endl; } virtual ~Base() { cout << "~Base" << endl; } private: int a; }; class Derive :public Base { public: virtual void func1() { cout << "Derive::func1" << endl; } virtual ~Derive() { cout << "~Derive"<< endl; } private: int b; }; void Test1() { Base* q = new Derive; delete q; } int main() { Test1(); system("pause"); return 0; }
这内里能够会有下一篇要说的多态,所以能够明白起来会费力一点。
注重这里我先让父类的析构函数不为虚函数(去掉virtual),我们看看输出效果:
这里它没有挪用子类的析构函数,由于他是一个父类范例指针,所以它只能挪用父类的析构函数,无权接见子类的析构函数,这类挪用要领会致使内存走漏,所以这里就是有缺点的,然则C++是不会许可本身有缺点,他就会想办法处置惩罚这个题目,这里就运用到了我们下次要讲的多态。如今我们让加上为父类析构函数加上virtual,让它变回虚函数,我们再运行一次顺序的:
诶! 子类的虚函数又被挪用了,这里发作了什么呢?? 来我们老要领翻开看管窗口。
方才这类状况就是多态,多态性能够简朴地归纳综合为“一个接口,多种要领”,顺序在运行时才决议挪用的函数,它是面向对象编程范畴的中心观点。这个我们下一个博客特地会总结多态.
固然虚函数的学问点远远没有这么一点,这里能够只是冰山一角,比如说菱形继续的虚函数表是什么样?然后菱形假造继续又是什么模样呢? 这些等我总结一下会特地写一个博客来议论菱形继续。虚函数表我们应当已晓得是什么东西了,也晓得单继续和多继续中它的运用,这些应当就足够了,这些实在都是都是为你让你更好的明白继续和多态,固然你肯定到分清晰重写,重定义,重载的他们离别的寄义是什么. 这一块能够有点绕,然则我们必需要控制.
以上就是引见有关C++中继续与多态的基本虚函数类的细致内容,更多请关注ki4网别的相干文章!