笨鸟先飞学编程系列-C++高级特性之模板.doc_第1页
笨鸟先飞学编程系列-C++高级特性之模板.doc_第2页
笨鸟先飞学编程系列-C++高级特性之模板.doc_第3页
笨鸟先飞学编程系列-C++高级特性之模板.doc_第4页
笨鸟先飞学编程系列-C++高级特性之模板.doc_第5页
已阅读5页,还剩8页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

笨鸟先飞学编程系列-C+高级特性之模板当我们越来越多的使用C+的特性, 将越来越多的问题和事物抽象成对象时, 我们不难发现:很多对象都具有共性。 比如 数值可以增加、减少;字符串也可以增加减少。 它们的动作是相似的, 只是对象的类型不同而已。C+ 提供了“模板”这一特性, 可以将“类型” 参数化, 使得编写的代码更具有通用性。 因此大家都称模板编程为 “通用编程”或 “泛型编程”。一般而言, 模板分为 函数模板 和 类模板,下面就让我们分别来了解一下它们。一、 函数模板1、 函数模板的定义和使用定义一个模板函数的格式并不复杂, 如下:template 返回类型 函数名(函数参数列表) / code .下面, 让我们来举一个例子来说明 模板函数的作用和用法(具体代码见 Exp01)。/ 定义一个函数模板, 用来实现任意类型数据的相互交换。template / 声明一个 T 数据类型, 此类型随 调用方 类型的变化而变化void swap(T& a, T& b) T tmp = a; a = b; b = tmp; 上面的代码, 说明了模板函数的用法。下面再给出调用的代码, 我们看看如何使用这个函数模板:int main(int argc, char* argv) int nNum1 = 50; int nNum2 = 30; double dfNum1 = 2.1; double dfNum2 = 3.0; char *pszFirst = Hello ; char *pszSec = world!; swap (nNum1, nNum2);/ 将swap函数模板实例化为 int类型的模板函数再调用 printf(nNum1 = %d, nNum2 = %drn, nNum1, nNum2); swap (dfNum1, dfNum2); printf(dfNum1 = %f, pszSec = %frn, dfNum1, dfNum2); swap (pszFirst, pszSec); printf(pszFirst = %s, pszSec = %srn, pszFirst, pszSec);return 0;具体的执行结果如下:我相信,如果你是第一次见到模板的代码,那你一定也会像我一样好奇,这个功能是怎么实现的,它是怎么做到让一段代码来兼容各种类型的呢?当我要反汇编该EXE得时候,无意间查看了下编程生成的map文件,让我看到了如下的内容: Address Publics by ValueRva+BaseLib:Object0001:00000140?swapYAXAAH0Z00401140 f iExp01.obj0001:00000190?swapYAXAAN0Z00401190 f iExp01.obj0001:000001f0?swapYAXAAPAD0Z004011f0 f iExp01.obj由此可见, 我们编写的void swap(T& a, T& b), 只是一个“函数模板”, 要使用它需要先将它实例化为一个“模板函数”(如:swap )。编译器在编译此程序的时候,为每个调用此模板的代码生成了一个函数。而且在后期的使用过程中,更加让我认识到:a、 函数模板 并不是函数,它仅在编译时根据调用此模板函数时传递的实参类型来生成具体的函数体!若 函数模板没有被调用责不生成任何代码也不对模板代码作任何语法检查。b、 在模板类型参数中, 实参与形参要求严格匹配,它不会进行任何的类型转换。c、 对于函数模板,我们在调用时不一定必须先进行将它实例化为一个函数也是可以的,编译器会自动的识别模板的类型。(换句话说:Exp01中的代码可以直接使用swap, 而不需要)2、 函数模板的重载当编写的一个模板无法满足所有需要的情况时,就需要对模板进行重载(或叫 特例化),例如:我们编写了一个较大值的模板Max:template T* const& Max(T const& a, T const& b) return a b ? b : a;A、 当我们需要传入两个指针类型的实参时,该模板就失效了,需要重载该模板:template T* const& Max(T* const& a, T* const& b) return *a *b ? *b : *a;B、 倘若我们再需要比较两个字符串大小时,上面两个模板都失效了。因为char* 并没有提供 operator 运行,我们只能通过调用strcmp库函数自己实现一个Max模板的特例(见Exp02):const char* Max(const char*& a, const char*& b) return strcmp(a, b) 0 ? b : a;说明:C+模板机制规定,如果一个调用,即匹配普通函数,又能匹配模板函数的话,则优先匹配普通函数。因此,当我们模板特例化的时候,会先匹配特例化的函数。二、 类模板1、 基本概念类模板一般应用于容器类中,使得容器能够处理各种类型的对象,如(详见Exp03):struct Node Node( int nData = 0 ) m_nData = nData; m_pPrev = m_pNext = NULL; int m_nData;/ 数据元素 Node* m_pPrev;/ 指向上一个元素的指针 Node* m_pNext;/ 指向下一个元素的指针;class CDListprivate: int m_nCount; Node* m_pHead; Node* m_pTail; int m_nMessage;public:CDList();virtual CDList();public:int GetLen() constm_nCount;Node* GetHead() constreturn m_pHead;Node* GetTail() constreturn m_pTail;public:bool Change(int nIndex1,int nIndex2);void Release();/增加Node* AddTail( int nData );Node* AddHead( int nData );Node* operator(int nIndex);/删除bool DeleteNode( int nIndex );void PrintAll();/查找Node* FindNode( int nIndex );对于这样的链表,其节点的元素只能存放整型数据。如果要想让此双向链表能够存放任何一种类型的元素,那此时我们需要的问题与函数模板就一样了,将此类修改成类模板,现在先不管类模板的写法,让我们按照函数模板的方法将类修改一下:template struct Node Node( T Data ) m_Data = Data; m_pPrev = m_pNext = NULL; Tm_Data; / 通用类型的数据元素 Node* m_pPrev;/ 指向上一个元素的指针 Node* m_pNext;/ 指向下一个元素的指针;这样,我们每个节点都可以使用通用的类型了,当然,对节点的处理也一样得处理。按照这个样子将类型参数化(为节省篇幅,具体的实现部分请参考Exp04):template class CDListprivate:int m_nMessage;/ 消息号int m_nCount;/ 链表中 元素的数量Node*m_pHead;/ 链表头指针Node*m_pTail;/ 链表尾指针public:CDList();virtual CDList();public:int GetLen() constm_nCount;Node* GetHead() constreturn m_pHead;Node* GetTail() constreturn m_pTail;public:bool Change(int nIndex1,int nIndex2);void Release();/增加Node* AddTail( T Data );Node* AddHead( T Data );Node* operator(int nIndex);/删除bool DeleteNode( int nIndex );void PrintAll();/查找Node* FindNode( int nIndex );这样就修改好了,很简单吧,貌似类模板没有什么太多的新语法规范。完整的模板代码,大家可以参考Exp04,下面我们总结一下类模板的一些语法小细节。2、 类模板的定义通过上面的一番修改,我相信你一定对类模板有了一定的了解,下面我们大致的总结一下类模板的定义格式:Template Class 类名/ code,可以使用模板参数T来指定通用的数据类型。正如上面的Exp04中一样,我们的模板写好了,但是它不能直接使用,就像函数模板一样,我们需要先将模板实例化成一个模板类才可以使用。在函数模板中,编译器会针对我们传递的实参类型等信息自动的给我们实例化函数模板为模板函数,但是类模板就没有这么智能了,必须手工实例化:int main(int argc, char* argv)CDList MyList;/ 将CDList实例化为一个int类型,也就是说链表中数据元素为整型/(20) (80) 100 200 50 60MyList.AddTail(20);MyList.AddTail(80);MyList.AddTail(100);MyList.AddTail(200);MyList.AddTail(50);MyList.AddTail(60);MyList.PrintAll();MyList.Change(0,1);MyList.PrintAll();return 0;程序执行结果:总结:a、 类模板 同样也不是类,它仅在编译时根据实例化本模板时传递的实参来生成具体的类代码!若 类模板没有被实例化也没有被调用,那编译器不会为本模板生成任何代码也不对模板代码作任何语法检查。3、 类模板的特化类模板的特化又被叫做类模板的定做,首先让我们来了解下什么叫作定做。通过上面几个小节的学习,我相信,大家都知道模板不能直接被使用:必须先给模板传递一个实参,将它实例化为一个模板类,然后才可以用它来定义具体的对象。这样就隐含了一个问题:我们通过给模板传递一个实参来实例化的模板类中的代码都是在模板中定义好的,如果我们不能用与定义好的模板代码来生成模板类,这时就需要使用模板的特化,也就是“定做”。比如,我们刚才写好的双向链表模板中,对于某一个类(比如CStudent)来说,不允许添加重复的节点,但是对于像普通的int,double等数据类型以及其它一些类时,又不需要有这类的限制。这时我们就需要将此双向链表模板针对这个不允许有重复节点的类(如:CStudent)进行特化,大致代码如下:template class CDListprivate:int m_nMessage;/ 消息号int m_nCount;/ 链表中 元素的数量Node*m_pHead;/ 链表头指针Node*m_pTail;/ 链表尾指针public:bool Change(int nIndex1,int nIndex2);void Release();/增加Node* AddTail( CStudent Data );Node* AddHead( CStudent Data );Node* operator(int nIndex);/删除bool DeleteNode( int nIndex );void PrintAll();/查找Node* FindNode( int nIndex );Node* CDList:AddTail( CStudent Data )Node* pNewNode = new Node(Data);if ( m_pTail )m_pTail-m_pNext = pNewNode;pNewNode-m_pPrev = m_pTail;if ( m_pTail = NULL )m_pHead = pNewNode;m_pTail = pNewNode;m_nCount+;return pNewNode;由此可知,为CStudent类定做的CDList模板类,就是以CDList为类名重写一份CDList实现而抛弃编译器为我们生成的CDList类。当一个模板拥有多个模板参数时,如果我们只对其部分参数定做则称为“局部定做”,这样定做出来的“物件”仍然是一个模板,因为我们只特化了一部分模板参数.说明:刚才,我们为CStudent类定做的CDList模板类,其实我们没有必要将整个CDList类都写一遍,只需要重写Add函数即可,例如:TemplateCDList:Add(CStudent& n)当然,这样的代码,需要写在Cpp文件中,并在.h文件中声明。三、 模板程序的组织当然,如果你足够细心,你一定会好奇,为什么我给的例子中,模板的代码都写在头文件中(.h文件),这里我们就讨论模板代码的组织方式。C+支持两种模板代码的组织方式,分别是包含方式(我们使用的就是包含方式)和分离方式。这两种组织方式没有太根本的区别,就是一个将代码全写在头文件中,分离方式是像写类一样声明和定义分别写在头文件(.h文件)和实现文件(cpp文件)中。下面我们分别讨论下这两种代码组织方式。1、 包含方式本专题中,所有的实例代码中的模板代码都是以包含方式组织的。因为好多的编译器(如VC6的cl)并不支持分离方式组织代码,将代码写在头文件也只是方便编译器的预处理工作能方便的将.h文件中的代码根据模板实参的类型生成相应的模板类。当然,将代码都写在头文件中还有一点点小要求:A、 如果模板的成员函数写在类外,则需要写成如下样式(见Exp04):template / 每个类外成员函数前都要有这句Node* CDList:AddTail( T Data )Node* pNewNode = new Node(Data);if ( m_pTail )m_pTail-m_pNext = pNewNode;pNewNode-m_pPrev = m_pTail;if ( m_pTail = NULL )m_pHead = pNewNode;m_pTail = pNewNode;m_nCount+;return pNewNode;B、 对于特化的代码则需要在.h文件中声明并在.cpp文件中定义,如果都写在.h文件中编译会报重定义错误。2、 分离方式上面已经提到过,所谓的分离方式组织代码,就是将模板的声明和定义分别写在头文件(.h文件)和实现文件(cpp文件)中,需要注意的是,并不是所有的编译器都支持这种写法,目前我只知道GCC支持这种写法。当然,分离方式组织代码也有个小要求,就是在模板的声明和定义的template关键字前都加上export关键字。比如:/ .h 头文件中export template class CDListpublic:CDList();virtual CDList();public:bool Change(int nIndex1,int nIndex2);void Release();/增加Node*

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论