浏览量 1 1.类里如果声明了虚函数,这个函数是实现的,哪怕是空实现,它的作用就是为了能让这个函数在它的子类里面可以被覆盖(override),这样的话,编译器就可以使用后期绑定来达到多态了。 纯虚函数只是一个接口,是个函数的声明而已,它要留到子类里去实现。 2.虚函数在子类里面可以不重写;但纯虚函数必须在子类实现才可以实例化子类。 3.虚函数的类用于 “实作继承”,继承接口的同时也继承了父类的实现。纯虚函数关注的是接口的统一性,实现由子类完成。 4.带纯虚函数的类叫抽象类,这种类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。抽象类被继承后,子类可以继续是抽象类,也可以是普通类。 5.虚基类是虚继承中的基类,具体见下文虚继承。
虚函数 代码如下定义: // test1107.cpp : 定义控制台应用程序的入口点。 son s; cout<<f.get_age()<<endl; cout<<s.get_age()<<endl; system("pause"); } 输出为: 1 0 在基类中的虚函数 当基类中的虚函数定义时,是使用指针或者引用作为参数,那么在运行是,要判断传入的参数,是基类的对象,还是派生类的对象。 如果是基类的对象,则调用基类中的虚函数定义。 如果是派生类的对象,则调用派生类中对基类虚函数的新定义的函数。
虚函数(impure virtual) C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现。 子类可以重写父类的虚函数实现子类的特殊化。 ; 纯虚函数(pure virtual) C++中包含纯虚函数的类,被称为是“抽象类”。 C++中的纯虚函数也是一种“运行时多态”。 } //虚函数 virtual void xhs(){ //这个虚函数必须得在基类中实现 cout<<"我是基类的虚函数"<<endl;//即使是空的虚函数也要在基类中实现 } //派生类中可以不写这个函数,但是派生类对象调用时会调用积累的虚函数 //纯虚函数 virtual void cxhs() =0; //这个纯虚函数不在基类中实现,必须在子类中实现
实现动态联编需要三个条件: 1、 必须把需要动态联编的行为定义为类的公共属性的虚函数。 2、 类之间存在子类型关系,一般表现为一个类从另一个类公有派生而来。 而不将析构函数定义为虚函数时,只调用基类的析构函数。 (2)只需要在声明函数的类体中使用关键字“virtual”将函数声明为虚函数,而定义函数时不需要使用关键字“virtual”。 我们可以看到下面几点: 1)虚函数按照其声明顺序放于表中。 2)父类的虚函数在子类的虚函数前面。 一般继承(有虚函数覆盖) 覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。 那么,对于派生类的实例的虚函数表会是下面的样子: 我们从表中可以看到下面几点, 1)覆盖的f()函数被放到了子类虚函数表中原来父类虚函数的位置。 2)没有被覆盖的函数依旧。 对于子类实例中的虚函数表,是下面这个样子: 我们可以看到: 1) 每个父类都有自己的虚表。 2) 子类的成员函数被放到了第一个父类的表中。
2.多态的定义及实现 2.1多态的构成条件 多态是一个继承关系下的类对象,去调用同一函数,产生了不同的行为。比如Student继承了Person。 2.1.1实现多态还有两个必须重要条件 1.必须是基类的指针或者引用调用虚函数 2.被调用的函数必须是虚函数,并且完成了虚函数重写 说明:要实现多态效果,第一必须是基类的指针或引用,因为只有基类的指针或引用才能既指向基类对象又指向派生类对象 2.析构函数的重写 基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同看起来不符合重写的规则,实际上编译器对析构函数的名称做了特殊处理 2.派生类由两部分构成,继承下来的基类和自己的成员,一般情况下,继承下来的基类中有虚函数表指针,自己就不会再生成虚函数表指针。 4.派生类的虚函数表中包含,(1)基类的虚函数地址,(2)派生类重写的虚函数地址完成覆盖,派生类自己的虚函数地址三个部分。
前四个字节存储的是虚函数表的指针vfptr,后四个字节存储对象成员var的值。虚函数表的大小为4字节,就一条函数地址,即虚函数fun的地址,它在虚函数表vftable的偏移是0。 并且虚函数表内的记录多了一条——MyClassA自己定义的虚函数funA。它的对象模型如图2所示。 ? 图2 MyClassA对象模型 我们可以得出结论:在单继承形式下,子类的完全获得父类的虚函数表和数据。 : 1> | &MyClassC_meta 1> | 0 1> 0 | &MyClassA::fun 1> 1 | &MyClassA::funA 1> 2 而且每一个父类都对应一个单独的虚函数表。MyClassC的对象模型如图3所示。 ? 图3 MyClassC对象模型 多重继承下,子类不再具有自身的虚函数表,它的虚函数表与第一个父类的虚函数表合并了。
2、实现这种动态的多态性时,必须使用基类类型的指针变量,并使该 指针指向不同的派生类对象,并通过调用指针所指向的虚函数才能实现 动态的多态性。 虚函数的访问 用基类指针访问与用对象名访问的区别: 1、用基指针访问虚函数时,指向其实际派生类对象重新定义的函数。实 现动态聚束。 2、通过一个对象名访问时,只能静态聚束。 2、把函数名赋值为0,本质上是将指向函数体的指针值赋为初值0。与定义空函数不一样,空函数的函数体为空,即调用该函数时,不执行任何动作。没有在派生类重新定义这种虚函数之前,是不能调用这种纯虚函数的。 *mptr2)(100); 或: (ps->*mptr1)( ); (ps-*mptr2)(100); 对指向成员函数的指针变量的使用方法说明以下几点: 1、指向类中成员函数的指针变量不是类中的成员, 2、不能将任一成员函数的地址赋给指向成员函数的指针变量,只有成员函数的参 数个数、参数类型、参数的顺序和函数的类型均与这种指针变量相同时,才能将成 员函数的指针赋给这种变量。
C++的虚函数是一种特殊的成员函数,用于实现多态性。虚函数允许在基类中声明一个函数,在派生类中根据需要进行重写,并通过基类指针或引用来调用派生类对象的特定实现。 ①虚函数的声明 在基类中,我们可以使用关键字virtual来声明一个虚函数。 ptr->show(); ③派生类重写虚函数 派生类可以重写基类中的虚函数,以提供自己的实现。 ." << endl; } }; ④纯虚函数 虚函数也可以被声明为纯虚函数,即没有默认实现的虚函数。纯虚函数通过在声明中使用= 0来标识。 虚函数使用动态绑定,即运行时将根据对象的实际类型选择正确的函数实现。 构造函数不能是虚函数。 静态成员函数不能是虚函数。 虚函数可以被继承,派生类可以选择是否重写虚函数。
对于经常被问到的虚函数和多态的问题,发现百度百科回答得十分详细,所以自己在百度百科上的解释进行总结 一、虚函数 (1)虚函数简介:在某基类中声明为virtual并在一个或者多个派生类中被重新定义的成员函数 (2)简单解释:被virtual关键字修饰的成员函数,就是虚函数。 (3)作用:实现多态性(polymorphism)。 (5)限制条件: 非类的成员函数不能定义为虚函数,类的成员函数中静态函数、构造函数也不能定义为虚函数,但是析构函数可以被定义为虚函数; 当基类中的某一成员函数声明为虚函数后,派生类中的同名函数(函数名相同 、参数列表完全一致、返回类型相关)自动成为虚函数; 如果声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数;在以该类为基类的派生类中,也不能出现这种同名函数 (2)实现方法:C++中,实现多态可以通过虚函数、抽象类、覆盖、模板(重构与多态无关)。
文章目录 一、多态与重载 1、多态的概念 2、重载—编译期多态的体现 3、虚函数—运行期多态的体现 二、虚函数实例 三、虚函数的实现(内存布局) 1、无继承情况 2、单继承情况(无虚函数覆盖) 3、单继承情况 (有虚函数覆盖) 4、多重继承情况(无虚函数覆盖) 5、多重继承情况(有虚函数覆盖) 四、虚函数的相关问题 1、构造函数为什么不能定义为虚函数 2、析构函数为什么要定义为虚函数? return 0; } 2、单继承情况(无虚函数覆盖) 假设有如下所示的一个继承关系: 请注意,在这个继承关系中,子类没有重载任何父类的函数。 ::f() b3->f(); //Derive::f() b1->g(); //Base1::g() b2->g(); //Base2::g() b3->g(); //Base3::g() 四、虚函数的相关问题 那么在构造函数完成之前,vptr是没有值的,也就无法通过vptr找到作为虚函数的构造函数所在的代码区。 2、析构函数为什么要定义为虚函数? 析构函数可以是虚函数且推荐最好设置为虚函数。
定义 纯虚函数就是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。 纯虚函数的意义在于,让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但类无法为纯虚函数提供一个合理的缺省实现。 纯虚函数和虚函数有什么区别 纯虚函数声明如下:virtual void function1()=0; 纯虚函数一定没有定义,纯虚函数用来规范派生类的行为, 即接口。 实现了纯虚函数的子类,该纯虚函数在子类中就变成了了虚函数,子类的子类可以覆盖该虚函数,由多态方式调用的时候动态绑定。 虚函数是C++中用于实现多态的机制。核心理念就是通过基类访问派生类定义的函数。 在有动态分配堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚的。 友元不是成员函数,只有成员函数才可以使虚拟的,因此友元不能是虚拟函数。
说明: 1.虚函数列表中的最后一个.表示的是虚函数列表的结束符,类似于字符串的/0。 2.虚函数指针往往是在类对象的第一个元素。 Base b1; cout << "b1:虚函数表的地址:" << (int*)(&b1) << endl; for(int i = 0; i < 2; i++){ :虚函数表的地址:0x7fffd27d5e80 // 2. 虚指针是跟对象绑定的,每一个类对象会对应一个虚指针,这个原因应该是虚指针是作为类的一个数据存储的导致的。例子参考 Base b和b1两个对象的虚指针地址,明显是不相同的。 2. 这里可以看出是2个虚函数指针,对应于基类数量d:虚函数表的地址:0x7fff5934cc80d:虚函数表的第[0][0]个函数地址:0x400e90 // 2.第一个基类的虚函数表首地址Derive f
言归正传,让我们一起进入虚函数的世界。 虚函数表 对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。 那么,在派生类的实例中,其虚函数表如下所示: 对于实例:Derive d; 的虚函数表如下: 我们可以看到下面几点: 1)虚函数按照其声明顺序放于表中。 2)父类的虚函数在子类的虚函数前面。 一般继承(有虚函数覆盖) 覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数,会是一个什么样子? 那么,对于派生类的实例,其虚函数表会是下面的一个样子: 我们从表中可以看到下面几点, 1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。 2)没有被覆盖的函数依旧。 对于子类实例中的虚函数表,是下面这个样子: 我们可以看到: 1) 每个父类都有自己的虚表。 2) 子类的成员函数被放到了第一个父类的表中。
必须是虚函数(派生类一定要重写基类中的虚函数) ---- Q2:什么是纯虚函数,与虚函数的区别 1、定义一个函数为虚函数,不代表函数为不被实现的函数。 2、纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加"=0" 3、声明了纯虚函数的类是一个抽象类。 1、static成员不属于任何类对象或类实例,所以即使给此函数加上virutal也是没有任何意义的。 2、静态与非静态成员函数之间有一个主要的区别,那就是静态成员函数没有this指针。 2、虚函数表类似一个数组,类对象中存储vptr指针,指向虚函数表。 类 B 对象的存储空间以及虚函数表(假定类 B 还有其他虚函数)如图 2 所示。
虚函数的使用方法(以下内容 摘自《C++面向对象程序》): (1)在基类用virtual声明成员函数为虚函数。 (2)在派生类中重新定义此函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。 C++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。 什么时候应该把一个成员函数声明为虚函数呢? (1)首先看成员函数所在的类是否会作为基类。然后看成员函数在类的继承后有无可能被更改功能,如果希望更改其功能的,一般应该讲其声明为虚函数。 (2)如果成员函数在类被继承后功能不需要修改,或派生类用不到该函数,则不要把它声明为虚函数。
类A包含虚函数vfunc1,vfunc2,由于类A包含虚函数,故类A拥有一个虚表。 图2:对象与它的虚表 上面指出,一个继承类的基类如果包含虚函数,那个这个继承类也有拥有自己的虚表,故这个继承类的对象也包含一个虚表指针,用来指向它的虚表。 类A,类B,类C的对象都拥有一个虚表指针,*__vptr,用来指向自己所属类的虚表。 类A包括两个虚函数,故A vtbl包含两个指针,分别指向A::vfunc1()和A::vfunc2()。 类B继承于类A,故类B可以调用类A的函数,但由于类B重写了B::vfunc1()函数,故B vtbl的两个指针分别指向B::vfunc1()和A::vfunc2()。 类C继承于类B,故类C可以调用类B的函数,但由于类C重写了C::vfunc2()函数,故C vtbl的两个指针分别指向B::vfunc1()(指向继承的最近的一个类的函数)和C::vfunc2()。
这一篇文章来讲讲C++的多态、虚函数、纯虚函数。 C++多态 多态:C++中的多态分为静态多态,动态多态。 (a, b) { } int area() { cout << "Triangle class area :" << endl; return (width * height / 2) : 您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。 纯虚函数就是虚函数的函数主体=0,也就是没有主体。 而纯虚函数必须在派生类中实现该纯虚函数。 ②当类中存在纯虚函数,则该类为抽象类。
首先理解一下分开的意思 成员函数后面用 const 修饰,const表示this是一个指向常量的指针,即对象成为一个常量,即它的成员不能够变化. 例如在Sales_data成员函数中,this的类型是Sales_data *const,即类一旦实例化一个对象后,this指向这个对象,是不能改变的,但是对象本身可以变) =0表示这个成员函数是纯虚函数 ,也就是它可以没有定义,只有接口,由它的继承类具体定义它的行为,当然,你也可以给它定义缺省的函数体 一个类里如果包含 =0 的纯虚函数,那么这个类就是一个抽象类,它不能具体实例化(不能创建它的对象), 而只能由它去派生子类 合起来在虚函数后面–>纯虚函数 const 写在函数后头还=0这里不是const=0,虚函数表示方法是 virtual 返回值 函数名(参数表){函数体} , 在继承的时候可以在子类中从新定义这个函数 如果你的子类中都重新定义了这个函数,那个父类中函数的定义就没有什么用了所以可以不定义只说明就行,也就是定义为纯虚函数形如: virtual 返回值 函数名(参数表)=0;这里就不用定义实际的函数了。
本文将深入解析虚函数与纯虚函数的区别,并通过实例展示它们在实际编程中的应用。一、虚函数虚函数是指在C++中,被virtual关键字修饰的成员函数。 虚函数表保存了类中所有虚函数的地址,通过虚表指针可以找到对应的函数地址,从而实现动态绑定。虚函数可以有实现,也可以没有实现。在子类中,虚函数可以被覆盖,也可以不被覆盖。 如果子类没有覆盖基类的虚函数,那么当通过基类指针或引用调用该函数时,将调用基类的虚函数实现。二、纯虚函数纯虚函数是一种特殊的虚函数,它在声明时除了加上virtual关键字外,还需要加上=0。 三、虚函数与纯虚函数的区别定义方式:虚函数在定义时在普通函数的基础上加上virtual关键字,而纯虚函数在定义时除了加上virtual关键字外,还需要加上=0。 多态性:虚函数和纯虚函数都可以实现多态性,但纯虚函数更多地用于定义抽象接口,而虚函数则用于实现具体的多态行为。类类型:包含虚函数的类可以是普通类,也可以是抽象类;而包含纯虚函数的类一定是抽象类。
在虚函数的声明语句末尾中加个 =0 ,她就会摇身一变成为纯虚函数。 子类可以重新定义基类的虚函数,我们把这个行为称之为复写(override)。 不管是虚函数还是纯虚函数,基类都可以为提供他们的实现(implementation),如果有的话子类可以调用基类的这些实现。 子类可自主选择是否要提供一份属于自己的个性化虚函数实现。 飞行,纯虚函数 }; 这是一个普通虚函数,意味着基类希望子类提供自己的个性化实现代码,但基类同时也提供一个缺省的虚函数实现版本,在子类不复写该虚函数的情况下作为备选方案 void aircraft::refuel 虚函数和纯虚函数都能做到这一点,区别是,子类如果不提供虚函数的实现,那就会自动调用基类的缺省方案。而子类如果不提供纯虚函数的实现,则编译将会失败。 第五,虚函数和普通的函数实际上是存储在不同的区域的,虚函数所在的区域是可被覆盖(也称复写override)的,每当子类定义相同名称的虚函数时就将原来基类的版本给覆盖了,另一侧面也说明了为什么基类中声明的虚函数在后代类中不需要另加声明一律自动为虚函数