C实验十一虚函数.docx
- 格式:docx
- 大小:58.72 KB
- 文档页数:4
虚函数是面向对象编程语言里一个很重要的机制,下面我们以一个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#之虚函数⾮常清晰全⾯的讲解若⼀个实例⽅法声明前带有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++虚函数表与虚析构函数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++中虚函数的作用(多态的原理)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++上机实验指导书(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、结果分析与实验体会继承时,子类对基类的访问属性,基类的私有成员无论以何种方式继承在子类中都是不可访问的,唯有调用基类中的成员函数方可访问其私有变量。
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++程序设计》实验报告准考证号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 -。
C++里的虚拟函数Sunn y.ma n我们做了五个图形类分别是CEIlips巳CCircI巳CCrect,CSquare,CTriange •它l.int ReadDataFromBuf(BYTE *buf);〃从文件中读]数据填充各个成员变量们都继承自CCShape ,都有如下两个函数:2.void Draw(CDC *pDC);向指定的场景pDC画出图形。
如果我们不用虚函数而用一般函数从文件中读出来,并画到相应的DC上。
我们的写法一走是这样的伪代码Int nCount=*Buffer[0];//多少个图形For(int i=0;i<nCount;i++){Tf(Buffer[k]== “Rect”)//每个图形名字固定用四个字节{K二K+4;CCrect *prect=new CCrect;Int nByte=prect. ReadDataFromBuf(&Buffer[k]);m_listShape. AddTail((CCShape *)prect); K+=nByte;}If (Buffer [k]== "Circ" ) //每个图形名字固定用四个字节{K=K+4;CCircle *pcircle=new CCircle;Int nByte= pcircle. ReadDataFromBuf(&Buffer[k]);m listShape. AddTail((CCShape *) pcircle);K+=nByte;}//所有图形的读过程}然后画的时候For (int i=0; KnCount; i++){CShcipe *pShcipe=m_listShtipe・ GetAt (pos);If (pShcipe->GetName()==w Rect v )CCRect *prect= (CCrect *)pShape;Preet->Draw(pDC);}If(pShape->GetName()== w Circ”){CCircle *pcircle=( CCircle *)pShcipe:pcircle ->Draw(pDC);}〃所有图形的写法}如果我们有一百个类,那么我们需要写一百次,那一万次呢???有没有什么办法,让这些派生类可以自己完成Draw呢?我们都知道所有的类都继承自CCShape ,那么给这个类一个Draw不就行了吗,答案是不可以的,因为这样一来,所有的类调用的都是CCShape::Draw不会达到我们想要的效果。
C语言虚函数中的特定函数简介C语言是一种面向过程的编程语言,并不直接支持面向对象的概念,其中包括了“类”、“对象”、“继承”等概念。
然而,通过使用一些技巧和设计模式,我们可以在C语言中实现类似于面向对象的功能,其中一个重要的概念就是虚函数。
虚函数是一种特殊的函数,它可以在派生类中被重写,从而实现多态。
虚函数的定义、用途和工作方式是C语言中面向对象编程的重要部分,本文将详细介绍这些内容。
虚函数的定义在C语言中,虚函数的定义需要使用函数指针和结构体实现。
我们可以使用函数指针将一个函数地址赋值给一个结构体中的成员变量,从而形成一个具有特定功能的“方法”。
这样,我们就可以通过这个函数指针来调用结构体中的函数,实现类似于面向对象中对象的方法调用的功能。
下面是一个虚函数的定义示例:typedef struct {void (*function_ptr)(void);} VTable;void function1(void) {printf("This is function1\n");}void function2(void) {printf("This is function2\n");}VTable vtable = {.function_ptr = function1};在上述示例中,我们使用typedef定义了一个VTable结构体,其中有一个function_ptr成员变量,它是一个指向函数的指针。
我们定义了两个函数function1和function2,并分别赋值给了vtable中的function_ptr成员变量。
虚函数的用途虚函数的主要用途是实现多态,使不同类型的对象可以调用相同的接口名称,但执行不同的操作。
通过使用虚函数,我们可以在C语言中实现类似于面向对象的继承和多态的功能。
在面向对象的编程中,我们可以定义一个基类(或接口),然后派生出不同的子类,每个子类都可以重写基类的虚函数,以实现它们自己的特定行为。
C中的虚函数(virtualfunction)一.简介虚函数是C++中用于实现多态(polymorphism)的机制。
核心理念就是通过基类访问派生类定义的函数。
假设我们有下面的类层次:class Father{public:virtual void foo() { cout << "Father::foo() is called" << endl;} };class Sun: public Father{public:virtual void foo() { cout << "Sun::foo() is called" << endl;}};那么,在使用的时候,我们可以:Father * a = new Sun(); //Father * a = new Father(); 如果是这样的被调用的函数(foo)就是Father的a->foo(); // 在这里,a虽然是指向Father的指针,但是被调用的函数(foo)却是Sun的!这个例子是虚函数的一个典型应用,通过这个例子,也许你就对虚函数有了一些概念。
它虚就虚在所谓“推迟联编”或者“动态联编”上,一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。
由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为“虚”函数。
虚函数只能借助于指针或者引用来达到多态的效果,如果是下面这样的代码,则虽然是虚函数,但它不是多态的:class Father{public:virtual void foo();};class Sun: public Father{virtual void foo();};void bar(){Father a;a.foo(); // Father::foo()被调用}1.1 多态在了解了虚函数的意思之后,再考虑什么是多态就很容易了。
实验十一_虚函数答案实验十一虚函数一、实验目的1)掌握虚函数的定义和使用2)掌握抽象类的定义和使用3)掌握纯虚函数的定义4)掌握虚析构函数、纯虚函数和抽象类的作用二、实验原理1.利用虚函数的作用:当编译器编译虚函数时,编译系统将用动态连接的方式进行编译。
即在编译时不确定该虚函数的版本,而是利用一种机制在运动过程中根据其所指向的实例决定使用哪一个函数版本。
2.利用虚析构函数的原则:当将基类指针或引用new运算符指向派生类对象时,为了在释放派生类对象时能调用派生类的析构函数,必须将基类的析构函数定义为虚函数。
3.抽象类的作用:为它的所有派生类提供一个公共接口,纯虚函数是定义抽象类的一种间接手段。
三、实验设备实验室里调备的计算机、window xp,visual c++6.0四、实验内容4.1分析下面各题程序,按各题的要求进行实验1)分析下面的程序,指出程序运行的结果:#includeclass CBase{public:virtual void f1() //将成员函数f1()声明为虚函数{cout<<"调用函数CBase::f1()!"<<endl;}< p="">virtual void f2() //将成员函数f2()声明为虚函数{cout<<"调用函数CBase::f2()!"<<endl;}< p="">void f3() //一般成员函数{cout<<"调用函数CBase::f3()!"<<endl;}< p=""> };class CDerived:public CBase //公有继承CBase {void f1(){cout<<"调用函数CDerived::f1()!"<<endl;}< p=""> void f2(){cout<<"调用函数CDerived::f2()!"<<endl;}< p=""> void f3(){cout<<"调用函数CDerived::f3()!"<<endl;}< p=""> };void main(){CBase obj1,*p; //定义CBase的对象obj1和指针对象p CDerived obj2; //定义CDerived的对象obj1p=&obj1 //将obj1的地址给pp->f1(); //通过p调用基类版本的f1()函数p->f2(); //通过p调用基类版本的f2()函数p->f3(); //通过p调用基类版本的f3()函数p=&obj2 //将派生类对象obj2的地址赋给pp->f1(); //动态连接,调用派生类版本p->f2(); //动态连接,调用派生类版本p->f3(); // 调用基类版本}运行的结果:调用函数CBase::f1()!调用函数CBase::f2()!调用函数CBase::f3()!调用函数CDerived::f1()!调用函数CDerived::f2()!调用函数CBase::f3()!Press any key to continue2)分析下面的程序,指出程序运行的结果:#includeclass CBase{public:CBase() //CBase类的构造函数{cout<<"调用构造函数CBase()!"<<endl;< p="">fun();} //构造函数中调用虚函数fun() virtual void fun() //定义虚函数{cout<<"调用构造函数CBase::fun()!"<<endl;}< p="">};class CDerived:public CBase{public:CDerived() //CDerived类的构造函数{cout<<"调用构造函数CDerived()!"<<endl;< p="">fun();} //构造函数中调用虚函数fun() void fun(){cout<<"调用构造函数CDerived::fun()!"<<endl;}< p="">};void main(){CDerived d; //创建对象d.fun();}运行结果:调用构造函数CBase()!调用构造函数CBase::fun()!调用构造函数CDerived()!调用构造函数CDerived::fun()!调用构造函数CDerived::fun()!Press any key to continue3)分析下面的程序,指出程序的错误#include#includeclass CBase{protected:char *ch;public:CBase(char *x){ch=new char[20];strcpy(ch,x);}virtual void fun()=0;virtual void fun1() //虚函数{cout<<x<<endl;}< p="">{cout<<ch<<endl;}< p="">~CBase(){delete[]ch; cout<<"~CBase()"<<endl;< p="">}};class CDerived:public CBase{protected:char *ch;public:CDerived (char *x,char*y):CBase(y){ch=new char[20];strcpy(ch,x);}void fun1()// {cout<<x<<endl;}< p="">{cout<<ch<<endl;}< p="">Virtual ~CDerived(){delete[]ch; cout<<"~CDerived()"<<endl;}< p=""> void main(){CBase obj1("Hello"),*p;p=&obj1p->fun1();p->fun();p=new CDerived("China","Hello"); p->fun1(); p->fun();delete p;}运行结果:HelloChina~CBase()~CBase()Press any key to continue修正后的源代码:#include#include#includeusing namespace std;class CBase{protected:char *ch;public:CBase(const char *x){ch = new char[20];strcpy(ch, x);}virtual void fun() {};virtual void fun1(){cout << ch << endl;};~CBase(){delete []ch;cout << "~CBase()" << endl;}};class CDerived: public CBaseprotected:char *ch;public:CDerived(const char *x, const char *y): CBase(y) {ch = new char[20];strcpy(ch, x);}void fun1(){cout << ch << endl;}~CDerived(){delete []ch;cout << "~CDerived()<<endl;";< p="">}};int main()CBase obj1("Hello"), *p;p = &obj1p->fun1();p->fun();p = new CDerived("China", "Hello");p->fun1();p->fun();delete p;return 0;}4.2编写并调试程序1)编写一个程序计算三角,正方形和圆形的面积。
什么是虚函数?简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。
为什么要引入虚函数?虚函数的作用是实现类的继承所体现的多态性,具体点是实现动态联编。
从程序的角度上来说,在定义了虚函数后,可以在派生类中对虚函数重新定义,以实现统一的接口,不同定义过程,在程序的运行阶段动态地选择合适的成员函数。
什么是多态性?简单点说,多态性是将接口与实现进行分离;C++实现运行时多态性的关键途径:在公有派生情况下,一个指向基类的指针可用来访问从基类继承的任何对象。
语法:普通函数的前面加上virtual[cpp]view plaincopyprint?1.virtual函数返回值类型虚函数名(形参表)2.{3.//函数体4.}虚函数的调用方式:只能通过指向基类的指针或基类对象的引用来调用虚函数调用语法:[cpp]view plaincopyprint?1.指向基类的指针变量名->虚函数名(实参表)2.基类对象的引用名. 虚函数名(实参表)注意:正常情况下,如果不把函数声明为虚函数,指向基类的指针的访问情况如下:1)基类指针指向基类对象:基类指针可以直接访问基类对象中的成员2)基类指针指向派生类对象:基类指针只能访问派生类中的从基类中继承的成员,派生类有同名的函数或成员,也只能调用基类的成员。
如果定义成虚函数时:定义一个基类指针,把不同的派生类对象付给它,会调用对应派生类的函数,而非基类函数。
举例:[cpp]view plaincopyprint?1.#include <iostream>ing namespace std;3.class A4.{5.public:6.virtual void show()7. {8. cout<<"A"<<endl;9. }10.};11.class B:public A12.{13.public:14.void show()15. {16. cout<<"B"<<endl;17. }18.};19.class C:public A20.{21.public:22.void show()23. {24. cout<<"C"<<endl;25. }26.};27.void main()28.{29. A*a;30. B b;31. C c;32. a=&b;33. a->show();34. a=&c;35. a->show();36. system("pause");37.}运行结果:B(换行)C(换行)--指向不同的派生类,调用不同的函数如果不加基类A中的Virtual,则输出结果:A(换行)A(换行)--基类指针,调用派生类中继承的基类成分定义虚函数,实现动态联编需要三个条件:1)必须把动态联编的行为定义为类的虚函数---定义虚函数2)类之间存在子类型关系,一般表现为一个类从另一个类公有派生而来---类之间是公有继承3)基类指针指向派生类的对象,然后使用基类指针调用虚函数注意:1、使用时,虚函数可以在基类中声明,提供界面。
实验报告(五)实验名称:多态和虚函数一、实验目的:1.掌握虚函数的定义及使用,对多态性的支持;二、实验内容:1.设计一个图形类(Shape),由它派生出三角形类(Triangle)、正方形类(Square)、圆形类(Circle),利用虚函数计算图形面积和周长,并在主函数中进行测试。
2.定义一个教师类,由教师类派生出讲师、副教授、教授类。
教师的工资分别由基本工资、课时费和津贴构成。
假设讲师、副教授、教授的基本工资分别为500、600、900元,课时费分别为每小时20、30、50元,津贴分别为300、500、1000。
定义虚函数来计算教师的工资,并通过主函数来进行验证。
三、实验过程及记录:按实验要求分别派生出相应的函数,然后在主函数中定义类的指针,分别对相应的虚函数扫描一遍输出结果即可四、实验结果及分析:要知道虚函数虚函数构造和在派生类中调用的格式,另外需注意派生类名称后面只有一个“:”号,再要注意主函数中如何用类型的指针调用函数输出结果的附源码:1的源码:#include<iostream>#include<cmath>using namespace std;class shape{public:virtual void Area(){cout<<"这是基类shape的求周长和求面积的函数"<<endl;}};class Triangle:public shape{public:int a,b,c,p;double s;void Area(){cout<<"这是派生类Triangle的求三角形周长和面积的函数"<<endl;cout<<"请输入三角的三条边长:"<<endl;cin>>a>>b>>c;p=a+b+c;s=sqrt(p*(p-a)*(p-b)*(p-c));cout<<"三角形周长为:"<<p<<endl;cout<<"三角形面积为:"<<s<<endl;}};class Circle:public shape{public:int r;double s,p;void Area(){cout<<"这是派生类Circle的求圆的周长和面积的函数"<<endl;cout<<"请输入圆的半径:"<<endl;cin>>r;p=2*3.1416*r;s=r*r*3.1416;cout<<"圆的周长为:"<<p<<endl;cout<<"圆的面积为:"<<s<<endl;}};class Square:public shape{int a;int s,p;void Area(){cout<<"这是派生类Square的求正方形的周长和面积的函数"<<endl;cout<<"请输入正方形的边长:"<<endl;cin>>a;p=4*a;s=a*a;cout<<"正方形的周长为:"<<p<<endl;cout<<"正方形的面积为:"<<s<<endl;}};void main(){shape s,*p_s;Triangle e;Circle f;Square h;p_s=&s;p_s->Area();p_s=&e;p_s->Area();p_s=&f;p_s->Area();p_s=&h;p_s->Area();}2的源码:#include<iostream>using namespace std;class Teacher{public:virtual void Money(){cout<<"这是基类Teacher的求工资的函数"<<endl;}};class jiangshi:public Teacher{public:int s,h;void Money(){cout<<"这是派生类jiangshi的求讲师工资的函数"<<endl;cout<<"请输入课时时间:"<<endl;cin>>h;s=800+20*h;cout<<"讲师的工资为:"<<s<<endl;}};class fujiaoshou:public Teacher{public:int s,h;void Money(){cout<<"这是派生类fujiaoshou的求副教授工资的函数"<<endl;cout<<"请输入课时时间:"<<endl;cin>>h;s=1100+30*h;cout<<"副教授的工资为:"<<s<<endl;}};class jiaoshou:public Teacher{public:int s,h;void Money(){cout<<"这是派生类jiaoshou的求教授工资的函数"<<endl;cout<<"请输入课时时间:"<<endl;cin>>h;s=1900+50*h;cout<<"副教授的工资为:"<<s<<endl;}};void main(){Teacher p,*p_p;jiangshi e;fujiaoshou f;jiaoshou h;p_p=&p;p_p->Money();p_p=&e;p_p->Money();p_p=&f;p_p->Money();p_p=&h;p_p->Money();}。
C++:虚函数的详解5.4.2 虚函数详解1.虚函数的定义虚函数就是在基类中被关键字virtual说明,并在派⽣类重新定义的函数。
虚函数的作⽤是允许在派⽣类中重新定义与基类同名的函数,并且可以通过基类指针或引⽤来访问基类和派⽣类中的同名函数。
虚函数的定义是在基类中进⾏的,它是在基类中需要定义为虚函数的成员函数的声明中冠以关键字virtual。
定义虚函数的格式如下:virtual 函数类型函数名(形参表){函数体}在基类中的某个成员函数声明为虚函数后,此虚函数就可以在⼀个或多个派⽣类中被重新定义。
在派⽣类中重新定义时,其函数类型、函数名、参数个数、参数类型的顺序,都必须与基类中的原型完全相同。
//例 5.21 虚函数的使⽤#include<iostream>using namespace std;class B0{public:virtual void print(char *p) //定义基类B0中的虚函数{cout<<p<<"print()"<<endl;}};class B1:public B0{public:virtual void print(char *p) //定义基类B0的公有派⽣类B1中的虚函数{cout<<p<<"print()"<<endl;}};class B2:public B1{public:virtual void print(char *p) //定义基类B1的公有派⽣类B2中的虚函数{cout<<p<<"print()"<<endl;}};int main(){B0 ob0,*op; //定义基类对象ob0和对象指针opop=&ob0;op->print("B0::"); //调⽤基类的B0的print()B1 ob1; //定义派⽣类B1的对象ob1op=&ob1;op->print("B1::"); //调⽤派⽣类B1的print()B2 ob2; //定义派⽣类B2的对象ob2op=&ob2;op->print("B2::"); //调⽤派⽣类B2的print()return0;}/*在程序中,语句op->print();出现了3次,由于op指向的对象不同,每次出现都执⾏了相应对象的虚函数print程序运⾏结果:B0::print()B1::print()B2::print()说明:(1)若在基类中,只是声明虚函数原型(需要加上virtual),⽽在类外定义虚函数时,则不必再加上virtual。
程序设计二(面向对象)_实训13_虚函数
一、虚函数的概念
虚函数是C++中的一个重要特性,它允许在派生类中重新定义基类中已存在的函数,并且能够通过基类指针或引用来调用派生类中重定义的函数。
虚函数在实现多态性方面有着重要的作用,是C++中的面向对象编程的关键之一。
二、虚函数的定义和实现
虚函数的定义和普通函数的定义类似,只需要在函数声明前面加上virtual关键字即可,例如:
virtual void func();
虚函数的实现方式有两种:
1. 声明和定义都在类内部
在类内部声明虚函数时,需要在函数名的后面加上“=0”,即表示这个函数只是一个接口,没有具体的实现。
例如:
这种方式称为纯虚函数,不能直接调用。
//定义
void className::func()
{
//具体实现
}
1. 多态性
例如:
class Animal {
public:
virtual void speak() {
cout << "Animal speaking!" << endl;
}
};
2. 纯虚函数和抽象类
如果一个类中包含一个纯虚函数,那么这个类就称为抽象类,它不能被实例化,只能作为其他类的基类使用。
四、总结
虚函数是C++中面向对象编程的重要特性,它能够实现多态性,提高程序的灵活性和可扩展性。
纯虚函数和抽象类可以用来定义接口函数,抽象类不能被实例化,只能作为其他类的基类使用。
虚函数需要注意继承和多态性所带来的一些问题,例如对象切片问题、虚构造函数、虚析构函数等。
实验十一虚函数
一、实验目的
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
源程序:
运行结果:
实验总结:。