### 类模板的定义及应用

在介绍模板之前，先来设计一个简单的链表，链表的功能是向尾节点添加数据，遍历链表中的节点，在链表结束时释放所有节点。

单向链表

class CNode //定义一个节点类

{

public:

CNode *m_pNext; //定义一个节点指针，指向下一个节点

int m_nData; //定义节点的数据

CNode() //定义节点类的构造函数

{

m_pNext = NULL; //将 m_pNext 设置为空

}

};

class CList //定义链表类 CList 类

{

private:

CNode *m_pHeader; //定义头节点

int m_nNodeSum; //节点数量

public:

CList() //定义链表的构造函数

{

m_pHeader = NULL; //初始化 m_pHeader

m_nNodeSum = 0; //初始化 m_NodeSum

}

CNode* MoveTrail() //移动到尾节点

{

CNode* pTmp = m_pHeader; //定义一个临时节点，将其指向头节点

for (int i=1;i<m_nNodeSum;i++) //遍历节点

{

pTmp = pTmp->m_pNext; //获取下一个节点

}

return pTmp; //返回尾节点

}

void AddNode(CNode *pNode) //添加节点

{

if (m_nNodeSum == 0) //判断链表是否为空

{

m_pHeader = pNode; //将节点添加到头节点中

}

else //链表不为空

{

CNode* pTrail = MoveTrail(); //搜索尾节点

pTrail->m_pNext = pNode; //在尾节点处添加节点

}

m_nNodeSum++; //使链表节点数量加 1

}

void IterateList () //遍历链表

{

if (m_nNodeSum > 0) //判断链表是否为空

{

CNode* pTmp = m_pHeader; //定义一个临时节点，将其指向头节点

cout << pTmp->m_nData << endl; //输出节点数据

for (int i=1; i<m_nNodeSum; i++) //遍历其他节点

{

pTmp = pTmp->m_pNext; //获取下一个节点

cout <<pTmp->m_nData << endl; //输出节点数据

}

}

}

~CList() //定义链表析构函数

{

if (m_nNodeSum > 0) //链表不为空

{

CNode *pDelete = m_pHeader; //定义一个临时节点，指向头节点

CNode *pTmp = NULL; //定义一个临时节点

for(int i=0; i<m_nNodeSum; i++) //遍历节点

{

pTmp = pDelete->m_pNext; //获取下一个节点

delete pDelete; //释放当前节点

pDelete = pTmp; //将下一个节点设置为当前节点

}

m_nNodeSum = 0; //将 m_NodeSum 设置为 0

pDelete = NULL; //将 pDelete 置为空

pTmp = NULL; //将 pTmp 置为空

}

m_pHeader = NULL; //将 m_pHeader 置为空

}

};

下面我们定义一个链表对象，调用 AddNode 方法向链表中添加节点，调用 IterateList 方法遍历节点。

int main(int argc, char* argv[])

{

CList list; //定义链表对象

for(int i=0; i<5; i++) //利用循环向链表中添加 5 个节点

{

CNode *pNode = new CNode(); //构造节点对象

pNode->m_nData = i; //设置节点数据

list.AddNode(pNode); //添加节点到链表

}

list.IterateList(); //遍历节点

return 0;

}

运行程序，效果如图 1 所示。

图 1 简单链表

上述代码在定义链表类 Clist 时存在一个缺陷就是链表不够灵活，其节点只能是 CNode 类型。为了让 CList 能够适应各种类型的节点，一个最简单的方法就是使用类模板。类模板的定义与函数模板类似，以关键字 template 开始，其后是由尖括号<>构成的模板参数。下面重新修改链表类 CList，以类模板的形式进行改写。

模板链表

template <class Type> //定义类模板

class CList //定义链表类 CList 类

{

private:

Type *m_pHeader; //定义头节点

int m_nNodeSum; //节点数量

public:

CList() //定义链表的构造函数

{

m_pHeader = NULL; //初始化 m_pHeader

m_nNodeSum = 0; //初始化 m_NodeSum

}

Type* MoveTrail() //移动到尾节点

{

Type* pTmp = m_pHeader; //定义一个临时节点，将其指向头节点

for (int i=1;i<m_nNodeSum;i++) //遍历节点

{

pTmp = pTmp->m_pNext; //获取下一个节点

}

return pTmp; //返回尾节点

}

void AddNode(Type *pNode) //添加节点

{

if (m_nNodeSum == 0) //判断链表是否为空

{

m_pHeader = pNode; //将节点添加到头节点中

}

else //链表不为空

{

Type* pTrail = MoveTrail(); //搜索尾节点

pTrail->m_pNext = pNode; //在尾节点处添加节点

}

m_nNodeSum++; //使链表节点数量加 1

}

void IterateList() //遍历链表

{

if (m_nNodeSum > 0) //判断链表是否为空

{

Type* pTmp = m_pHeader; //定义一个临时节点，将其指向头节点

cout << pTmp->m_nData << endl; //输出节点数据

for (int i=1; i<m_nNodeSum; i++) //遍历其他节点

{

pTmp = pTmp->m_pNext; //获取下一个节点

cout <<pTmp->m_nData << endl; //输出节点数据

}

}

}

~CList() //定义链表析构函数

{

if (m_nNodeSum > 0) //链表不为空

{

Type *pDelete = m_pHeader; //定义一个临时节点，指向头节点

Type *pTmp = NULL; //定义一个临时节点

for(int i=0; i<m_nNodeSum; i++) //遍历节点

{

pTmp = pDelete->m_pNext; //获取下一个节点

delete pDelete; //释放当前节点

pDelete = pTmp; //将下一个节点设置为当前节点

}

m_nNodeSum = 0; //将 m_NodeSum 设置为 0

pDelete = NULL; //将 pDelete 置为空

pTmp = NULL; //将 pTmp 置为空

}

m_pHeader = NULL; //将 m_pHeader 置为空

}

};

上述代码对原来的 CList 类对象进行了修改，将出现节点类 CNode 的地方替换为模板类 Type。这样，链表类 CList 就能够适应不同类型的节点了。此时的 CList 就是一个模板类。模板类由 template 关键字开始，其后是模板参数列表，由尖括号（<>）表示。模板参数列表不能为空，它可以是一个类型参数，即由关键字 class 或 typename 和一个标识符构成，也可以是非类型参数，由一个常量表达式表示。一个类模板可以有多个类型参数，例如：

template <class Type1, class Type2, typename Type3>

class CList

{

//……

}；

注意：每个模板参数必须由class或typename标识，不能够利用一个class或typename关键字定义多个模板参数。

模板非类型参数由一个普通的参数声明构成，并且模板类型参数和非类型参数可以混合在一起使用。例如，定义一个缓冲区的类模板时，可以使用模板类型参数表示缓冲区中存储的对象类型，使用一个模板非类型参数表示缓冲区的大小。

template <class Type, int nSize>

class CBuffer

{

//……

};

下面定义一个节点类 CNet，演示模板类 CList 是如何适应不同的节点类型。

class CNet //定义一个节点类

{

public:

CNet *m_pNext; //定义一个节点指针，指向下一个节点

int m_nData; //定义节点的数据

CNet() //定义节点类的构造函数

{

m_pNext = NULL; //将 m_pNext 设置为空

}

};

接下来在 main 函数中定义两个链表对象，分别采用 CNode 节点类型和 CNet 节点类型。

int main(int argc, char* argv[])

{

CList<CNode> nodeList; //定义链表对象

for(int i=0; i<5; i++) //利用循环向链表中添加 5 个节点

{

CNode *pNode = new CNode(); //构造节点对象

pNode->m_nData = i; //设置节点数据

nodeList.AddNode(pNode); //添加节点到链表

}

nodeList.IterateList(); //遍历节点

CList<CNet> netList; //定义链表对象

for(int j=0; j<5; j++) //利用循环向链表中添加 5 个节点

{

CNet *pNode = new CNet(); //构造节点对象

pNode->m_nData = j; //设置节点数据

netList.AddNode(pNode); //添加节点到链表

}

netList.IterateList(); //遍历节点

return 0;

}

类模板 CList 虽然能够使用不同类型的节点，但是对节点的类型是有一定要求的。第一，节点类必须包含一个指向自身的指针类型成员 m_pNext，因为在 CList 中访问了 m_pNext 成员。第二，节点类中必须包含数据成员 m_nData，。实际上 m_nData 成员可以是任意类型，只是在 CList 类的 IterateList 方法中，笔者为了演示遍历链表节点，使用了“cout <<pTmp->m_nData << endl;”语句输出节点数据，限制了 m_nData 的数据类型。

在应用程序中使用类模板时，还应该注意以下事项。

q 模板参数与全局对象重名

模板参数的名字在模板声明后直到模板介绍都可以在模板中使用。如果在全局区域定义了与模板参数相同的对象，则在模板中全局对象被隐藏。例如：

typedef int Type; //在全局区域自定义类型

template <class Type>

class CList //定义链表类 CList 类

{

private:

Type *m_pHeader; //定义头节点

//……

};

成员 m_pHeader 是一个 Type 指针类型，而不是 int 指针类型。

q 模板参数名不能与模板中自定义类型或类的名字相同

template <class Type>

class CList //定义链表类 CList 类

{

private:

typedef int Type;

Type *m_pHeader; //定义头节点

//……

};

在应用类模板时，上面的语句将导致编译时错误。