




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、内联函数:定义内联函数从源代码层看,有函数的结构,而在编译后,却不具备函数的性质。编译时,类似宏替换,使用函数体替换调用处的函数名。一般在代码中用inline修饰,但是否能形成内联函数,需要看编译器对该函数定义的具体处理。 实现有两种实现方式: 1.在类声明的内部声明,而在类声明外部定义叫做显式内联函数,如: class display int t; public: void output(void);display object; inline void display:output(void) cout << "i is " << i <&
2、lt;"n" 2.在类声明的内部定义,叫做隐式内联函数,如: class display int t; public: inline void output(void) cout<<"i is "<< i << "n" 实际应用引入内联函数的目的是为了解决程序中函数调用的效率问题。 函数是一种更高级的抽象。它的引入使得编程者只关心函数的功能和使用方法,而不必关心函数功能的具体实现;函数的引入可以减少程序的目标代码,实现程序代码和数据的共享。但是,函数调用也会带来降低效率的问题,因为调用函数实际上将程
3、序执行顺序转移到函数所存放在内存中某个地址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方。这种转移操作要求在转去前要保护现场并记忆执行的地址,转回后先要恢复现场,并按原来保存地址继续执行。因此,函数调用要有一定的时间和空间方面的开销,于是将影响其效率。特别是对于一些函数体代码不是很大,但又频繁地被调用的函数来讲,解决其效率问题更为重要。引入内联函数实际上就是为了解决这一问题。 在程序编译时,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体来进行替换。显然,这种做法不会产生转去转回的问题,但是由于在编译时函数体中的代码被替代到程序中,因此会增加目标程序代码量,进而增加空间开
4、销,而在时间开销上不象函数调用时那么大,可见它是以目标代码的增加为代价来换取时间的节省。 在程序中,调用其函数时,该函数在编译时被替代,而不是像一般函数那样是在运行时被调用。 注意事项使用内联函数应注意的事项 内联函数具有一般函数的特性,它与一般函数所不同之处只在于函数调用的处理。一般函数进行调用时,要将程序执行权转到被调用函数中,然后再返回到调用它的函数中;而内联函数在调用时,是将调用表达式用内联函数体来替换。在使用内联函数时,应注意如下几点: 1.在内联函数内不允许用循环语句和开关语句。 如果内联函数有这些语句,则编译将该函数视同普通函数那样产生函数调用代码,递归函数(自己调用自己的函数)
5、是不能被用来做内联函数的。内联函数只适合于只有15行的小函数。对一个含有许多语句的大函数,函数调用和返回的开销相对来说微不足道,所以也没有必要用内联函数实现。 2.内联函数的定义必须出现在内联函数第一次被调用之前。 3.本栏目讲到的类结构中所有在类说明内部定义的函数是内联函数。 虚函数:定义虚函数必须是基类的非静态成员函数,其访问权限可以是protected或public,在基类的类定义中定义虚函数的一般形式: virtual 函数返回值类型 虚函数名(形参表) 函数体 作用虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数,在定义了虚函数后,可以在基类的派生类中对虚函
6、数重新定义,在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型。以实现统一的接口,不同定义过程。如果在派生类中没有对虚函数重新定义,则它继承其基类的虚函数。 当程序发现虚函数名前的关键字virtual后,会自动将其作为动态联编处理,即在程序运行时动态地选择合适的成员函数。 动态联编规定,只能通过指向基类的指针或基类对象的引用来调用虚函数,其格式: 指向基类的指针变量名->虚函数名(实参表) 或 基类对象的引用名. 虚函数名(实参表) 虚函数是C+多态的一种表现 例如:子类继承了父类的一个函数(方法),而我们把父类的指针指向子类,则必须把父类的该函数(方法)设为virtual(
7、虚函数)。 使用虚函数,我们可以灵活的进行动态绑定,当然是以一定的开销为代价。 如果父类的函数(方法)根本没有必要或者无法实现,完全要依赖子类去实现的话,可以把此函数(方法)设为virtual 函数名=0 我们把这样的函数(方法)称为纯虚函数。 如果一个类包含了纯虚函数,称此类为抽象类 。 示例虚函数的实例#include<iostream.h> class Cshape public: void SetColor( int color) m_nColor=color; void virtual Display( void) cout<<"Cshape&quo
8、t;<<endl; private: int m_nColor; ; class Crectangle: public Cshape public: void virtual Display( void) cout<<"Crectangle"<<endl; ; class Ctriangle: public Cshape void virtual Display( void) cout<<"Ctriangle"<<endl; ; class Cellipse :public Cshape pub
9、lic: void virtual Display(void) cout<<"Cellipse"<<endl; ; void main() Cshape obShape; Cellipse obEllipse; Ctriangle obTriangle; Crectangle obRectangle; Cshape * pShape4= &obShape, &obEllipse,&obTriangle, & obRectangle ; for( int I= 0; I< 4; I+) pShapeI->Di
10、splay( ); 本程序运行结果: Cshape Cellipse Ctriangle Crectangle 条件所以,从以上程序分析,实现动态联编需要三个条件: 1、 必须把动态联编的行为定义为类的虚函数。 2、 类之间存在子类型关系,一般表现为一个类从另一个类公有派生而来。 3、 必须先使用基类指针指向子类型的对象,然后直接或者间接使用基类指针调用虚函数。 其他信息定义虚函数的限制: (1)非类的成员函数不能定义为虚函数,类的成员函数中静态成员函数和构造函数也不能定义为虚函数,但可以将析构函数定义为虚函数。实际上,优秀的程序员常常把基类的析构函数定义为虚函数。因为,将基类的析构函数定义为
11、虚函数后,当利用delete删除一个指向派生类定义的对象指针时,系统会调用相应的类的析构函数。而不将析构函数定义为虚函数时,只调用基类的析构函数。 (2)只需要在声明函数的类体中使用关键字“virtual”将函数声明为虚函数,而定义函数时不需要使用关键字“virtual”。 (3)当将基类中的某一成员函数声明为虚函数后,派生类中的同名函数自动成为虚函数。 (4)如果声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。在以该类为基类的派生类中,也不能出现这种同名函数。 虚函数联系到多态,多态
12、联系到继承。所以本文中都是在继承层次上做文章。没了继承,什么都没得谈。 c+的虚函数下面是对C+的虚函数这玩意儿的理解。 一, 什么是虚函数(如果不知道虚函数为何物,但又急切的想知道,那你就应该从这里开始) 简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异而采用不同的策略。下面来看一段简单的代码 class A public: void print() cout<<”This is A”<<endl;
13、 ; 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,多态还有个关键之处就是一切用指向基类的指针或引用来操作对象。那现在就把
14、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; /现在成了虚函数了 ; cl
15、ass 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的代码,这样输出的结果就是Th
16、is is A和This is B了。 现在来消化一下,我作个简单的总结,指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数。 二, 虚函数是如何做到的(如果你没有看过Inside The C+ Object Model这本书,但又急切想知道,那你就应该从这里开始) 虚函数是如何做到因对象的不同而调用其相应的函数的呢?现在我们就来剖析虚函数。我们先定义两个类 class A /虚函数示例代码 public: virtual void fun()cout<<1<<endl; virtual void fun2()cout<
17、<2<<endl; ; class B:public A public: void fun()cout<<3<<endl; void fun2()cout<<4<<endl; ; 由于这两个类中有虚函数存在,所以编译器就会为他们两个分别插入一段你不知道的数据,并为他们分别创建一个表。那段数据叫做vptr指针,指向那个表。那个表叫做vtbl,每个类都有自己的vtbl,vtbl的作用就是保存自己类中虚函数的地址,我们可以把vtbl形象地看成一个数组,这个数组的每个元素存放的就是虚函数的地址,请看图
18、0; 通过左图,可以看到这两个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就不同,而不同的v
19、tbl里装着对应类的虚函数地址,所以这样虚函数就可以完成它的任务。 而对于class A和class B来说,他们的vptr指针存放在何处呢?其实这个指针就放在他们各自的实例对象里。由于class A和class B都没有数据成员,所以他们的实例对象里就只有一个vptr指针。通过上面的分析,现在我们来实作一段代码,来描述这个带有虚函数的类的简单模型。 #include<iostream> using namespace std; /将上面“虚函数示例代码”添加在这里 int main() void (*fun)(A*); A *p=new B; long lVptrAddr; me
20、mcpy(&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; new B是向内存(内存分5个区:全局名字
21、空间,自由存储区,寄存器,代码空间,栈)自由存储区申请一个内存单元的地址然后隐式地保存在一个指针中.然后把这个地址附值给A类型的指针P. . long lVptrAddr; 这个long类型的变量待会儿用来保存vptr的值 memcpy(&lVptrAddr,p,4); 前面说了,他们的实例对象里只有vptr指针,所以我们就放心大胆地把p所指的4bytes内存里的东西复制到lVptrAddr中,所以复制出来的4bytes内容就是vptr的值,即vtbl的地址 现在有了vtbl的地址了,那么我们现在就取出vtbl第一个slot里的内容 memcpy(&fun,reinterpre
22、t_cast<long*>(lVptrAddr),4); 取出vtbl第一个slot里的内容,并存放在函数指针fun里。需要注意的是lVptrAddr里面是vtbl的地址,但lVptrAddr不是指针,所以我们要把它先转变成指针类型 fun(p); 这里就调用了刚才取出的函数地址里的函数,也就是调用了B:fun()这个函数,也许你发现了为什么会有参数p,其实类成员函数调用时,会有个this指针,这个p就是那个this指针,只是在一般的调用中编译器自动帮你处理了而已,而在这里则需要自己处理。 delete p; 释放由p指向的自由空间; system("pause"
23、;); 屏幕暂停; 如果调用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>
24、using namespace std; class A /虚函数示例代码2 public: 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"<&
25、lt;endl; ; /end/虚函数示例代码2 int 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; /将上面“虚函数
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年重庆危险品从业资格证题库有多少题目
- 2025年出版物发行零售项目发展计划
- 公文写作与处理考试中的备考策略总结
- 城市社区共享服务协议
- 教育培训行业教师资质证明书(6篇)
- 2025年公文写作的框架及试题答案
- 物流行业员工收入及奖金证明(6篇)
- 行政管理的决策支持系统及试题及答案
- 2025企业云端存储服务合同
- 2025关于设备采购合同补充的合同协议范本
- 英美文学选读教材翻译
- 大学语文说课课件
- 古建筑施工合同
- 大连理工大学画法几何自学片段课件
- 慢性心功能不全护理查房
- 双新转常规申请表
- 公司企业接收证明
- 毕业设计论文小型风力发电机毕业设计
- 区域急救及医院急诊信息系统建设方案
- FIDIC银皮书(中英文对照)
- DB11T 1010-2019 信息化项目软件开发费用测算规范
评论
0/150
提交评论