笨鸟先飞学编程系列C++高级特性之模板.docx
- 文档编号:26519836
- 上传时间:2023-06-20
- 格式:DOCX
- 页数:16
- 大小:31.35KB
笨鸟先飞学编程系列C++高级特性之模板.docx
《笨鸟先飞学编程系列C++高级特性之模板.docx》由会员分享,可在线阅读,更多相关《笨鸟先飞学编程系列C++高级特性之模板.docx(16页珍藏版)》请在冰豆网上搜索。
笨鸟先飞学编程系列C++高级特性之模板
笨鸟先飞学编程系列-C++高级特性之模板
当我们越来越多的使用C++的特性,将越来越多的问题和事物抽象成对象时,我们不难发现:
很多对象都具有共性。
比如数值可以增加、减少;字符串也可以增加减少。
它们的动作是相似的,只是对象的类型不同而已。
C++提供了“模板”这一特性,可以将“类型”参数化,使得编写的代码更具有通用性。
因此大家都称模板编程为“通用编程”或“泛型编程”。
一般而言,模板分为函数模板和类模板,下面就让我们分别来了解一下它们。
一、函数模板
1、函数模板的定义和使用
定义一个模板函数的格式并不复杂,如下:
template<模板参数列表>
返回类型函数名(函数参数列表)
{
//code...
}
下面,让我们来举一个例子来说明模板函数的作用和用法(具体代码见Exp01)。
//定义一个函数模板,用来实现任意类型数据的相互交换。
template
voidswap(T&a,T&b)
{
Ttmp=a;
a=b;
b=tmp;
}
上面的代码,说明了模板函数的用法。
下面再给出调用的代码,我们看看如何使用这个函数模板:
intmain(intargc,char*argv[])
{
intnNum1=50;
intnNum2=30;
doubledfNum1=2.1;
doubledfNum2=3.0;
char*pszFirst="Hello";
char*pszSec="world!
";
swap
printf("nNum1=%d,nNum2=%d\r\n",nNum1,nNum2);
swap
printf("dfNum1=%f,pszSec=%f\r\n",dfNum1,dfNum2);
swap
printf("pszFirst=%s,pszSec=%s\r\n",pszFirst,pszSec);
return0;
}
具体的执行结果如下:
我相信,如果你是第一次见到模板的代码,那你一定也会像我一样好奇,这个功能是怎么实现的,它是怎么做到让一段代码来兼容各种类型的呢?
当我要反汇编该EXE得时候,无意间查看了下编程生成的map文件,让我看到了如下的内容:
Address
PublicsbyValue
Rva+Base
Lib:
Object
0001:
00000140
?
swap@@YAXAAH0@Z
00401140fi
Exp01.obj
0001:
00000190
?
swap@@YAXAAN0@Z
00401190fi
Exp01.obj
0001:
000001f0
?
swap@@YAXAAPAD0@Z
004011f0fi
Exp01.obj
由此可见,我们编写的voidswap(T&a,T&b),只是一个“函数模板”,要使用它需要先将它实例化为一个“模板函数”(如:
swap
编译器在编译此程序的时候,为每个调用此模板的代码生成了一个函数。
而且在后期的使用过程中,更加让我认识到:
a、函数模板并不是函数,它仅在编译时根据调用此模板函数时传递的实参类型来生成具体的函数体!
若函数模板没有被调用责不生成任何代码也不对模板代码作任何语法检查。
b、在模板类型参数中,实参与形参要求严格匹配,它不会进行任何的类型转换。
c、对于函数模板,我们在调用时不一定必须先进行将它实例化为一个函数也是可以的,编译器会自动的识别模板的类型。
(换句话说:
Exp01中的代码可以直接使用swap,而不需要<>)
2、函数模板的重载
当编写的一个模板无法满足所有需要的情况时,就需要对模板进行重载(或叫特例化),例如:
我们编写了一个较大值的模板Max:
template
T*const&Max(Tconst&a,Tconst&b)
{
returna
b:
a;
}
A、当我们需要传入两个指针类型的实参时,该模板就失效了,需要重载该模板:
template
T*const&Max(T*const&a,T*const&b)
{
return*a<*b?
*b:
*a;
}
B、倘若我们再需要比较两个字符串大小时,上面两个模板都失效了。
因为char*并没有提供operator<运行,我们只能通过调用strcmp库函数自己实现一个Max模板的特例(见Exp02):
constchar*Max(constchar*&a,constchar*&b)
{
returnstrcmp(a,b)<0?
b:
a;
}
说明:
C++模板机制规定,如果一个调用,即匹配普通函数,又能匹配模板函数的话,则优先匹配普通函数。
因此,当我们模板特例化的时候,会先匹配特例化的函数。
二、类模板
1、基本概念
类模板一般应用于容器类中,使得容器能够处理各种类型的对象,如(详见Exp03):
structNode
{
Node(intnData=0)
{
m_nData=nData;
m_pPrev=m_pNext=NULL;
}
intm_nData;//数据元素
Node*m_pPrev;//指向上一个元素的指针
Node*m_pNext;//指向下一个元素的指针
};
classCDList
{
private:
intm_nCount;
Node*m_pHead;
Node*m_pTail;
intm_nMessage;
public:
CDList();
virtual~CDList();
public:
intGetLen()const
{
m_nCount;
}
Node*GetHead()const
{
returnm_pHead;
}
Node*GetTail()const
{
returnm_pTail;
}
public:
boolChange(intnIndex1,intnIndex2);
voidRelease();
//增加
Node*AddTail(intnData);
Node*AddHead(intnData);
Node*operator[](intnIndex);
//删除
boolDeleteNode(intnIndex);
voidPrintAll();
//查找
Node*FindNode(intnIndex);
};
对于这样的链表,其节点的元素只能存放整型数据。
如果要想让此双向链表能够存放任何一种类型的元素,那此时我们需要的问题与函数模板就一样了,将此类修改成类模板,现在先不管类模板的写法,让我们按照函数模板的方法将类修改一下:
template
structNode
{
Node(TData)
{
m_Data=Data;
m_pPrev=m_pNext=NULL;
}
Tm_Data;//通用类型的数据元素
Node
Node
};
这样,我们每个节点都可以使用通用的类型了,当然,对节点的处理也一样得处理。
按照这个样子将类型参数化(为节省篇幅,具体的实现部分请参考Exp04):
template
classCDList
{
private:
intm_nMessage;//消息号
intm_nCount;//链表中元素的数量
Node
Node
public:
CDList();
virtual~CDList();
public:
intGetLen()const
{
m_nCount;
}
Node
{
returnm_pHead;
}
Node
{
returnm_pTail;
}
public:
boolChange(intnIndex1,intnIndex2);
voidRelease();
//增加
Node
Node
Node
//删除
boolDeleteNode(intnIndex);
voidPrintAll();
//查找
Node
};
这样就修改好了,很简单吧,貌似类模板没有什么太多的新语法规范。
完整的模板代码,大家可以参考Exp04,下面我们总结一下类模板的一些语法小细节。
2、类模板的定义
通过上面的一番修改,我相信你一定对类模板有了一定的了解,下面我们大致的总结一下类模板的定义格式:
Template
Class类名
{
//code,可以使用模板参数T来指定通用的数据类型。
}
正如上面的Exp04中一样,我们的模板写好了,但是它不能直接使用,就像函数模板一样,我们需要先将模板实例化成一个模板类才可以使用。
在函数模板中,编译器会针对我们传递的实参类型等信息自动的给我们实例化函数模板为模板函数,但是类模板就没有这么智能了,必须手工实例化:
intmain(intargc,char*argv[])
{
CDList
//(20)(80)1002005060
MyList.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();
return0;
}
程序执行结果:
总结:
a、类模板同样也不是类,它仅在编译时根据实例化本模板时传递的实参来生成具体的类代码!
若类模板没有被实例化也没有被调用,那编译器不会为本模板生成任何代码也不对模板代码作任何语法检查。
3、类模板的特化
类模板的特化又被叫做类模板的定做,首先让我们来了解下什么叫作定做。
通过上面几个小节的学习,我相信,大家都知道模板不能直接被使用:
必须先给模板传递一个实参,将它实例化为一个模板类,然后才可以用它来定义具体的对象。
这样就隐含了一个问题:
我们通过给模板传递一个实参来实例化的模板类中的代码都是在模板中定义好的,如果我们不能用与定义好的模板代码来生成模板类,这时就需要使用模板的特化,也就是“定做”。
比如,我们刚才写好的双向链表模板中,对于某一个类(比如CStudent)来说,不允许添加重复的节点,但是对于像普通的int,double等数据类型以及其它一些类时,又不需要有这类的限制。
这时我们就需要将此双向链表模板针对这个不允许有重复节点的类(如:
CStudent)进行特化,大致代码如下:
template<>
classCDList
{
private:
intm_nMessage;//消息号
intm_nCount;//链表中元素的数量
Node
Node
public:
boolChange(intnIndex1,intnIndex2);
voidRelease();
//增加
Node
Node
Node
//删除
boolDeleteNode(intnIndex);
voidPrintAll();
//查找
Node
};
Node
:
AddTail(CStudentData)
{
Node
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++;
returnpNewNode;
}
…
由此可知,为CStudent类定做的CDList模板类,就是以CDList
当一个模板拥有多个模板参数时,如果我们只对其部分参数定做则称为“局部定做”,这样定做出来的“物件”仍然是一个模板,因为我们只特化了一部分模板参数….
说明:
刚才,我们为CStudent类定做的CDList模板类,其实我们没有必要将整个CDList
Template<>
CDList
:
Add(CStudent&n)
{
……
}
当然,这样的代码,需要写在Cpp文件中,并在.h文件中声明。
三、模板程序的组织
当然,如果你足够细心,你一定会好奇,为什么我给的例子中,模板的代码都写在头文件中(.h文件),这里我们就讨论模板代码的组织方式。
C++支持两种模板代码的组织方式,分别是包含方式(我们使用的就是包含方式)和分离方式。
这两种组织方式没有太根本的区别,就是一个将代码全写在头文件中,分离方式是像写类一样声明和定义分别写在头文件(.h文件)和实现文件(cpp文件)中。
下面我们分别讨论下这两种代码组织方式。
1、包含方式
本专题中,所有的实例代码中的模板代码都是以包含方式组织的。
因为好多的编译器(如VC6的cl)并不支持分离方式组织代码,将代码写在头文件也只是方便编译器的预处理工作能方便的将.h文件中的代码根据模板实参的类型生成相应的模板类。
当然,将代码都写在头文件中还有一点点小要求:
A、如果模板的成员函数写在类外,则需要写成如下样式(见Exp04):
template
Node
:
AddTail(TData)
{
Node
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++;
returnpNewNode;
}
B、对于特化的代码则需要在.h文件中声明并在.cpp文件中定义,如果都写在.h文件中编译会报重定义错误。
2、分离方式
上面已经提到过,所谓的分离方式组织代码,就是将模板的声明和定义分别写在头文件(.h文件)和实现文件(cpp文件)中,需要注意的是,并不是所有的编译器都支持这种写法,目前我只知道GCC支持这种写法。
当然,分离方式组织代码也有个小要求,就是在模板的声明和定义的template关键字前都加上export关键字。
比如:
//.h头文件中
exporttemplate
classCDList
{
public:
CDList();
virtual~CDList();
public:
boolChange(intnIndex1,intnIndex2);
voidRelease();
//增加
Node
Node
Node
//删除
boolDeleteNode(intnIndex);
voidPrintAll();
//查找
Node
};
在实现文件(cpp文件)中。
exporttemplate
Node
:
AddTail(TData)
{
Node
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++;
returnpNewNode;
}
….
四、学习小结
经过上面各个小节的学习,我相信大家一定像我一样,对模板有了一点认识。
大家也一定都知道,模板只是在编译期间编写,所有的代码都只有效与编译期。
因此,模板的重载、特化等多态性也都是在编译期间体现出来的,如果我们对编译生成的可执行文件进行反汇编时,我们不会找到任何与模板有关的代码,因为模板只是编译期间的产物。
关于模板的作用,我相信大家也一定都体会到了,它可以大大的减轻我们的编码负担,提高编程效率。
关于模板的用法和技巧还有很多,单单模板特性足可以出一本书的篇幅来描述其特性及用法。
因此本专题也只是带领大家了解模板的基础用法,关于模板的更多更深入知识,请参考“模板元编程”相关内容。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 笨鸟先飞 编程 系列 C+ 高级 特性 模板
![提示](https://static.bdocx.com/images/bang_tan.gif)