您好,欢迎访问三七文档
虚函数必须是基类的非静态成员函数,其访问权限可以是protected或public,在基类的类定义中定义虚函数的一般形式:virtual函数返回值类型虚函数名(形参表){函数体}派生类可以重置(“覆盖”)基类的非虚函数吗?EffectiveC++有条款说明这个问题的条款37:决不要重新定义继承而来的非虚函数有两种方法来看待这个问题:理论的方法和实践的方法。让我们先从实践的方法开始。毕竟,理论家一般都很耐心。假设类D公有继承于类B,并且类B中定义了一个公有成员函数mf。mf的参数和返回类型不重要,所以假设都为void。换句话说,我这么写:classB{public:voidmf();...};classD:publicB{...};甚至对B,D或mf一无所知,也可以定义一个类型D的对象x,Dx;//x是类型D的一个对象那么,如果发现这么做:B*pB=&x;//得到x的指针pB-mf();//通过指针调用mf和下面这么做的执行行为不一样:D*pD=&x;//得到x的指针pD-mf();//通过指针调用mf你一定就会感到很惊奇。因为两种情况下调用的都是对象x的成员函数mf,因为两种情况下都是相同的函数和相同的对象,所以行为会相同,对吗?对,会相同。但,也许不会相同。特别是,如果mf是非虚函数而D又定义了自己的mf版本,行为就不会相同:classD:publicB{public:voidmf();//隐藏了B::mf;参见条款50...};pB-mf();//调用B::mfpD-mf();//调用D::mf行为的两面性产生的原因在于,象B::mf和D::mf这样的非虚函数是静态绑定的(参见条款38)。这意味着,因为pB被声明为指向B的指针类型,通过pB调用非虚函数时将总是调用那些定义在类B中的函数----即使pB指向的是从B派生的类的对象,如上例所示。相反,虚函数是动态绑定的(再次参见条款38),因而不会产生这类问题。如果mf是虚函数,通过pB或pD调用mf时都将导致调用D::mf,因为pB和pD实际上指向的都是类型D的对象。所以,结论是,如果写类D时重新定义了从类B继承而来的非虚函数mf,D的对象就可能表现出精神分裂症般的异常行为。也就是说,D的对象在mf被调用时,行为有可能象B,也有可能象D,决定因素和对象本身没有一点关系,而是取决于指向它的指针所声明的类型。引用也会和指针一样表现出这样的异常行为。实践方面的论据就说这么多。我知道你现在想知道的是,不能重新定义继承而来的非虚函数的理论依据是什么。我很高兴解答。条款35解释了公有继承的含义是是一个,条款36说明了为什么在一个类中声明一个非虚函数实际上为这个类建立了一种特殊性上的不变性。如果将这些分析套用到类B、类D和非虚成员函数B::mf,那么,·适用于B对象的一切也适用于D对象,因为每个D的对象是一个B的对象。·B的子类必须同时继承mf的接口和实现,因为mf在B中是非虚函数。那么,如果D重新定义了mf,设计中就会产生矛盾。如果D真的需要实现和B不同的mf,而且每个B的对象----无论怎么特殊----也真的要使用B实现的mf,那么,每个D将不是一个B。这种情况下,D不能从B公有继承。相反,如果D真的必须从B公有继承,而且D真的需要和B不同的mf的实现,那么,mf就没有为B反映出特殊性上的不变性。这种情况下,mf应该是虚函数。最后,如果每个D真的是一个B,并且如果mf真的为B建立了特殊性上的不变性,那么,D实际上就不需要重新定义mf,也就决不能这样做。不管采用上面的哪一种论据都可以得出这样的结论:任何条件下都要禁止重新定义继承而来的非虚函数。派生类和基类的关系:1:派生类对象可以使用基类的方法,条件是方法不是私有的。2:基类指针可以在不进行显示类型转换的情况下指向派生类对象;基类引用可以在不进行显示类型转换的情况下引用派生类对象;但基类指针或引用只能用于调用基类的方法;3:不可以将基类对象和地址赋给派生类引用和指针;4:将派生类引用或者指针转换成为基类引用或指针被称为向上强制转换,不需要进行显示类型转换。将基类指针或引用转换为派生类指针或引用成为向下强制转换。5:如果基类函数中没有使用虚函数,使用类型转换才有静态编译;如果基类中有虚函数,则采用动态联编。编译器处理虚函数的方法:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数的地址数组的指针。数组被成为虚函数表,虚函数表中存储了为类对象进行声明的虚函数的地址。/******网上资料*******/1.用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。2.存在虚函数的类都有一个一维的虚函数表叫做虚表。类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。3.多态性是一个接口多种实现,是面向对象的核心。分为类的多态性和函数的多态性。4.多态用虚函数来实现,结合动态绑定。5.纯虚函数是虚函数再加上=0。6.抽象类是指包括至少一个纯虚函数的类。纯虚函数:virtualvoidbreathe()=0;即抽象类!必须在子类实现这个函数!即先有名称,没内容,在派生类实现内容!虚标指针的初始化:那么虚表指针在什么时候,或者说在什么地方初始化呢?答案是在构造函数中进行虚表的创建和虚表指针的初始化。还记得构造函数的调用顺序吗,在构造子类对象时,要先调用父类的构造函数,此时编译器只“看到了”父类,并不知道后面是否后还有继承者,它初始化父类对象的虚表指针,该虚表指针指向父类的虚表。当执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。总结(基类有虚函数):1.每一个类都有虚表。2.虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现。如果基类有3个虚函数,那么基类的虚表中就有三项(虚函数地址),派生类也会有虚表,至少有三项,如果重写了相应的虚函数,那么虚表中的地址就会改变,指向自身的虚函数实现。如果派生类有自己的虚函数,那么虚表中就会添加该项。3.派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。/~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~/虚基类1,一个类可以在一个类族中既被用作虚基类,也被用作非虚基类。2,在派生类的对象中,同名的虚基类只产生一个虚基类子对象,而某个非虚基类产生各自的子对象。3,虚基类子对象是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的。4,最派生类是指在继承结构中建立对象时所指定的类。5,派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用;如果未列出,则表示使用该虚基类的缺省构造函数。6,从虚基类直接或间接派生的派生类中的构造函数的成员初始化列表中都要列出对虚基类构造函数的调用。但只有用于建立对象的最派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类的构造函数的调用在执行中被忽略,从而保证对虚基类子对象只初始化一次。7,在一个成员初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行。虚基类:如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员。现在,将类A声明为虚基类,方法如下:classA//声明基类A{…};classB:virtualpublicA//声明类B是类A的公用派生类,A是B的虚基类{…};classC:virtualpublicA//声明类C是类A的公用派生类,A是C的虚基类{…};注意:虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。因为一个基类可以在生成一个派生类时作为虚基类,而在生成另一个派生类时不作为虚基类。虚函数1,虚函数是非静态的、非内联的成员函数,而不能是友元函数,但虚函数可以在另一个类中被声明为友元函数。2,虚函数声明只能出现在类定义的函数原型声明中,而不能在成员函数的函数体实现的时候声明。3,一个虚函数无论被公有继承多少次,它仍然保持其虚函数的特性。4,若类中一个成员函数被说明为虚函数,则该成员函数在派生类中可能有不同的实现。当使用该成员函数操作指针或引用所标识的对象时,对该成员函数调用可采用动态联编。5,定义了虚函数后,程序中声明的指向基类的指针就可以指向其派生类。在执行过程中,该函数可以不断改变它所指向的对象,调用不同版本的成员函数,而且这些动作都是在运行时动态实现的。虚函数充分体现了面向对象程序设计的动态多态性。纯虚函数版本的成员函数,而且这些动作都是在运行时动态实现的。虚函数充分体现了面向对象程序设计的动态多态性。纯虚函数1,当在基类中不能为虚函数给出一个有意义的实现时,可以将其声明为纯虚函数,其实现留待派生类完成。2,纯虚函数的作用是为派生类提供一个一致的接口。3,纯虚函数不能实化化,但可以声明指针。为什么构造函数不能是虚函数2010-05-1723:52:21|分类:c语言|标签:|字号大中小订阅首先,让我们假设他是虚的.当我们在构造函数中时并调用虚函数.大家都知道,对于普通的成员函数虚函数的调用是在运行时决定的(即晚捆绑.因为在编译时无法知道这个对象是属于这个成员函数的那个类,还是属于由他派生出来的类).然而,在构造函数中调用虚函数时,他所调用的仅仅是本地版本.也就是说,虚函数在构造函数中并不工作!第一,在概念上,构造函数的工作是把对象变成存在物。在任何构造函数中,对象可能只是部分被形成—我们只能知道基类已被初始化了,但不知道哪个类是从这个基类继承来的。然而,虚函数是“向前”和“向外”进行调用。它能调用在派生类中的函数。如果我们在构造函数中也这样做,那么我们所调用的函数可能操作还没有被初始化的成员,这将导致灾难的发生。第二,。当一个构造函数被调用时,它做的首要的事情之一是初始化它的VPTR。因此,它只能知道它是“当前”类的,而完全忽视这个对象后面是否还有继承者。当编译器为这个构造函数产生代码时,它是为这个类的构造函数产生代码--既不是为基类,也不是为它的派生类(因为类不知道谁继承它)。所以它使用的VPTR必须是对于这个类的VTABLE。而且,只要它是最后的构造函数调用,那么在这个对象的生命期内,VPTR将保持被初始化为指向这个VTABLE。但如果接着还有一个更晚派生的构造函数被调用,这个构造函数又将设置VPTR指向它的VTABLE,等.直到最后的构造函数结束。VPTR的状态是由被最后调用的构造函数确定的。这就是为什么构造函数调用是从基类到更加派生类顺序的另一个理由。但是,当这一系列构造函数调用正发生时,每个构造函数都已经设置VPTR指向它自己的VTABLE。如果函数调用使用虚机制,它将只产生通过它自己的VTABLE的调用,而不是最后的VTABLE(所有构造函数被调用后才会有最后的VTABLE)。另外,许多编译器认识到,如果在构造函数中进行虚函数调用,应该使用早捆绑,因为它们知道晚捆绑将只对本地函数产生调用。无论哪种情况,在构造函数中调用虚函数都没有结果。所以,构造函数不能是虚的,然而,对于析构函数来说他常常是,而且最好是虚的!这个此处暂时不议.定义虚函数的限制:(1)非类的成员函数不能定义为虚函数,类的成员函数中静态成员函数和构造函数也不能定义为虚函数,但可以将析构函数定义为虚函数。实际上,优秀的程序员常常把基类的析构函数定义为虚函数。因为,将基类的析构函数定义为虚函数后,当利用delete删除一个指向派生类定义的对象指针时,系统会调用相应的类的析构函数。而不将析构函数定义为虚函数时,只调用基类的析构函数。(2)只需要在声明函数的类体中使用关键字“virtual”将函数声明为虚函数,而定义函数时不需要使用关键字“virtual”。(3)当将基类中的某一成员函数声明为虚函数后,派生类中的同名函数自动成为虚函数。(4)如果声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名
本文标题:C++虚函数详解
链接地址:https://www.777doc.com/doc-5368381 .html