C实验十一虚函数.docx
C++虚函数机制分析.doc

虚函数是面向对象编程语言里一个很重要的机制,下面我们以一个C++例了,分析其对应的C 语言程序来说明虚函数的机制。
面向对象有了一个垂要的概念就是对彖的实例,对彖的实例代表一个具体的对彖,故其肯定有一个数据结构保存这实例的数据,这一数据包括变量,接口函数指针,如果是虚函数, 则有相应的虚函数指针,其他函数指针不包括。
要讲虚函数机制,必须讲继承,因为只有继承才有虚函数的动态绑定功能,先讲下C++ 继承对彖实例内存分配基础知识:C++继承分为两种,普通继承和虚拟继承(virtual)0具体的继承乂根据父类中的两数是否virtual而不同。
下面就单继承分为几种情况阐述:1.普通继承+父类无virtual函数若子类没有新定义virtual函数此吋子类的布局是:由低地址->高地址为父类的元素(没有vptr), T类的元素(没有vptr).若子类有新定义virtual函数此时子类的布局是:由低地址-〉高地址为父类的元素(没有vptr),子类的元素(包含vptr,指向vtable.)2.普通继承+父类有virtual函数不管子类没有新定义virtiml函数此吋子类的布局是:由低地址-〉高地址为父类的元素(包含vptr),子类的元素.如果子类有新定义的virtual函数,那么在父类的vptr(也就是笫一个vptr)对应的vtable 中添加一个函数指针.3.v让tual继承若子类没有新定义virtual函数此时子类的布局是:由低地址-〉高地址子类的元素(有vptr),虚基类的元素.为什么这里会岀现vptr,因为虚基类派牛出來的类中,虚类的对象不在固定位置(猜测应该是在内存的尾部),需耍一个中介才能访问虚类的对彖.所以虽然没有virtual函数,了类也需要有一个vptr,对应的vtable屮需要有一项指向虚基类.若子类有新定义virtual两数此时子类的布局是与没有定义新virtual函数内存布局一致.但是在vtable中会多出新增的虚函数的指针.4.多重继承此时子类的布局是:由低地址-〉高地址为父类pl的元索Vpl按照实际情况确定元索屮是否包含vptr),父类p2的元素(p2按照实际情况确定元素中是否包含vptr),子类的元素.如果所冇父类都没冇vptr,那么如果了类定义了新的virtual function,那么子类的元索中会有vptr,对应的viable会有相应的函数指针.如果有的父类存在vptr.如果了类定义了新的virtual function,会生成一个了类的vtable,这个了类的vtable是,在它的父类的vtable屮后添加这个新的虚函数指针生成的. 因为子类分配的空间显示并没有新增加一•个4字节的指针空间,其实不管子类增加了多少新的虚函数,其空间大小不变,因为其和虚函数相关的分配的空间就是一个vptr,是一个指针,也就是4字节,不变,要变是变在vtable.比如如下一个类:Class testl () {};fun 1() {};pub I ic Virtual a() {println( "testl: a”);};public Virtual b(int b){println( "testl:b”);};int a;}Class test2 extends testl {fun2 () {};public Virtual b (int b) {this->b++;println( "test2:b” ) };pub lie Vi trual c ( ) {pi'intln ( "test2:c}”)}int b;}Int main () {testl a=new test2 ();a. b ();}首先我们看看下:类testl, test2实例大小及其内存分配图:testl的实例数据大小是:虚函数表指针(4) +iaptr接口指针+int变量大小(4) =12而test2的实例数据大小是:testl大小+其变屋b大小二12+4=16注意这是上面的提到的虚类继承,子类新增的虚函数不增加了类大小,只是在其虚函数表中体现。
C#之虚函数非常清晰全面的讲解

C#之虚函数⾮常清晰全⾯的讲解若⼀个实例⽅法声明前带有virtual关键字,那么这个⽅法就是虚⽅法。
虚⽅法与⾮虚⽅法的最⼤不同是,虚⽅法的实现可以由派⽣类所取代,这种取代是通过⽅法的重写实现的(以后再讲)虚⽅法的特点:虚⽅法前不允许有static,abstract,或override修饰符虚⽅法不能是私有的,因此不能使⽤private修饰符虚⽅法的执⾏:我们知道⼀般函数在编译时就静态地编译到了执⾏⽂件中,其相对地址在程序运⾏期间是不发⽣变化的,⽽虚函数在编译期间是不被静态编译的,它的相对地址是不确定的,它会根据运⾏时期对象实例来动态判断要调⽤的函数,其中那个申明时定义的类叫申明类,那个执⾏时实例化的类叫实例类。
如:A a =new B(); 其中A是申明类,B是实例类。
1.当调⽤⼀个对象的函数时,系统会直接去检查这个对象申明定义的类,即申明类,看所调⽤的函数是否为虚函数;2.如果不是虚函数,那么它就直接执⾏该函数。
⽽如果是⼀个虚函数,那么这个时候它就不会⽴刻执⾏该函数了,⽽是开始检查对象的实例类。
3.在这个实例类⾥,他会检查这个实例类的定义中是否有实现该虚函数或者重新实现该虚函数(通过override关键字)的⽅法,如果有,它就不会再找了,⽽是马上执⾏该实例类中实现的虚函数的⽅法。
⽽如果没有的话,系统就会不停地往上找实例类的⽗类,并对⽗类重复刚才在实例类⾥的检查,直到找到第⼀个重载了该虚函数的⽗类为⽌,然后执⾏该⽗类⾥重载后的函数。
class A{public virtual void Sum(){Console.WriteLine("I am A Class,I am virtual sum().");}}class Program{static void Main(string[] args){A a=new A(); // 定义⼀个a这个A类的对象.这个A就是a的申明类,实例化a对象,A是a的实例类a.Sum();Console.Read();}}执⾏a.Sum:1.先检查申明类A2.检查到是sum是虚拟⽅法3.转去检查实例类A,结果是题本⾝4.执⾏实例类A中实现Sum的⽅法5.输出结果 I am A Class,I am virtual sum().class A{public virtual void Sum(){Console.WriteLine("I am A Class,I am virtual sum().");}}class B : A{public override void Sum() // 重新实现了虚函数{Console.WriteLine("I am B Class,I am override sum().");}}class Program{static void Main(string[] args){A a=new B(); // 定义⼀个a这个A类的对象.这个A就是a的申明类,实例化a对象,B是a的实例类a.Sum();Console.Read();}}执⾏a.Sum:1.先检查申明类A2.检查到是虚拟⽅法3.转去检查实例类B,有重写的⽅法4.执⾏实例类B中的⽅法5.输出结果 I am B Class,I am override sum(). class A{public virtual void Sum(){Console.WriteLine("I am A Class,I am virtual sum().");}}class B : A{public override void Sum() // 重新实现了虚函数{Console.WriteLine("I am B Class,I am override sum().");}}class C : B{}class Program{static void Main(string[] args){A a=new C();// 定义⼀个a这个A类的对象.这个A就是a的申明类,实例化a对象,C是a的实例类a.Sum();Console.Read();}}执⾏a.Sum:1.先检查申明类A2.检查到是虚拟⽅法3.转去检查实例类C,⽆重写的⽅法4.转去检查类C的⽗类B,有重写的⽅法5.执⾏⽗类B中的Sum⽅法6.输出结果 I am B Class,I am override sum().例4:class A{public virtual void Sum(){Console.WriteLine("I am A Class,I am virtual sum().");}}class B : A{public new void Sum() //覆盖⽗类⾥的同名函数,⽽不是重新实现{Console.WriteLine("I am B Class,I am new sum().");}}class Program{static void Main(string[] args){A a=new B();a.Sum();Console.Read();}}执⾏a.Sum:1.先检查申明类A2.检查到是虚拟⽅法3.转去检查实例类B,⽆重写的(这个地⽅要注意了,虽然B⾥有实现Sum(),但没有使⽤override关键字,所以不会被认为是重写)4.转去检查类B的⽗类A,就为本⾝5.执⾏⽗类A中的Sum⽅法6.输出结果 I am A Class,I am virtual sum().那么如果在例4⾥,申明的是类B呢?class A{public virtual void Sum(){Console.WriteLine("I am A Class,I am virtual sum().");}}class B : A{public new void Sum() //覆盖⽗类⾥的同名函数,⽽不是重新实现{Console.WriteLine("I am B Class,I am new sum().");}}class Program{static void Main(string[] args){B b=new B();b.Sum();Console.Read();}}执⾏B类⾥的Sum(),输出结果I am B Class,I am new sum().可以使⽤抽象函数重写基类中的虚函数吗?答案是可以的。
C++虚函数表与虚析构函数

C++虚函数表与虚析构函数1.静态联编和动态联编联编:将源代码中的函数调⽤解释为要执⾏函数代码。
静态联编:编译时能确定唯⼀函数。
在C中,每个函数名都能确定唯⼀的函数代码。
在C++中,因为有函数重载,编译器须根据函数名,参数才能确定唯⼀的函数代码。
动态联编:编译时不能确定调⽤的函数代码,运⾏时才能。
C++中因为多态的存在,有时编译器不知道⽤户将选择哪种类型的对象,因此⽆法确定调⽤的唯⼀性,只有在运⾏时才能确定。
2.虚成员函数,指针或引⽤,动态联编指针或引⽤才能展现类的多态性。
当类中的⽅法声明为virtual时,使⽤指针或引⽤调⽤该⽅法,就是动态联编。
若是普通⽅法,则为静态联编。
⽰例如下:class Test{public:virtual show(){std::cout<<"Test::show()"<<std::endl;}};class SubTest:public Test{public:virtual show(){std::cout<<"SubTest::show()"<<std::endl;}};int main(){SubTest subTest;Test * p = &subTest;//指向⼦类的指针Test & a = subTest;//⼦类的引⽤Test * p2 = new Test;//指向⽗类的指针p->show();a.show();p2->show();return0;}程序没有释放内存,我们将在后⾯析构函数的时候,完善该程序。
3.动态联编使⽤原则动态联编,需要跟踪基类指针或引⽤指向的实际对象类型,因此效率低于静态联编。
1)当类不会⽤作基类时,成员函数不要声明为virtual2)当成员函数不重新定义基类的⽅法,成员函数不要声明为virtual4.关于虚函数1)⽗类成员函数若声明为virtual,则⼦类中也是虚的,若要重新定义该⽅法,可显式加上virtual关键字,也可不加,编译器编译时会⾃动加上。
C 中虚函数的作用

C++中虚函数的作用(多态的原理)2009-04-17 22:02■ 如果你期望衍生类别重新定义一个成员函数,那么你应该在基础类别中把此函数设为virtual。
■ 以单一指令唤起不同函数,这种性质称为Polymorphism,意思是"the ability toassume many forms",也就是多态。
■ 虚拟函数是C++ 语言的Polymorphism 性质以及动态绑定的关键。
■ 既然抽象类别中的虚拟函数不打算被调用,我们就不应该定义它,应该把它设为纯虚拟函数(在函数声明之后加上"=0" 即可)。
■ 我们可以说,拥有纯虚拟函数者为抽象类别(abstract Class),以别于所谓的具象类别(concrete class)。
■ 抽象类别不能产生出对象实体,但是我们可以拥有指向抽象类别之指针,以便于操作抽象类别的各个衍生类别。
■ 虚拟函数衍生下去仍为虚拟函数,而且可以省略virtual 关键词。
虚函数联系到多态,多态联系到继承.所以本文中都是在继承层次上做文章.没了继承,什么都没得谈.下面是对C++的虚函数这玩意儿的理解.一.什么是虚函数(如果不知道虚函数为何物,但有急切的想知道,那你就应该从这里开始)简单地说,那些被virtual关键字修饰的成员函数,就是虚函数.虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异而采用不同的策略.下面来看一段简单的代码: class A{public:void print(){ cout<<"This is A"<<endl;}};class B: public A{public:void print(){ cout<<"This is B"<<endl;}};int main() //为了在以后便于区分,我这段main()代码叫做main1{A a;B b;a.print();b.print();}通过class A和class B的print()这个接口,可以看出这两个class 因个体的差异而采用了不同的策略,输出的结果也是我们预料中的,分别是This is A和This is B.但这是否真正做到了多态性呢? No,多态还有个关键之处就是一切用指向基类的指针或引用来操作对象.那现在就把main()处的代码改一改.int main() //main2{A a;B b;A* p1=&a;A* p2=&b;p1->print();p2->print();}运行一下看看结果,哟呵,蓦然回首,结果却是两个This is A.问题来了,p2明明指向的是class B的对象但却是调用的class A的print()函数,这不是我们所期望的结果,那么解决这个问题就需要用到虚函数.class A{public:virtual void print(){ cout<<"This is A"<<endl;} //现在成了虚函数了};class B: public A{public:void print(){ cout<<"This is B"<<endl;} //这里需要在前面加上关键字virtual吗?};毫无疑问,class A的成员函数print()已经成了虚函数,那么class B 的print()成了虚函数了吗?回答是Yes,我们只需在把基类的成员函数设为virtual,其派生类的相应的函数也会自动变为虚函数.所以,class B的print()也成了虚函数.那么对于在派生类的相应函数前是否需要用virtual关键字修饰,那就是你自己的问题了.现在重新运行main2的代码,这样输出的结果就是This is A和This is B了.现在来消化一下,我作个简单的总结,指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数.二.虚函数是如何做到的(如果你没有看过《Inside The C++ Object Model》这本书,但又急切想知道,那你就应该从这里开始.)虚函数是如何做到因对象的不同而调用其相应的函数的呢?现在我们就来剖析虚函数.我们先定义两个类:class A{ //虚函数示例代码public:virtual void fun(){cout<<1<<endl;}virtual void fun2(){cout<<2<<endl;}};class B: public A{public:void fun(){cout<<3<<endl;}void fun2(){cout<<4<<endl;}};由于这两个类中有虚函数存在,所以编译器就会为他们两个分别插入一段你不知道的数据,并为他们分别创建一个表.那段数据叫做vptr指针,指向那个表.那个表叫做vtbl,每个类都有自己的vtbl,vtbl的作用就是保存自己类中虚函数的地址,我们可以把vtbl形象地看成一个数组,这个数组的每个元素存放的就是虚函数的地址,请看图通过上图,可以看到这两个vtbl分别为class A和class B服务.现在有了这个模型之后,我们来分析下面的代码:A *p=new A;p->fun();毫无疑问,调用了A::fun(),但是A::fun()是如何被调用的呢?它像普通函数那样直接跳转到函数的代码处吗? No,其实是这样的,首先是取出vptr的值,这个值就是vtbl的地址,再根据这个值来到vtbl这里,由于调用的函数A::fun()是第一个虚函数,所以取出vtbl第一个slot 里的值,这个值就是A::fun()的地址了,最后调用这个函数.现在我们可以看出来了,只要vptr不同,指向的vtbl就不同,而不同的vtbl里装着对应类的虚函数地址,所以这样虚函数就可以完成它的任务.而对于class A和class B来说,他们的vptr指针存放在何处呢?其实这个指针就放在他们各自的实例对象里.由于class A和class B都没有数据成员,所以他们的实例对象里就只有一个vptr指针.通过上面的分析,现在我们来实作一段代码,来描述这个带有虚函数的类的简单模型.#include<iostream>using namespace std;//将上面"虚函数示例代码"添加在这里int main(){void (*fun)(A*) ;A *p=newB ;long lVptrAddr ;memcpy(&lVptrAddr, p, 4) ;memcpy(&fun, reinterpret_cast<long*>(lVptrAddr), 4) ;fun(p) ;delete p ;system("pause");}用VC或Dev-C++编译运行一下,看看结果是不是输出3,如果不是,那么太阳明天肯定是从西边出来.现在一步一步开始分析: void (*fun)(A*);这段定义了一个函数指针名字叫做fun,而且有一个A*类型的参数,这个函数指针待会儿用来保存从vtbl里取出的函数地址;A* p=new B;这个我不太了解,算了,不解释这个了;long lVptrAddr;这个long类型的变量待会儿用来保存vptr的值;memcpy(&lVptrAddr,p,4);前面说了,他们的实例对象里只有vptr 指针,所以我们就放心大胆地把p所指的4bytes内存里的东西复制到lVptrAddr中,所以复制出来的4bytes内容就是vptr的值,即vtbl的地址;现在有了vtbl的地址了,那么我们现在就取出vtbl第一个slot里的内容;memcpy(&fun,reinterpret_cast<long*>(lVptrAddr),4);取出vtbl 第一个slot里的内容,并存放在函数指针fun里.需要注意的是lVptrAddr里面是vtbl的地址,但lVptrAddr不是指针,所以我们要把它先转变成指针类型;fun(p);这里就调用了刚才取出的函数地址里的函数,也就是调用了B::fun()这个函数,也许你发现了为什么会有参数p,其实类成员函数调用时,会有个this指针,这个p就是那个this指针,只是在一般的调用中编译器自动帮你处理了而已,而在这里则需要自己处理;delete p;和system("pause");这个我不太了解,算了,不解释这个了.如果调用B::fun2()怎么办?那就取出vtbl的第二个slot里的值就行了;memcpy(&fun,reinterpret_cast<long*>(lVptrAddr+4),4);为什么是加4呢?因为一个指针的长度是4bytes,所以加4,或者memcpy(&fun,reinterpret_cast<long*>(lVptrAddr)+1,4);这更符合数组的用法,因为lVptrAddr被转成了long*型别,所以+1就是往后移sizeof(long)的长度.三.以一段代码开始#include<iostream>using namespace std;class A{ //虚函数示例代码2public:virtual void fun(){ cout<<"A::fun"<<endl;}virtual void fun2(){cout<<"A::fun2"<<endl;} };class B: public A{public:void fun(){ cout<<"B::fun"<<endl;}void fun2(){ cout<<"B::fun2"<<endl;}}; //end虚函数示例代码2int main(){void (A::*fun)(); //定义一个函数指针A *p=new B;fun=&A::fun;(p->*fun)();fun = &A::fun2;(p->*fun)();delete p;system("pause");}你能估算出输出结果吗?如果你估算出的结果是A::fun和A::fun2,呵呵,恭喜恭喜,你中圈套了.其实真正的结果是B::fun和B::fun2,如果你想不通就接着往下看.给个提示,&A::fun和&A::fun2是真正获得了虚函数的地址吗?首先我们回到第二部分,通过段实作代码,得到一个"通用"的获得虚函数地址的方法.#include<iostream>using namespace std;//将上面"虚函数示例代码2"添加在这里void CallVirtualFun(void* pThis,int index=0){void (*funptr)(void*);long lVptrAddr;memcpy(&lVptrAddr,pThis,4);memcpy(&funptr,reinterpret_cast<long*>(lVptrAddr)+index,4);funptr(pThis); //调用}int main(){A* p=new B;CallVirtualFun(p); //调用虚函数p->fun()CallVirtualFun(p,1);//调用虚函数p->fun2()system("pause");}现在我们拥有一个"通用"的CallVirtualFun方法.这个通用方法和第三部分开始处的代码有何联系呢?联系很大.由于A::fun()和A::fun2()是虚函数,所以&A::fun和&A::fun2获得的不是函数的地址,而是一段间接获得虚函数地址的一段代码的地址,我们形象地把这段代码看作那段CallVirtualFun.编译器在编译时,会提供类似于CallVirtualFun这样的代码,当你调用虚函数时,其实就是先调用的那段类似CallVirtualFun的代码,通过这段代码,获得虚函数地址后,最后调用虚函数,这样就真正保证了多态性.同时大家都说虚函数的效率低,其原因就是,在调用虚函数之前,还调用了获得虚函数地址的代码.最后的说明:本文的代码可以用VC6和Dev-C++4.9.8.0通过编译,且运行无问题.其他的编译器小弟不敢保证.其中,里面的类比方法只能看成模型,因为不同的编译器的低层实现是不同的.例如this指针,Dev-C++的gcc就是通过压栈,当作参数传递,而VC的编译器则通过取出地址保存在ecx中.所以这些类比方法不能当作具体实现。
C++多态与虚函数实验

南阳理工学院C++上机实验指导书(2011版)软件学院·软件工程教研室2011.3C++上机实验指导书——软件学院·软件工程教研室[2011版]目录实验1 C++编程环境实践....................... 错误!未定义书签。
实验2 基本数据类型、运算符和表达式. (2)实验3 选择和循环结构(*)............... 错误!未定义书签。
实验4 指针与引用(*) ....................... 错误!未定义书签。
实验5 函数与重载.................................. 错误!未定义书签。
实验6 类与对象 ...................................... 错误!未定义书签。
实验7 运算符重载(*) ....................... 错误!未定义书签。
实验8 继承............................................... 错误!未定义书签。
实验9 多继承(*)................................ 错误!未定义书签。
实验10 多态与虚函数 (2)注:带“*”为选做实验,建议学生课后自行完成实验10 多态与虚函数一、实验目的1.理解多态的概念2.掌握如何用虚函数实现运行时多态3.掌握如何利用抽象类二、实验内容及步骤1.设计一个图形类(Shape),由它派生出5个派生类:三角形类(Triangle)、正方形类(Square)、圆形类(Circle)、矩形类(Rectangle)、梯形类(triangle)类,利用虚函数计算图形面积,用一个函数printArea分别输出以上5者的面积。
#include<iostream>#include<iomanip>#include<cmath>using namespace std;const double PI = 3.1415926;class Shape{public:virtual double GetArea() = 0;};class Triangle : public Shape{private:double a, b, c;public:double TArea;double GetArea();};double Triangle::GetArea(){cout << "请输入三角形的三边长: ";cin >> a >> b >> c;while((a < 0) || (b < 0) || (c < 0) || (a + b <= c) || (a + c <= b) || (b + c <= a)){cout << "输入的边长小于零或输入的边长不能构成三角形!" << endl;cout << "请重新输入三角形的三边长: ";cin >> a >> b >> c;}tmp = (a + b + c) / 2.0;TArea = sqrt(tmp * (tmp - a) * (tmp - b) * (tmp - c));return TArea;}class Rectangle : public Shape{private:double length, width;public:double RArea;double GetArea();};double Rectangle::GetArea(){cout << "请输入矩形的长和宽: ";cin >> length >> width;while(length < 0 || width < 0){cout << "矩形的长和宽不能小于零!" << endl;cout << "请重新输入矩形的长和宽: ";cin >> length >> width;}RArea = length * width;return RArea;}class Circle : public Shape{private:double Radius;public:double GetArea();};double Circle::GetArea(){cout << "请输入圆的半径: ";cin >> Radius;while(Radius < 0){cout << "圆的半径不能小于零!" << endl;cout << "请重新输入圆的半径: ";cin >> Radius;}CArea = 2 * PI * Radius;return CArea;}class Square : public Shape{private:double side;public:double SArea;double GetArea();};double Square::GetArea(){cout << "请输入正方形的边长: ";cin >> side;while(side < 0){cout << "正方形的边长不能小于零!" << endl;cout << "请重输入正方形的边长: ";cin >> side;}SArea = side * side;return SArea;}class triangle : public Shape{private:double UpBase, DownBase , Higth;public:double TArea;double GetArea();};double triangle::GetArea(){cout << "请输入梯形的上底,下底和高: ";cin >> UpBase >> DownBase >> Higth;while(UpBase < 0 || DownBase < 0 || Higth < 0){cout << "梯形的上底,下底和高不能小于零!" << endl;cout << "请重新输入梯形的上底,下底和高: ";cin >> UpBase >> DownBase >> Higth;}TArea = (UpBase + DownBase)* Higth / 2.0;return TArea;}void PrintArea(){Triangle myTri;Square mysqu;Circle mycir;Rectangle myrec;triangle mytri;cout << fixed << setprecision(2) << "三角形的面积: " << myTri.GetArea() << endl;cout << fixed << setprecision(2) << "正方形的面积: " << mysqu.GetArea() << endl;cout << fixed << setprecision(2) << "圆形的面积: " << mycir.GetArea() << endl;cout << fixed << setprecision(2) << "矩形的面积: " << myrec.GetArea() << endl;cout << fixed << setprecision(2) << "梯形的面积: " << mytri.GetArea() << endl;}int main(void){PrintArea();return 0;2.定义一个教师类,由教师类派生出讲师、副教授、教授类。
多态性和虚函数 实验报告

淮海工学院计算机科学系实验报告书课程名:《 C++程序设计(二)》题目:多态性和虚函数班级:学号:姓名:1、实验内容或题目(1)声明二维坐标类作为基类派生圆的类,把派生类圆作为基类,派生圆柱体类。
其中,基类二维坐标类有成员数据:x、y坐标值;有成员函数:构造函数实现对基类成员数据的初始化、输出的成员函数,要求输出坐标位置。
派生类圆类有新增成员数据:半径(R);有成员函数:构造函数实现对成员数据的初始化、计算圆面积的成员函数、输出半径的成员函数。
派生圆柱体类新增数据有高(H);新增成员函数有:构造函数、计算圆柱体体积的函数和输出所有成员的函数。
请完成程序代码的编写、调试。
(2)教材393页7-8题。
(3)教材416页1、4、5题。
2、实验目的与要求(1)理解继承与派生的概念(2)掌握通过继承派生出一个新的类的方法(3)了解多态性的概念(4)了解虚函数的作用与使用方法3、实验步骤与源程序⑴实验步骤先定义一个基类point,及其成员函数,然后以public的继承方式定义子类circle,再定义一个派生类cylinder,最后在main主函数中定义类对象,调用函数实现其功能。
先定义一个基类A及其重载的构造函数,然后以Public派生出子类B,再定义其构造函数,最后在main主函数中定义类对象,调用成员函数实现其功能。
⑵源代码1.#include <iostream.h>class Point{public:Point(float=0,float=0);void setPoint(float,float);float getX() const {return x;}float getY() const {return y;}friend ostream & operator<<(ostream &,const Point &); protected:float x,y;};Point::Point(float a,float b){x=a;y=b;}void Point::setPoint(float a,float b){x=a;y=b;}ostream & operator<<(ostream &output,const Point &p){cout<<"["<<p.x<<","<<p.y<<"]"<<endl;return output;}class Circle:public Point{public:Circle(float x=0,float y=0,float r=0);void setRadius(float);float getRadius() const;float area () const;friend ostream &operator<<(ostream &,const Circle &); protected:float radius;};Circle::Circle(float a,float b,float r):Point(a,b),radius(r){}void Circle::setRadius(float r){radius=r;}float Circle::getRadius() const {return radius;}float Circle::area() const{return 3.14159*radius*radius;}ostream &operator<<(ostream &output,const Circle &c){cout<<"Center=["<<c.x<<","<<c.y<<"], r="<<c.radius<<", area="<<c.area()<<endl;return output;}class Cylinder:public Circle{public:Cylinder (float x=0,float y=0,float r=0,float h=0);void setHeight(float);float getHeight() const;float area() const;float volume() const;friend ostream& operator<<(ostream&,const Cylinder&);protected:float height;};Cylinder::Cylinder(float a,float b,float r,float h):Circle(a,b,r),height(h){}void Cylinder::setHeight(float h){height=h;}float Cylinder::getHeight() const {return height;}float Cylinder::area() const{return 2*Circle::area()+2*3.14159*radius*height;}float Cylinder::volume() const{return Circle::area()*height;}ostream &operator<<(ostream &output,const Cylinder& cy){cout<<"Center=["<<cy.x<<","<<cy.y<<"], r="<<cy.radius<<", h="<<cy.height <<"\narea="<<cy.area()<<", volume="<<cy.volume()<<endl;return output;}int main(){Cylinder cy1(3.5,6.4,5.2,10);cout<<"\noriginal cylinder:\nx="<<cy1.getX()<<", y="<<cy1.getY()<<", r=" <<cy1.getRadius()<<", h="<<cy1.getHeight()<<"\narea="<<cy1.area()<<", volume="<<cy1.volume()<<endl;cy1.setHeight(15);cy1.setRadius(7.5);cy1.setPoint(5,5);cout<<"\nnew cylinder:\n"<<cy1;Point &pRef=cy1;cout<<"\npRef as a point:"<<pRef;Circle &cRef=cy1;cout<<"\ncRef as a Circle:"<<cRef;return 0;}2.(1)#include <iostream>using namespace std;class A{public:A(){a=0;b=0;}A(int i){a=i;b=0;}A(int i,int j){a=i;b=j;}void display(){cout<<"a="<<a<<" b="<<b;} private:int a;int b;};class B : public A{public:B(){c=0;}B(int i):A(i){c=0;}B(int i,int j):A(i,j){c=0;}B(int i,int j,int k):A(i,j){c=k;}void display1(){display();cout<<" c="<<c<<endl;}private:int c;};int main(){B b1;B b2(1);B b3(1,3);B b4(1,3,5);b1.display1();b2.display1();b3.display1();b4.display1();return 0;}(2)#include <iostream>using namespace std;class A{public:A(){cout<<"constructing A "<<endl;} ~A(){cout<<"destructing A "<<endl;} };class B : public A{public:B(){cout<<"constructing B "<<endl;} ~B(){cout<<"destructing B "<<endl;} };class C : public B{public:C(){cout<<"constructing C "<<endl;}~C(){cout<<"destructing C "<<endl;}};int main(){C c1;return 0;}3.(1)//Point.hclass Point{public:Point(float=0,float=0);void setPoint(float,float);float getX() const {return x;}float getY() const {return y;}friend ostream & operator<<(ostream &,const Point &); protected:float x,y;}//Point.cppPoint::Point(float a,float b){x=a;y=b;}void Point::setPoint(float a,float b){x=a;y=b;}ostream & operator<<(ostream &output,const Point &p){output<<"["<<p.x<<","<<p.y<<"]"<<endl;return output;}//Circle.h#include "point.h"class Circle:public Point{public:Circle(float x=0,float y=0,float r=0);void setRadius(float);float getRadius() const;float area () const;friend ostream &operator<<(ostream &,const Circle &);protected:float radius;};//Circle.cppCircle::Circle(float a,float b,float r):Point(a,b),radius(r){}void Circle::setRadius(float r){radius=r;}float Circle::getRadius() const {return radius;}float Circle::area() const{return 3.14159*radius*radius;}ostream &operator<<(ostream &output,const Circle &c){output<<"Center=["<<c.x<<","<<c.y<<"], r="<<c.radius<<", area="<<c.area()<<endl;return output;}//Cylinder.h#include "circle.h"class Cylinder:public Circle{public:Cylinder (float x=0,float y=0,float r=0,float h=0);void setHeight(float);float getHeight() const;float area() const;float volume() const;friend ostream& operator<<(ostream&,const Cylinder&);protected:float height;};//Cylinder.cppCylinder::Cylinder(float a,float b,float r,float h):Circle(a,b,r),height(h){}void Cylinder::setHeight(float h){height=h;}float Cylinder::getHeight() const {return height;}float Cylinder::area() const{ return 2*Circle::area()+2*3.14159*radius*height;}float Cylinder::volume() const{return Circle::area()*height;}ostream &operator<<(ostream &output,const Cylinder& cy){output<<"Center=["<<cy.x<<","<<cy.y<<"], r="<<cy.radius<<", h="<<cy.height<<"\narea="<<cy.area()<<", volume="<<cy.volume()<<endl;return output;}//main.cpp#include <iostream.h>#include "cylinder.h"#include "point.cpp"#include "circle.cpp"#include "cylinder.cpp"int main(){Cylinder cy1(3.5,6.4,5.2,10);cout<<"\noriginal cylinder:\nx="<<cy1.getX()<<", y="<<cy1.getY()<<", r=" <<cy1.getRadius()<<", h="<<cy1.getHeight()<<"\narea="<<cy1.area()<<", volume="<<cy1.volume()<<endl;cy1.setHeight(15);cy1.setRadius(7.5);cy1.setPoint(5,5);cout<<"\nnew cylinder:\n"<<cy1;Point &pRef=cy1;cout<<"\npRef as a point:"<<pRef;Circle &cRef=cy1;cout<<"\ncRef as a Circle:"<<cRef;return 0;}(2)#include <iostream>using namespace std;class Shape{public:virtual double area() const =0;class Circle:public Shape{public:Circle(double r):radius(r){} virtual double area() const {return 3.14159*radius*radius;}; protected:double radius;};class Rectangle:public Shape{public:Rectangle(double w,double h):width(w),height(h){} virtual double area() const {return width*height;} protected:double width,height; };class Triangle:public Shape{public:Triangle(double w,double h):width(w),height(h){} virtual double area() const {return 0.5*width*height;} protected:double width,height; };void printArea(const Shape &s)cout<<s.area()<<endl;} int main(){Circle circle(12.6);cout<<"area of circle =";printArea(circle);Rectangle rectangle(4.5,8.4);cout<<"area of rectangle =";printArea(rectangle);Triangle triangle(4.5,8.4);cout<<"area of triangle =";printArea(triangle);return 0;}(3)#include <iostream>using namespace std;class Shape{public:virtual double area() const =0;};class Circle:public Shape{public:Circle(double r):radius(r){}virtual double area() const {return 3.14159*radius*radius;}; protected:double radius;class Square:public Shape{public:Square(double s):side(s){}virtual double area() const {return side*side;}protected:double side;};class Rectangle:public Shape{public:Rectangle(double w,double h):width(w),height(h){}virtual double area() const {return width*height;}protected:double width,height; };class Trapezoid:public Shape{public:Trapezoid(double t,double b,double h):top(t),bottom(t),height(h){} virtual double area() const {return 0.5*(top+bottom)*height;} protected:double top,bottom,height;};class Triangle:public Shapepublic:Triangle(double w,double h):width(w),height(h){}virtual double area() const {return 0.5*width*height;} protected:double width,height;};int main(){Circle circle(12.6);Square square(3.5);Rectangle rectangle(4.5,8.4);Trapezoid trapezoid(2.0,4.5,3.2);Triangle triangle(4.5,8.4);Shape *pt[5]={&circle,&square,&rectangle,&trapezoid,&triangle};double areas=0.0;for(int i=0;i<5;i++){areas=areas+pt[i]->area();}cout<<"totol of all areas="<<areas<<endl;return 0;}4、测试数据与实验结果(可以抓图粘贴)5、结果分析与实验体会继承时,子类对基类的访问属性,基类的私有成员无论以何种方式继承在子类中都是不可访问的,唯有调用基类中的成员函数方可访问其私有变量。
虚函数纯虚函数普通函数.docx

C++在继承中虚函数、纯虚函数、普通函数,三者的区别1.虚函数(impure virtual)C++的帰函数主要作用是“运行时多态〃,父类屮提供虚函数的实现,为子类提供默认的函数实现。
子类可以重写父类的虚函数实现子类的特殊化。
如下就是一个父类中的虚函数:class A{public:virtual void out2(string s){cout«"A(out2):"«s«endl;}2.纯虚函数(pure virtual)C++中包含纯虚函数的类,被称为是"抽彖类〃。
抽彖类不能使用newtU对彖,只有实现了这个纯虚函数的子类才能new出对象。
C++中的纯虚函数更像是〃只提供申明,没有实现〃,是对子类的约束,是“接口继承〃。
C++中的纯虚函数也是一种“运行时多态〃。
如下而的类包含纯虚函数,就是“抽彖类〃:class A {public:virtual void outl(string s)=0;virtual void out2(string s){cout«"A(out2):"«s«endl;}};百3.普通函数(no-virtual)普通函数是静态编译的,没有运行时多态,只会根据指针或引用的“字面值〃类对象,调用自己的普通函数。
普通函数是父类为子类提供的“强制实现〃。
因此,在继承关系屮,子类不应该重写父类的普通函数,因为函数的调用至于类对彖的字面值有关。
4.程序综合实例心#inelude <iostream>using namespace std;class A {public:virtual void outl()=0; ///由子类实现virtual ~A(){};virtual void out2() ///默认实现cout«"A(out2)"«endl;}void out3() ///强制实现{ cout«"A(out3)"«endl;}};class B:public A{public:virtual ~B(){};void outl(){ cout«"B(outl)"«endl;}void out2(){ cout«"B(out2)"«endl;}void out3(){ cout«"B(out3)"«endl;}};int main(){A *ab=new B;ab->outl();ab->out2();ab->out3();cout«' 1 * * * ************* *** ♦*♦**!•«endl;B *bb=new B;bb->outl();bb->out2();bb->out3();delete ab;delete bb;return 0;}C++虚函数与纯虚函数用法与区别(转)1.虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽彖类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class)。
C++程序设计实验 虚函数的定义

《C++程序设计》实验报告准考证号xxxxxx题目:虚函数的定义与使用姓名xxx 日期xxx 实验环境:Visual C++ 6.0- 1 -- 2 - 实验内容与完成情况实验目的:1,掌握虚函数定义的一般格式2,掌握通过构造函数和析构函数调用虚函数的规则3,掌握通过对象、指针、引用调用虚函数的规则4,掌握虚函数产生动态联编的条件实验内容:1,完成基类、派生类的定义定义类A ,B ,C ,并类A 公有派生B ,B 公有派生CA ,B ,C 中各自定义构造函数和析构函数,在构造函数和析构函数中调用相关虚函数 2,在类A ,B ,C 中分别定义相关成员函数和虚函数,能够产生相关输出结果 3,设计main 函数创建派生类对象,基类指针,基类引用通过派生类对象调用虚函数函数通过基类引用调用虚函数通过基类指针调用虚函数并输出结果源程序代码:#include <iostream>using namespace std;class A{public:A(){ cout<<"A 构造完成"<<endl; }virtual void ha(){ cout<<"A->ha 函数"<<endl;}~A(){ cout<<"B 析构"<<endl;}virtual void hb(){ cout<<"A->hb 函数"<<endl;}};class B:public A{public:B(){ cout<<"B 构造调用"<<endl; cout<<"B 构造函数中调用ha 函数:";ha();cout<<"B 构造调用完成"<<endl;}void h(){ cout<<"B->h 函数"<<endl; cout<<"B 类h 中调用ha 函数:";ha();}出现的问题解决方案(列出遇到的问题和解决办法,列出未解决的问题)- 3 -。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
实验十一虚函数
一、实验目的
1)掌握虚函数的定义和使用
2)掌握抽彖类的定义和使用
3)掌握纯虚函数的定义
4)掌握虚析构函数、纯虚函数和抽象类的作用
二、实验原理
1.利用虚函数的作用:当编译器编译虚函数吋,编译系统将用动态连接的方式进行编译。
即在编译时不确定该虚函数的版本,而是利用一种机制在运动过程屮根据其所指向的实例决定使用哪一个函数版本。
2.利用虚析构函数的原则:当将基类指针或引用new运算符指向派生类对象时, 为了在释放派牛类对象时能调用派牛类的析构函数,必须将基类的析构函数定义为虚函数。
3.抽彖类的作用:为它的所有派生类提供一个公共接口,纯虚函数是定义抽彖类的一种间接手段。
三、实验设备
实验室里调备的计算机、window xp,visual c++6.0
四、实验内容
4.1分析下面各题程序,按各题的要求进行实验
1)分析下面的程序,指出程序运行的结果:
# include<iostream.h>
class CBase
{public:
virtual void fl() 〃将成员函数fl()声明为虚函数
{cout«n调用函数CBase::fl()!H«endl;}
virtual void f2() 〃将成员函数f2()声明为虚函数
{cout«"调用函数CBase::f2()!"«endl;}
void f3() //一般成员函数
{cout«n调用函数CBase::f3()!H«endl;}
};
class CDerived:public CBase 〃公有继承CBase
{void fl()
{cout«n调用函数CDerived::fl()!M«endl;}
void f2()
{cout«n调用函数CDerived::f2()!M«endl;}
void f3()
{cout«"调用函数CDerived::f3()! "«endl;}
};
void main()
{CBase objl,*p; 〃定义CBase的对象objl和指针对象p
CDerived obj2; 〃定义CDerived 的对彖obj 1
p=&objl;//将objl的地址给p
p->fl();〃通过p调用基类版本的fl()函数
p->f2(); 〃通过p调用基类版本的f2()函数p->f3(); 〃通过p调用基类版本的f3()函数p=&obj2;//将派生类对象obj2的地址赋给p p->fl();〃动态连接,调用派生类版本
p->f2();〃动态连接,调用派生类版木p->f3(); // 调用基类版本
运行的结果:
2)分析下面的程序,指出程序运行的结果:
#include<iostream.h>
class CBase
{public:
CBase() //CBase类的构造函数
{cout«M调用构造函数CBase()!u«endl;
fun();} 〃构造函数屮调用虚函数fun()
virtual void fun() 〃定义虚函数
{cout«"调用构造函数CBase::fun()!"«endl;}
};
class CDerived:public CBase
{public:
CDerived() //CDerived 类的构造函数
{cout«"调用构造函数CDerived()! "«endl;
fun();} 〃构造函数中调用虚函数fun()
void fun()
{cout«H调用构造函数CDerived::fun()!H«endl;}
};
void main()
{CDerived d; 〃创建对象
d.fun();
}
运行结果:
3)分析下面的程序,指出程序的错误
# include<iostream.h>
#include<string.h> class CBase
{protected:
char *ch;
public:
CBase(char *x)
{ch二new char[20];
strcpy(ch,x);
}
virtual void fun()=0;
virtual void fun 1() {cout«x«endl;}
〃虚函数
{cout«ch«endl;) ~CBase()
{delete[]ch; cout«"~CBase()"«endl;
}
};
class CDerived:public CBase
{protected:
char *ch;
public:
CDerived (char *x,char*y):CBase(y) {ch=new char[20];
strcpy(ch,x);}
void fun 1()
// {cout«x«endl;J {cout«ch«endl;) Virtual ~CDerived()
{delete[]ch; cout«H-CDerived()n«endl;} };
void main()
{CBase objl(n Hello n),*p;
p=&obj 1;
p->funl();
p->fun();
p=new CDerived(” ChinaTHello”);
p->funl();
p->fun();
delete p;
运行结果:
4. 2编写并调试程序
1)编写一个程序计算三角,正方形和圆形的面积。
分析:依题意,可以抽象出一个基类CBase,在其中说明一个虚函数,用來求面积,并利用单接口、多实现版本设计各个图形求面积的方法。
(参考教材P2095.25)
源程序:
运行结果:
2)编写一个程序计算正方体、球体和圆柱体的表面积和体积。
分析:依题意,抽象出一个公共基类CContaineer为抽象类,在其屮定义求表面积和体积的虚函数(该抽象类本身没有表面积和体积可言)。
抽象类中定义一个公共的数据成员radius, 此数据可作为球体的半径、正方体的边长、圆柱体底血积圆半径。
由此抽象类派生出要描述的三个类,在这三个类中都具有求表面积和体积的重定义版本。
参考例5.27
源程序:
运行结果:
实验总结:。