数学建模社区-数学中国

标题: template 的用法 [打印本页]

作者: 韩冰    时间: 2004-11-21 00:32
标题: template 的用法
在程序设计当中经常会出现使用同种数据结构的不同实例的情况。例如:在一个程序中 9 p' b1 v1 e. L4 V3 R" e
可以使用多个队列、树、图等结构来组织数据。同种结构的不同实例,也许只在数据元素 8 z7 a; ~; V, [" J' f3 U
的类型或数量上略有差异,如果对每个实例都重新定义,则非常麻烦且容易出错。那么能
5 s6 m; l5 \, {  H8 E3 z5 Q否对同种类型数据结构仅定义一次呢?答案是肯定的,C++提供的类模板(Class Template
) g- Y: {) \  i# \  `)就可以实现该功能。
8 R, C6 S( @. }$ f一、类模板
: t& O2 \; k. y. \( s类模板是C++提供的一种特殊机制,通过它我们可以定义一种特殊的类(称为模板类),在类 : u* K/ U, h! S1 @/ E6 i+ ~
的定义中可以包含待定的类型参数,在声明类的实例时,系统会自动根据传递的类型生成
( b5 q/ M0 V) L4 y1 \用户想要生成的类实例。下面是用C++实现的一个简单的模板类Clist的定义。
) T- x9 P: o& Y4 g) |, yTemplate <class T, int I> class CList 5 g% F& j' J/ f( V4 |
{
+ w" _) Y* E8 x& g( @# A+ _public:
! S9 r" t. Q6 Y) q. ~! @: Eint SetItem(int Index, const T &Item);
' p' g' h6 P2 H/ Zint GetItem(int Index, T &Item);
1 o, i+ b& c: b3 r* v0 G1 Mprivate: ) L9 d8 V9 i9 J' Z- n
T Buffer[I]; $ V) f5 r  g( P7 {, x( I
} 4 p& E$ D) D* i6 x
在这里,T是类型参数,I是整型常量参数。T和I的实际值是在声明具体类实例时指定的。
! s5 x: g' {, F* ~% g+ K, ~模板类的<>号内可以包括任意个类型参数和常量参数(至少要有一个参数)。类型参数和 2 b. q; E2 A: }+ o8 y+ @
常量参数可以是任何合法的标准类型和用户自定义类型,包括简单类型及各种结构体。同
- Q, p$ s- l1 J) |+ W其他类一样,类成员函数SetItem的实现可以在类定义内完成,也可以在类CList定义处实 , [# [; ~3 @# I0 L0 }( B7 B4 P6 M
现:
3 q7 T, n( l5 K  H" otemplate<class T, int I> int CList<T, I>::SetItem(int Index, const T &Item) ( W0 f5 o- \! Q5 \4 ?
{
3 Y" e- y- ]: Sif ( (Index<0)||(Index>I-1) ) - {" j8 q/ J8 E" v5 m1 {) F
 return 0; // 出错 6 m; V  X  l# n+ Y3 n) ~) h' ]0 v" F
Buffer[Index]= Item ;
2 L4 [9 K4 ]' O2 K! F# `( L! ` return 1; // 成功 % K, |/ T- O7 t) h9 r2 G
}
7 j9 E! w; a$ W6 n& t; l$ |值得注意的是,在类定义外完成函数实现时,必须以关键字template和类模板定义中相同
$ M5 v& m% c8 u% [  |的参数表(<>号内的)开头(上例为template<class T, int I>),并且范围分解操作符前的 ' b5 I- P& d* i. q' v! h; `
类名后应跟上模板参数名清单(上例为CList<T, I>)。另外,与非模板类不同的是,必须将
0 C& n1 q5 n3 ]9 F% V( @/ S函数实现包括在调用它的每个源文件中,使编译器能从函数实现产生代码。通常的做法是
. V6 {5 Q7 v) v  d* O将模板类的函数实现也放在定义该类的头文件中,这样只需在调用的源文件中包含该头文
. y" {% M/ y4 S" u1 C/ X* F件即可。 1 L3 T7 _' L- I& b
那么,如何使用生成特定的类实例呢?我们可以像使用其他类一样来使用模板类,不过必须
2 w* O- U" X  I: O$ l, V指定模板参数的值。例如采用如下声明: ( t, a+ ~  F$ O/ U- W1 i
CList <int, 100> IntList;
+ X9 j+ A9 y6 ~1 F6 Z则使IntList成为CList类的实例,每次出现的T参数都换成int, 每次出现的I参数都换成 5 c; y) v( r1 P+ x+ _
100。这样,IntList类中的Buffer就是一个长度为100的整型数组,SetItem和GetItem函数 / Y0 \# i3 b+ D8 J( |
参数是int值的引用。例:
) A$ i4 [5 Z0 y$ |& D' VIntList.SetItem(0, 5); //给数组第一个元素赋为整数5 # K+ |% x4 M6 d. p
模板类还可以像其他类一样可以定义构造函数和析构函数。下面我们以一种简单的数据
$ T9 J, q7 |* v% s3 N$ h' ~结构——堆栈为例,来说明如何用类模板来构造通用数据结构。 7 }# N# v: G- U! d
二、 利用类模板实现通用堆栈结构
# r) E' y! ]4 V6 T8 `$ l6 s7 J2 C任何抽象数据结构在计算机中的实现,归根结底都只有两种方式:顺序存储(用数组实现) + I! a' `0 q- D6 w; S1 L7 |/ t- x- J* P" E
,链式存储(用指针实现)。堆栈也不例外,按其实现方式可分为顺序栈(用数组实现)和链
1 X/ r- ?* k. ~& n/ R) S栈(用指针实现)。 ' N  P5 {4 i4 Y' b- C
1. 通用顺序栈的实现 " n+ t8 _1 I' ~* U2 {" M9 S
因为顺序栈中的元素在空间上连续存储,栈顶的元素位置需要注明,所以构造顺序栈的模
0 L6 Z( `5 _0 k6 R/ @6 u8 m; R% I板类应该有这样的一些成员变量:一个待定类型和长度的数组Buffer,一个记录栈顶元素
2 s( ?& Q; N" I. n. S- W的数组下标的整型变量top。堆栈的基本操作主要有:入栈(Push)、出栈(Pop)、置空(Se
1 I7 l! N, V# M0 }tEmpty)、判断当前状态(IsEmpty)等,它们应用模板类的成员函数来实现。作为一个标准
) m) v3 b' A4 S" ?的类,它还应该有自己的构造函数和析构函数。具有这些功能的模板类,就可以作为一个 $ F% \. b- ^. g3 |# T+ Y
通用的顺序栈来使用了。该类的定义如下: ' P8 i3 S) ?, `, C, g, u
template <class T,int SIZE> class CArrayStackTemp & l4 Z- D3 |6 ^6 v
{
4 L9 t8 p" j- v+ v6 lpublic: * N/ ?0 F' b* ?; j; u
CArrayStackTemp () //缺省构造函数,构造一个空堆栈
- c) i# t' `0 X8 x3 y3 s" g( ]% d{
5 s8 n# Q' |* ^6 u5 stop= -1;
/ \' J5 M% X" h6 V};
; A) |( E, R; O, v~ CArrayStackTemp (){};//析构函数
. J  K0 ^, |, b void SetEmpty (); //置空堆栈
+ G( N) g0 m. U bool IsEmpty(); //判断堆栈是否为空
( L5 T5 X- N- ~0 R4 D9 k bool Push(T element); //入栈 - b0 y* S0 h( Z* x4 T8 N% M/ @
 bool Pop(T& element);//出栈
! F* m% n; E) hprivate: 1 D/ _# ~! j- B' M
T Buffer[SIZE];
/ y7 j6 [1 X1 o2 }- A! \6 M int top; 5 r+ U6 J4 U/ N$ c- c
};
6 Z& F3 v) E! e% ]; s2 ~与堆栈的基本操作相对应的成员函数的实现如下:
) Z; _& h; n! H7 Dtemplate <class T, int SIZE> void CArrayStackTemp<T, SIZE>:: SetEmpty ()
& h1 V" P% P6 y+ B( q! j{
' E, v2 m' M4 R; i$ l3 |, r  {' _1 ptop= -1; //将栈顶指针赋 -1,并不实际清除数组元素
5 I- O  d5 |0 s, ]! H0 F! G7 W) }} ! z5 o$ v/ Y. W' @% Y1 R' s' g! z7 \8 q' R
template <class T, int SIZE> bool CArrayStackTemp<T, SIZE>:: IsEmpty () ) j% p; ^6 |" n) C4 v" g. j+ n2 i
{
4 T" p3 B* V* W' J* `return(top == -1);   }4 o9 A- Z, x5 v8 d3 C
}
  o0 O# ~4 {5 Rtemplate <class T, int SIZE> bool CArrayStackTemp<T, SIZE>:: Push (T element
1 _: p+ \4 K% g7 ~) 3 t# l4 _1 Q6 A" L9 A$ B0 S
{
& B* v, p# o, j7 }( Dtop++; + M* c5 ]5 @2 \5 j, k& m( D
if (top>SIZE-1) , M# x/ z! U: }1 y; _
{
! m$ c" E( l/ ]8 m& Jtop--;
- d; m! t1 Q3 N8 u/ o( ^return false; //堆栈已满,不能执行入栈操作 ' E* l) U/ m5 z" o4 L, x
}
  {* [! J9 G$ `' P( p' n) b, wBuffer[top]=element; ' G5 r. \2 ?9 B2 H$ h3 Z$ b0 \3 N
return true; 9 T/ c3 v( r9 n. z* T- y; \2 E
}
! s* |. B' l3 W% `9 V! rtemplate <class T, int SIZE> void CArrayStackTemp<T, SIZE>:: Pop (T& element
, E' p3 q3 O/ }: W* f" [4 A) y7 s)
" b5 Q. r2 F  k3 J3 [$ B. }# Y( G{ ) P! N, r. _8 Q/ g
if (IsEmpty())
5 `) w) u/ x& Z8 L1 M return false; 0 N" Q. @5 f+ N7 Q, g
element =Buffer[top];
: t7 u. u) U" S: d& p8 y! ]- xtop--;
$ o6 y8 @& K2 f2 a. r4 }, mreturn true;
6 |' m5 b2 A+ T, y: G) D' p} , R- \0 M# e2 D  l" s! `3 R
根据实际需要,还可以扩充堆栈功能。例如:加入取栈顶元素、求堆栈长度等操作,其方法
. p$ u" C7 z/ p6 Q# T( F如上。 : \* K2 r+ T3 G. `+ Q; W3 J2 w$ ~
2. 通用链栈的实现
; x+ L% d$ V) E! A% z模板类中允许使用指针和定义自己的结构,这就为实现链式结构提供了保证。这里采用一 7 a+ K$ o+ P; ?8 x! U/ j' @
个单链表来实现堆栈,栈顶指针指向链表的第一个结点,入栈和出栈均在链表的头进行。
* M& _; N# O* {; \该模板类的定义如下:
* p+ _0 u; P: ^3 }template <class T> class CLinkStackTemp
  }- T0 W6 ?) d  n( u{ 4 x  }" a% Z6 F; K
public: 2 b8 y/ I& Q) _% q5 D; A- H1 n9 y
 //类的缺省构造函数,生成一个空堆栈 ' ?# T3 Y0 d. z2 M% p/ o
CLinkStackTemp ()
$ m/ U* Y6 P( L$ k; U{
4 p7 L- ~9 A& D4 i% Otop=NULL;
) u, |- O" S4 b1 q$ z5 s1 q};
, W' H  p0 ]7 Z! Q( _" P: I0 P~ClinkStackTemp(){}; //析构函数
. D3 {. \( ?2 `/ @, o! Y4 [ //定义结点结构
8 o% D9 k' y# b* i struct node
3 D/ C( ^& S1 Q! I& c{ 9 S  j9 @% ^5 K
T
! K# B  `7 V+ `: U  data; //入栈元素 6 x& u9 U1 @- \4 j2 `
 node* next; //指向下一结点的指针 + {+ T% a3 G1 x& E
}; . H1 {9 Z' i* |0 W, T
 void SetEmpty(); //置空堆栈 , G! t* H: T" H
 bool IsEmpty(); //判断堆栈是否为空 0 `: e  n4 A. b3 F0 M
 bool Push(T element); //压入堆栈 9 c$ ^" _  r6 G. A8 c
 bool Pop(T& element);//弹出堆栈 & W% i* D+ m. d4 s/ Y
private:
  ^0 D$ b1 p! ~  ?' P. k node* top;
9 U0 r; B- k- a3 G" W8 W+ M};
$ |; J- o4 l8 a+ Q+ A" q; O该类的成员函数实现如下:   z. Q' e5 V5 V0 c) r5 u7 z
template <class T> void CLinkStackTemp <T>::SetEmpty() " p6 d; N# d* H! w! C4 L# C
{
# o$ U% O- l% {( h0 Z( F//释放堆栈占用的内存
% i' t2 z+ l+ h  Z3 K' H3 }node* temp; / @# m+ v: p! C# ~7 o
while (top!=NULL) * X) Z; X3 G7 I# B/ T
{
% ]% y0 e0 Q3 a temp=top;
' h6 G" D* a4 s top=top->next; 5 m3 T5 d' a1 j4 Z
 delete temp;
" v/ K1 l4 ^& p' A; d}
% g" X5 I& G3 x% H: l2 d} % Q# F0 k& J$ t4 i" |9 X; }
template <class T> bool CLinkStackTemp <T>::IsEmpty()
1 A% b7 h! i  ]; v) _6 w9 }/ A{ 5 {) b+ S! i) K: m
return (top==NULL); / T& S: K  r4 V3 }; k  u, P
}
+ ]9 M5 V' X' }( b5 L3 otemplate <class T> bool CLinkStackTemp <T>:ush(T element) : \& I7 b& `# D  ?$ m/ l
{
  y3 T% P. A9 g- Jnode* temp=new node();
# N  w! A' Q! P8 A- D1 `if (temp ==NULL)
0 Z. B4 O$ y* R return false ;
# E, Y. k/ J; Z5 ctemp->data=element;
" T. k% M% k% ytemp->next=top; . v& ~+ {) E! K) s8 q2 ^0 Y& C
top=temp;
+ d7 `9 m; n; @: J. a1 D) Qreturn true;
! n) C8 a1 \" \" v- e1 K# T: A}
8 p/ A4 J% I. o' N, k% J3 f9 ?template <class T> bool CLinkStackTemp <T>:op(T& element) ; b. F4 n/ v: `% n: q6 s
{
, ]: @/ B3 F3 ?  V& d3 K9 Jif ( IsEmpty()) 8 S# [- k: H# d4 ~  @  g( U
 return false; ; Q! |+ P, U  X1 L
node* q = top;
. F" G' I) M, }# J  N9 V* s8 relement = top->data;
: j7 l; s& R0 w* D2 F, A/ [top=top->next; - l9 V$ ~  ]5 O, O
delete q;
" P8 t0 @' E8 t* @% _4 Q+ Mreturn true; * d2 j! l  b- m- y6 B4 M% T
} 9 b0 V; e" n3 {+ i/ Q
与顺序栈的实现略有不同,链栈不必指定栈的容量,其大小可以是近似"无限"的。为了程 9 e' n% Z, A" T$ R" Q+ a
序的使用方便,我们同样可以加入一些增强的功能。
" @+ S- O; I/ u7 ]  o  t三、 通用堆栈类的使用
2 \- E# s: M% a9 E通用堆栈类的使用较为简单,堆栈类的实例就是一个可以方便使用的堆栈。对堆栈的操作 8 F' `+ p0 c- B" @& a8 h
都是通过类的成员函数来实现的。使用的具体步骤如下: ( M$ V9 g  d* z8 h- y/ n
1. 在要使用堆栈类的程序代码的文件开头包括模板类及其成员函数的定义。 0 _+ Y' A0 h8 b
2. 类的实例化,可声明成变量,也可以声明它的指针,如: # k6 ~7 e3 v3 ?6 q
CArrayStackTemp <int, 100> intStack; //生成一个长度为100的int型堆栈 ( H6 g' \/ p& R! Z& U
//生成一个元素为Record型的堆栈,Record为自定义结构
) o8 W" f- j9 t" [# _CLinkStackTemp <Record>* RecordStack;
# T8 D# z4 I9 MRecordStack=new CLinkStackTemp<Record>;
: u, i. F& {  J应注意在定义顺序栈时,必须指定栈的大小,而链栈则不必。另外在指定指针类型和执行
, d  ?) n( n; g- ?) @# \' N- ?new操作时,必须对模板参数赋值,并且前后要一致。
, f# r. k% l7 X- o5 u3 [# O: U3. 对堆栈进行操作,如:
* Y4 o( Q# W1 Q  ^( k$ fintStack.Push(3); //将整数3入栈 % X9 Y1 B# z' ?( r& P5 X$ z
RecordStack.SetEmpty(); //将堆栈置空
0 _6 @7 @1 t2 H3 m( d5 q无论我们使用哪种堆栈类,对用户来讲都是透明的,操作起来并无差别。




欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5