容器模板之类的Word格式文档下载.docx
《容器模板之类的Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《容器模板之类的Word格式文档下载.docx(14页珍藏版)》请在冰豆网上搜索。
这个max函数就是一个模板函数,它可以传入一个“类型”的参数,以便实现任意类型求最大值的效果。
假设我们这样使用它:
intx=5,y=10;
(x,y);
这时候发生了什么呢?
我们传入的“类型参数”是int,因此编译器在编译这段代码时会使用int来构造一个新函数:
intmax(inta,intb)
后面的事就和编译普通的函数一样了,C++编译器继续使用强类型系统编译这个函数,由强类型系统来检查这个函数是否正确。
这个过程叫做模板的“特化”,它发生在编译期,当编译器发现模板函数、模板类被使用(注意,不是定义)的时候进行的。
这个系统实际上比较像宏,但是比宏更为智能。
很明显,编译器必须知道模板如何特化这个函数,因此模板函数的实现,必须在“使用点”之前,因此模板库只能通过头文件库的形式来提供。
//更为准确的解释C++Primer310.5
C++支持两种模板编译模式包含模式InclusionModel和分离模式SeparationMode。
包含模式:
像内联函数那样将模板的说明和定义全部放在头文件中.
分离模式:
将模板说明放在头文件中,但是将模板实现放在c文件中,但是需要加"
export"
关键字,告诉编译器在生成被其他文件使用的函数模板实例时可能需要这个模板定义。
可见使用包含模式比较简洁,所以推荐使用包含模式,将模板的说明和定义全部放在头文件中发布。
除非出于软件商业化目的,不希望暴露模板定义代码给用户,需要采用分离模式。
4.模板的类型推导-根据用户传送过来的行参推导
对于函数,编译器是知道传入参数的类型的,比如上面的max,max<
?
>
(x,y),由于第一个参数x是int类型的,那么?
这里需要填写什么呢?
我们可以很明显的推断出应该是"
int"
,否则,后面的强类型系统将无法编译这个函数。
编译器同样知道x的类型,因此它也能推导出“类型参数”,这时候我们调用时就可省略模板参数了。
这个推导是按顺序来的,因此如果上面的y是其他类型,?
仍然会被推导为int,如果y无法隐性转换为int,强类型编译时就会报错。
5.类型推导的隐式类型转换
在决定模板参数类型前,编译器执行下列隐式类型转换:
左值变换
修饰字转换
派生类到基类的转换
见《C++Primer》([注2],P500)对此主题的完备讨论。
简而言之,编译器削弱了某些类型属性,例如我们例子中的引用类型的左值属性。
举例来说,编译器用值类型实例化函数模板,而不是用相应的引用类型。
同样地,它用指针类型实例化函数模板,而不是相应的数组类型。
它去除const修饰,绝不会用const类型实例化函数模板,总是用相应的非const类型,不过对于指针来说,指针和const指针是不同的类型。
底线是:
自动模板参数推导包含类型转换,并且在编译器自动决定模板参数时某些类型属性将丢失。
这些类型属性可以在使用显式函数模板参数申明时得以保留。
6.模板的实例化,特化
函数模板
实例化:
函数模板指定了实际类型或值构造出独立的函数的过程,称为实例化,即模板的使用过程。
模板的实例化可以通过隐式推导实现,也可以通过显示指定实现。
特化:
我们并不总是能够写出对所有可能被实例化的类型都是最合适的函数模板在某些情况下,我们可能想利用类型的某些特性来编写一些比模板实例化的函数更高效的函数,这时,就为某种类型,显式定义模板的实现,成为显式特化,所以模板特还还是模板行为的定义过程。
#include<
cstring>
//constchar*显式特化定义了max的字符串实现
typedefconstchar*PCC;
template<
>
PCCmax<
PCC>
(PCCs1,PCCs2)
{
return(strcmp(s1,s2)>
0?
s1:
s2);
}
类模板
特化和部分特化
7.仿函数
仿函数这个词经常会出现在模板库里(比如STL),那么什么是仿函数呢?
顾名思义:
仿函数就是能像函数一样工作的东西,请原谅我用东西这样一个代词,下面我会慢慢解释。
voiddosome(inti)
这个dosome是一个函数,我们可以这样来使用它:
dosome(5);
那么,有什么东西可以像这样工作么?
答案1:
重载了()操作符的对象,比如:
structDoSome
voidoperator()(inti);
DoSomedosome;
这里类(对C++来说,struct和类是相同的)重载了()操作符,因此它的实例dosome可以这样用dosome(5);
和上面的函数调用一模一样,不是么?
所以dosome就是一个仿函数了。
实际上还有答案2:
函数指针指向的对象。
typedefvoid(*DoSomePtr)(int);
typedefvoid(DoSome)(int);
DoSomePtr*ptr=&
func;
DoSome&
dosome=*ptr;
dosome(5);
//这里又和函数调用一模一样了。
当然,答案3成员函数指针指向的成员函数就是意料之中的答案了。
8.仿函数的用处
不管是对象还是函数指针等等,它们都是可以被作为参数传递,或者被作为变量保存的。
因此我们就可以把一个仿函数传递给一个函数,由这个函数根据需要来调用这个仿函数(有点类似回调)。
STL模板库中,大量使用了这种技巧,来实现库的“灵活”。
比如:
for_each,它的源代码大致如下:
typenameIterator,typenameFunctor>
voidfor_each(Iteratorbegin,Iteratorend,Fucntorfunc)
for(;
begin!
=end;
begin++)
func(*begin);
这个for循环遍历了容器中的每一个元素,对每个元素调用了仿函数func,这样就实现了对“每个元素做同样的事”这样一种编程的思想。
特别的,如果仿函数是一个对象,这个对象是可以有成员变量的,这就让仿函数有了“状态”,从而实现了更高的灵活性。
模板用法:
定义、使用示例
iostream>
usingnamespacestd;
typenameA,typenameB>
classtest
public:
test();
~test();
intgX();
};
test<
A,B>
:
test()
~test()
inttest<
gX()
cout<
<
sizeof(A)<
endl;
sizeof(B)<
return0;
intmain(void)
int,char>
x;
x.gX();
return0;
/////////////////////////////////List.h///////////////////////////
iostream.h>
classT>
classList
List();
~List();
voidAdd(T&
);
voidRemove(T&
T*Find(T&
voidPrintList();
protected:
structNode{
Node*pNext;
T*pT;
Node*pFirst;
List<
T>
List()
pFirst=0;
voidList<
Add(T&
t)
Node*temp=newNode;
temp->
pT=&
t;
pNext=pFirst;
pFirst=temp;
Remove(T&
Node*q=0;
if(*(pFirst->
pT)==t)
q=pFirst;
pFirst=pFirst->
pNext;
else
{
for(Node*p=pFirst;
p->
p=pNext)
if(*(p->
pNext->
q=p->
pNext=q->
break;
if(q)
deleteq->
pT;
deleteq;
T*List<
Find(T&
p;
p=p->
pNext)
returnp->
PrintList()
cout<
*(p->
pT)<
"
"
;
~List()
Node*p;
while(p=pFirst)
deletep->
deletep;
///////////////////////List.c//////////////////////////////
#Includelist.h
voidmain()
float>
floatList;
for(inti=1;
i<
7;
i++)
floatList.Add(*newfloat(i+0.6));
floatList.PrintList();
floatb=3.6;
float*pa=floatList.Find(b);
if(pa)
floatList.Remove(*pa);
VisualC++的模板和泛型如何配合使用
本篇教程来源于完全教程网原文链接:
我将演示模板和泛型在何种情况下能配合使用,在何种情况下不能配合使用,并指明在VisualC++2005下当前模板实现方式的缺陷,以此来结束这一系列有关在Microsoft.NETFramework中进行泛型编程的专栏。
我选择通过讨论将标准模板库(STL)引入.NET的过程中进行的工作,来介绍这份材料。
首先,我将回顾STL的基本元素,这样所有人都将站在同一起跑线上。
STL的CAI
有三个主要元素可用于标准模板库的设计:
容器、算法和迭代器(CAI)。
顺序容器
STL矢量vector和List类表示顺序容器。
顺序容器保存第一个元素、第二个元素等等,直到最后一个元素。
用程序来表示的函数参数列表通常作为包含字符串类型的元素的矢量来实现。
例如:
以下是引用片段:
vectorparamlist;
关联容器
Map和Set类表示关联容器。
关联容器支持快速查找。
例如,Map表示键/值对:
键用于查找,而值表示存储和检索的数据。
要表示电话号码簿,您需要声明一个带有字符串键和整数值的Map:
mapphonedir;
多重映射可使单一键支持多个电话条目。
泛型算法
STL还提供一个算法集,其中包含用于查找、排序、替换和合并的算法(可以对容器进行运算)。
这些算法称为泛型算法,因为它们独立于正在其上进行运算的元素类型(例如整型、双精度类型或字符串类型)和包含元素的容器类型(例如无论容器是矢量、列表还是内置数组)。
泛型算法通过直接在容器上进行运算来实现容器独立性。
程序不向算法传递容器,而是向它们传递标记了要通过其进行迭代的元素范围的迭代器对(第一个,最后一个],其中最后一个元素充当终结标志或一个标记,以表明元素集之后的元素并将停止算法:
sort(paramlist.begin(),paramlist.end());
此处,begin()和end()是所有STL容器提供的方法,会将迭代器返回到元素集第一个元素和位于末尾元素之后的元素。
例如,看一下以下声明序列:
voidf()
{
intia[4]={21,8,5,13};
vector
ivec(ia,ia+4);
//将ivec初始化为ia...
list
ilist(ia,ia+4);
//将ilist初始化为ia...
//...
}
注意:
ia+4实际指向最后一个元素(13)之后的地址。
使用STL的人们最初可能被这种区别所蒙蔽,例如,传递截止到ia+3处,这将使元素13不能包括在一系列值中。
迭代器
迭代器提供了一种统一且独立于类型的方式来访问、遍历和比较容器的元素。
迭代器是一种类抽象,可以提供统一的指针操作(例如++、--、*、==、!
=),无论容器是内置数组、列表还是其他任何一致的STL容器:
//每次调用相同的泛型算法...
sort(ia,ia+4);
sort(ivec.begin(),ivec.end());
sort(ilist.begin(),ilist.end());
}
在每个排序调用中(共三个),结果序列理所当然是:
5、8、13、21(Fibonacci序列的第四个到第七个元素)。
现在,这只是一种对STL的理想化观点,并不能证明在正式约束和实际约束下实际可行。
正式约束是指:
不是所有的容器类型均支持所有算法运算。
例如,Map或Set不支持random_shuffle,因为对元素进行的任何重新排序均违反容器类型,这就像将索引编入堆栈中将违反堆栈的语义特征一样。
更实际地说,通过泛型算法,使用排序或查找来获得List容器中的元素,比在矢量上进行同一运算更加费力。
因此,List容器类型提供了自己的比泛型算法更高效的类方法。
同样,使用List容器类型的查找方法来查找Map元素,比使用查找算法(通过向算法传递Map的开始和结束迭代器及其键)更快捷。
大家可能会希望算法在所有容器上的执行效果都相同,但实际上,算法更适合在Block容器和内置数组,而不是在List容器和关联容器类型上使用。
实际上,当我在Bell实验室与AlexStepanov一起工作时,他就把泛型算法称为Block算法。
为.NET重新设计STL
要将STL合并到.NET中,首先要将容器作为公共语言运行库(CLR)类型重新实现。
出于多种原因,我在本文中不会进行深入的讨论。
总之,最好使容器成为引用类而不是值类。
//暂时简化声明...
template
refclassvector{...};
refclassmap{...};
在本机STL中,所有容器都是非多态的。
矢量的声明将给定实际矢量对象。
示例如下:
//本机STL矢量
//ivec.empty()==true
//ivec.size()==0
vector<
int>
ivec;
//ivec2.empty()==false
//ivec2.size()==10
ivec2(10);
但是在C++/CLI下声明引用类型时,将定义一个跟踪句柄(矢量本身位于托管的堆中)。
默认情况下,句柄将设置为nullptr。
请查看图1。
下一个设计要求是:
使不支持模板的其他语言(例如C#和VisualBasic?
)能够使用容器。
最简单的策略是:
使模板容器实现一个或多个系统容器接口(分到两个命名空间中),如图2中所示。
通常,您将希望同时支持收集和泛型接口,以使当前使用收集接口的客户端能够使用您的类型。
以下是声明支持两种接口的方法:
refclassvector:
System:
Collections:
ICollection,
Generic:
ICollection
{...};
要实现系统收集命名空间的容器接口,还必须实现IEnumerator和IEnumerator的实例:
generic
refclassvector_enumerator:
IEnumerator,
IEnumerator
实现系统容器接口的弊端是:
虽然使得元素可用,但是无法操作STL/CLR类型的容器。
因此,额外的设计支持还应该提供泛型阴影容器类型,以使其他语言可以使用实际的容器类型。
有两个常规策略可以实现这种支持:
Plauger方式和Tsao方式(以两个主要设计师P.J.Plauger和AnsonTsao的姓名来命名)。
可以认为Plauger方式提供泛型阴影类型。
也就是说,您将创建阴影泛型类(可以将其称为generic_vector)。
它包含矢量模板的副本。
publicrefclassvector_generic
vector^m_templ;
//哎呀...
public:
vector_generic(vector^);
};
m_templ声明行上的“哎呀”注释表示在.NET下对模板使用的约束。
由于存在这种约束,您不能以泛型类型存储要求实例化的模板。
这是因为,两个参数化类型功能的实例化时间不同。
泛型由运行时来实例化;
而模板由编译器来实例化。
因此,模板可以包含泛型,而泛型不能包含模板。
Plauger方式下的解决方案为:
创建一个公共泛型接口,模板和泛型均通过该接口派生。
有关示例,请参阅图3。
Tsao方式下的解决方案是根据以下事实得出的:
接口始终为模板容器(在特定程序集中实例化)的引用。
因此,您只需提供一个接口并实现模板即可。
泛型阴影类型将被消除。
interfaceclassvector_interface:
ICollection{...};
vector_interface,ICollection{...};
在任何情况下,除了那些使用程序集的人们以外,所有人都可以执行泛型实例而不是STL/CLR容器。
这是因为,C++/CLR下的模板不能成为公共程序集成员。
下一部分中讨论了此问题。
模板为非公共程序集类型
要使.NET识别类型,它需要两个元素:
程序代码(表示要转换为公共中间语言(CIL)的类型)和元数据(描述类型的详细信息)。
不幸的是,模板此时还不能通过任何一个元素提供给.NET。
与析构函数一样,模板并不是为.NET而存在的。
.NET仅能识别模板的具体实例;
而不能识别出它们是一种模板。
例如,.NET可以识别vector和vector的CIL和元数据,但是不能识别共享的模板矢量,CIL和元数据都是该模板矢量的实例。
这种不可识别性的副作用是,您不能辨别模板。
也就是说,您不能询问vector实例:
“您是模板吗?
如果是,请把您的参数列表和类定义传递给我好吗?
”
另