什么是多态
从字面上明白就是多种形状的意义。而多态一词最初源自希腊语,其寄义就是“多种形式”,意义是是具有多种形式或形状的情况,在C++言语中多态有着更普遍的寄义。在C++ primer一书中把具有继续关联的多个范例称为多态范例,因为我们能运用这些范例的“多种形式”而无须在乎它们的差异。百度百科上提到在面向对象言语中,接口的多种差异的完成体式格局即为多态。援用Charlie Calverts对多态的形貌——多态性是许可你将父对象设置成为一个或更多的他的子对象相称的手艺,赋值以后,父对象就能够根据当前赋值给它的子对象的特征以差异的体式格局运作。简朴的说,就是一句话:许可将子类范例的指针赋值给父类范例的指针。多态性在Object Pascal和C++中都是经由过程虚函数完成的。
只从观点形貌是没法深入清楚的明白它的,下面我们就具体剖析一下。
1、对象范例
这里所说的对象范例能够用下面的图来表现:我们经由过程代码举例说明一下
1 class Derived1:public Base 2 {}; 3 class Derived2:public Base 4 {}: 5 int main() 6 { 7 Derived1* p1 = new Derived1; 8 Base = p1; 9 Derived2* p2 = new Derived1;10 Base = p2;11 return = p212 }
静态范例在编译时老是已知的,他是变量声明时的范例或表达式生成的范例;动态范例则是变量或表达式示意的内存中的对象的范例。动态范例直到运转时才可晓得。
2、静态多态与动态多态
静态多态
静态多态也称为静态绑定或早绑。编译器在编译时期完成的, 编译器根据函数实参的范例(可能会举行隐式范例转换) , 可推断出要挪用谁人函数, 假如有对应的函数就挪用该函数, 不然涌现编译毛病。
1 int Add(int left,int right) 2 { 3 return left + right; 4 } 5 float Add(float left, float right) 6 { 7 return left + right; 8 } 9 int main()10 { 11 cout<<Add(1,2)<<endl; //挪用int Add()函数12 cout<<Add(1.34f,3.21f)<<endl; //调float Add()函数13 return 0;14 }
这里我把重载归为了静态多态。重载的完成是:编译器根据函数差异的参数表,对同名函数的称号做润饰,然后这些同名函数就成了差异的函数(最少关于编译器来讲是如许的)。函数的挪用,在编译器间就已肯定了,是静态的(记着:是静态)。也就是说,它们的地点在编译期就绑定了(早绑定)。恰是因为重载的这类性子,也有结论以为:重载只是一种言语特征,与多态无关,与面向对象也无关。基于面向对象来讲重载的观点并不属于“面向对象编程”。然则多态性又是一个比较普遍的观点。这里为了便于明白先不去深度剖析它们的区分,等多态的别的部份具体剖析终了,我们再来讲。
动态多态
动态绑定: 在顺序实行时期(非编译期) 推断所引 用对象的现实范例, 根据其现实范例挪用响应的要领。运用virtual关键字润饰类的成员 函数时, 指明该函数为虚函数, 派生类须要从新完成, 编译器将完成动态绑定。
FunTest1( cout << << FunTest2( cout << << FunTest3( cout << << FunTest4( cout << << CDerived : FunTest1( cout << << FunTest2( cout << << FunTest3( _iTest1) { cout << << FunTest4( _iTest1, cout << << CBase* pBase = pBase->FunTest1( pBase->FunTest2( pBase->FunTest3( pBase->FunTest4( }
当我们运用基类的指针或援用挪用基类中定义的一个函数常常,我们并不晓得该函数真正的对象是什么范例,因为他多是一个基类的对象,也多是一个派生类的对象。假如该函数是虚函数,则直到运转时才会晓得到及实行哪一个版本,推断的根据是援用或指针所绑定的对象的实在范例。
析构函数、静态范例函数、友元函数、内联函数与虚函数
CTest& =( CTest& * fri end voi d FunTestFri end() ; 12 }
什么是虚函数
实在在前面的假造继续中我们已用到了虚函数这个观点,在那里我们是为了处置惩罚菱形平常继续中接见二义性的题目,但在多态中,他有更大的作用。百度百科中对虚函数是这么说的:在某基类中声明为 virtual 并在一个或多个派生类中被从新定义的成员函数,用法花样为:virtual 函数返回范例 函数名(参数表) {函数体};完成多态性,经由过程指向派生类的基类指针或援用,接见派生类中同名掩盖成员函数。抽象的诠释为“求同存异”,它的作用就是完成多态性。
简朴地说,那些被virtual关键字润饰的成员函数,就是虚函数。虚函数的作用,用专业术语来诠释就是完成多态性(Polymorphism),多态性是将接口与完成举行星散;用抽象的言语来诠释就是完成以配合的要领,但因个体差异,而采纳差异的战略。
1 class A 2 { 3 public: 4 virtual void print(){cout<<"This is A"<<endl;} 5 }; 6 class B : public A 7 { 8 public: 9 void print(){cout<<"ThisisB"<<endl;}10 };11 int main()12 {13 A a;14 B b;15 A *p1 = &a;16 A *p2 = &b;17 p1->print();18 p2->print();19 return 0;20 }
毫无疑问,class A的成员函数print()已成了虚函数,那末class B的print()成了虚函数了吗?回覆是Yes,我们只需在把基类的成员函数设为virtual,其派生类的响应的函数也会自动变成虚函数。所以,class B的print()也成了虚函数。那末关于在派生类的响应函数前是不是须要用virtual关键字润饰,那就是你自身的题目了(语法上可加可不加,不加的话编译器会自动加上,但为了浏览轻易和规范性,发起加上)。
运转代码,输出的效果是This is A和This is B。
总结:指向基类的指针在操纵它的多态类对象时,会根据差异的类对象,挪用其响应的函数,这个函数就是虚函数。
析构函数与虚函数
当在析构函数前面加virtual关键字时报错:,我们来剖析一下缘由。
1、虚函数的实行依赖于虚函数表。而虚函数表在组织函数中举行初始化事情,即初始化vptr,让他指向正确的虚函数表。而在组织对象时期,虚函数表还没有被初 始化,将没法举行。
2、组织一个对象的时刻,必需晓得对象的现实范例,而虚函数行动是在运转时期肯定现实范例的。而在组织一个对象时,因为对象还未组织胜利。编译器没法晓得对象 的现实范例,是该类自身,照样该类的一个派生类,或是更深条理的派生类。没法肯定。
虚函数的意义就是开启动态绑定,顺序会根据对象的动态范例来选摘要挪用的要领。然而在组织函数运转的时刻,这个对象的动态范例还不完全,没有办法肯定它究竟是什么范例,故组织函数不能动态绑定。(动态绑定是根据对象的动态范例而不是函数名,在挪用组织函数之前,这个对象基础就不存在,它怎样动态绑定?)
编译器在挪用基类的组织函数的时刻并不晓得你要组织的是一个基类的对象照样一个派生类的对象。
静态范例函数与虚函数
1、 static成员不属于任何类对象或类实例,所以纵然给此函数加上virutal也是没有任何意义的。
2、static函数没有this指针,而且不会进入虚函数表的。当经由过程指针或许援用挪用时基础没法把this指针传递给static函数,从而没法表现出多态。静态成员函数与平常成员函数的差异就在于缺乏this指针,没有这个this指针天然也就无从晓得name是哪一个对象的成员了。
友元函数与虚函数
因为C++
不支持友元函数的继续,关于没有继续特征的函数没有虚函数的说法。
内联成员函数与虚函数
内联函数就是为了在代码中直接睁开,削减函数挪用消费的价值,虚函数是为了在继续后对象能够正确的实行自身的行动,这是不可能一致的。(再说了,inline
函数在编译时被睁开,虚函数在运转时才动态的邦定函数)
赋值运算符的重载与虚函数
当我们把赋值运算符的重载定义为虚函数时编译能够经由过程,然则平常不发起这么做虽然能够将operator=定义为虚函数, 但运用时轻易殽杂。
1、没法给派生类的自有成员赋值;
2、挪用虚函数要举行查虚表等一系列操纵,效力下落。
析构函数与虚函数
析构函数设为虚函数的作用:在类的继续中,假如有基类指针指向派生类,那末用基类指针delete时,假如不定义成虚函数,派生类中派生的那部份没法析构。
1 #include <stdafx.h> 2 #include <stdio.h> 3 class A 4 { 5 public: 6 A(); 7 virtual~A(); 8 }; 9 A::A()10 {}11 A::~A()12 {13 printf("Delete class APn");14 }15 class B : public A16 {17 public:18 B();19 ~B();20 };21 B::B()22 { }23 B::~B()24 {25 printf("Delete class BPn");26 }27 int main(int argc, char* argv[])28 {29 A *b=new B;30 delete b;31 return 0;32 }
输出效果为:Delete class B Delete class A
假如把A的virtual去掉:那就变成了Delete class A也就是说不会删除派生类里的盈余部份内容,也即不挪用派生类的虚函数
析构函数总结:
1. 假如我们定义了一个组织函数,编译器就不会再为我们生成默许组织函数了。
2. 编译器生成的析构函数是非虚的,除非是一个子类,其父类有个虚析构,此时的函数虚特征来自父类。
3. 有虚函数的类,险些能够肯定要有个虚析构函数。
4. 假如一个类不多是基类就不要说明析构函数为虚函数,虚函数是要消耗空间的。
5. 析构函数的异常退出会致使析构不完全,从而有内存泄漏。最好是供应一个管理类,在管理类中供应一个要领来析构,挪用者再根据这个要领的效果决议下一步的操纵。
6. 在组织函数不要挪用虚函数。在基类组织的时刻,虚函数是非虚,不会走到派生类中,既是采纳的静态绑定。明显的是:当我们组织一个子类的对象时,先挪用基类的组织函数,组织子类中基类部份,子类还没有组织,还没有初始化,假如在基类的组织中挪用虚函数,假如能够的话就是挪用一个还没有被初始化的对象,那是很风险的,所以C++中是不能够在组织父类对象部份的时刻挪用子类的虚函数完成。然则不是说你不能够那末写顺序,你这么写,编译器也不会报错。只是你假如这么写的话编译器不会给你挪用子类的完成,而是照样挪用基类的完成。
7.在析构函数中也不要挪用虚函数。在析构的时刻会首先挪用子类的析构函数,析构掉对象中的子类部份,然后在挪用基类的析构函数析构基类部份,假如在基类的析构函数内里挪用虚函数,会致使其挪用已析构了的子类对象内里的函数,这是异常风险的。
8. 记得在写派生类的拷贝函数时,挪用基类的拷贝函数拷贝基类的部份。
总结:
1、 派生类重写基类的虚函数完成多态, 请求函数名 、 参数列表、 返回值完全相同。 (协变除外)。
2、 基类中定义了 虚函数, 在派生类中该函数始终保持虚函数的特征。
3、 只 有类的非静态成员 函数才定义为虚函数, 静态成员 函数不能定义为虚函数。
4、 假如在类外定义虚函数, 只 能在声明函数时加virtual关键字, 定义时不必加。
5、 组织函数不能定义为虚函数, 虽然能够将operator=定义为虚函数, 但最好不要这么做, 运用时轻易殽杂。
6、 不要在组织函数和析构函数中挪用虚函数, 在组织函数和析构函数中, 对象是不完全的, 可能会涌现未定义的行动。
7、 最好将基类的析构函数声明为虚函数。 ( 因为派生类的析构函数跟基类的析构函数称号不一样, 然则组成掩盖, 这里编译器做了特别处置惩罚)
8、 虚表是一切类对象实例共用的。
以上就是静态多态与动态多态以及虚函数相干 的细致内容,更多请关注ki4网别的相干文章!