一、类模板定义
定义一个类模板:
template<class 模板参数表>
class 类名{
// 类定义......
};
其中,template 是声明类模板的关键字,表示声明一个模板,模板参数可以是一个,也可以是多个,可以是类型参数,也可以是非类型参数。类型参数由关键字class或typename及其后面的标识符构成。非类型参数由一个普通参数构成,代表模板定义中的一个常量。例:
template<class type,int width>
//type为类型参数,width为非类型参数
class Graphics;
注意:
(1)、如果在全局域中声明了与模板参数同名的变量,则该变量被隐藏掉。
(2)、模板参数名不能被当作类模板定义中类成员的名字。
(3)、同一个模板参数名在模板参数表中只能出现一次。
(4)、在不同的类模板或声明中,模板参数名可以被重复使用。
(5)、在类模板的前向声明和定义中,模板参数的名字可以不同。
(6)、类模板参数可以有缺省实参,给参数提供缺省实参的顺序是先右后左。
(7)、类模板名可以被用作一个类型指示符。当一个类模板名被用作另一个模板定义中的类型指示符时,必须指定完整的实参表。
二、如何编写一个类模板
类模板一般应用于容器类中,使得容器能够处理各种类型的对象。下面的双向链表,其节点只能存放整型数据类型的元素:
struct Node
{
Node( int nData = 0 )
{
m_nData = nData;
m_pPrev = m_pNext = NULL;
}
int m_nData; // 数据元素
Node* m_pPrev; // 指向上一个元素的指针
Node* m_pNext; // 指向下一个元素的指针
};
class CDList
{
private:
int m_nCount;
Node* m_pHead;
Node* m_pTail;
public:
CDList();
virtual ~CDList();
public:
int length() const
{
return m_nCount;
}
Node* get_head() const
{
return m_pHead;
}
Node* get_tail() const
{
return m_pTail;
}
public:
void release();
//增加
Node* push_back( int nData )
{
Node* pNewNode = new Node(nData);
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;
}
Node* push_front( int nData );
Node* operator[](int nIndex);
//删除
bool erase( int nIndex );
//查找
Node* find_node( int nIndex );
};
如果要想让此双向链表能够存放任何一种类型的元素,那必须将此类修改成类模板,其实把类改写成类模板,并不是什么难事,对照原来的类,一步一步改就行了!另外,这也是最不容易出错的编码方式。下面是实现双向链表的C++代码:
#include <tchar.h>
#include <stdlib.h>
#include <iostream>
#include <assert.h>
template <typename T>
struct NodeT
{
NodeT( T Data )
{
m_Data = Data;
m_pPrev = m_pNext = NULL;
}
T m_Data; // 通用类型的数据元素
NodeT<T>* m_pPrev; // 指向上一个元素的指针
NodeT<T>* m_pNext; // 指向下一个元素的指针
};
template <typename T>
class CDListT
{
private:
int m_nCount; // 链表中元素的数量
NodeT<T>* m_pHead; // 链表头指针
NodeT<T>* m_pTail; // 链表尾指针
public:
CDListT(): m_nCount(0), m_pHead(NULL), m_pTail(NULL){}
template<typename T>
CDListT(const T &data) : m_nCount(0), m_pHead(NULL), m_pTail(NULL)
{
push_front(data);
}
virtual ~CDListT()
{
release();
}
public:
int length() const
{
return m_nCount;
}
NodeT<T>* get_head() const
{
return m_pHead;
}
NodeT<T>* get_tail() const
{
return m_pTail;
}
public:
//
NodeT<T>* operator[](int nPos)
{
return find_node(nPos);
}
//
bool empty() const { return ( !m_pHead || !m_pTail ); }
//
void release()
{
while(m_pHead)
{
NodeT<T>* temp(m_pHead);
m_pHead = m_pHead->m_pNext;
delete temp;
m_nCount = 0;
}
}
//从尾部增加
NodeT<T>* push_back( T Data )
{
NodeT<T>* pNewNode = new NodeT<T>(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;
}
//从头部增加
NodeT<T>* push_front( T Data )
{
NodeT<T>* pNewNode = new NodeT<T>(Data);
if ( m_pHead )
m_pHead->m_pPrev = pNewNode;
pNewNode->m_pNext = m_pHead;
if (m_pHead == NULL)
m_pTail = pNewNode;
m_pHead = pNewNode;
m_nCount++;
return pNewNode;
}
//从尾部弹出
template<typename T>
T pop_back()
{
if( empty() )
throw("CDListT : list is empty");
NodeT<T>* temp(m_pTail);
T data( m_pTail->m_Data );
m_pTail = m_pTail->m_pPrev;
if( m_pTail )
m_pTail->m_pNext = NULL;
else
m_pHead = NULL;
delete temp;
m_nCount--;
return data;
}
//从头部弹出
template<typename T>
T pop_front()
{
if( empty() )
throw("CDListT : list is empty");
NodeT<T>* temp(m_pHead);
T data( m_pHead->m_Data );
m_pHead = m_pHead->m_pNext;
if( m_pHead )
m_pHead->m_pPrev = NULL;
else
m_pTail = NULL;
delete temp;
m_nCount--;
return data;
}
//删除
bool erase( int nPos )
{
assert(1 <= nPos && nPos <= m_nCount);
NodeT<T> *pTmpNode = m_pHead;
// 是否为第一个节点
if (1 == nPos)
{
m_pHead = m_pHead->m_pNext;
if(m_pHead)
{
m_pHead->m_pPrev = NULL;
}
delete pTmpNode;
--m_nCount;
if (0 == m_nCount)
{
m_pTail = NULL;
}
return true;
}
//如果不是...
for (int i = 1; i < nPos; ++i)
{
pTmpNode = pTmpNode->m_pNext;
}
pTmpNode->m_pPrev->m_pNext = pTmpNode->m_pNext;
//是否为最后一个节点
if(pTmpNode->m_pNext)
{
pTmpNode->m_pNext->m_pPrev = pTmpNode->m_pPrev;
}
else
{
m_pTail = pTmpNode->m_pPrev;
}
delete pTmpNode;
--m_nCount;
if (0 == m_nCount)
{
m_pTail = NULL;
}
return true;
}
//查找
template <typename T>
NodeT<T>* find_node( int pos )
{
assert(1 <= pos && pos <= m_nCount);
NodeT<T>* pTmp = m_pHead;
for (int i=1; i < pos; ++i)
{
pTmp = pTmp->m_pNext;
}
return pTmp;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
CDListT<int> _list; // 将CDListT实例化为一个int类型,即链表中数据元素为整型
_list.push_back(12);
_list.push_back(23);
_list.push_back(34);
_list.push_back(45);
_list.push_back(56);
_list.push_back(67);
_list.push_front(78);
//12
NodeT<int> *_node = _list.find_node<int>(2);
std::cout << _node->m_Data << "\r\n";
//23
_list.erase(3);
//打印链表
int nCount = _list.length();
while( nCount > 0)
{
std::cout << _list.pop_back<int>() << ", ";
nCount--;
}
system("pause");
return 0;
}
输出结果:
12
67, 56, 45, 34, 12, 78
三、模板程序的组织
C++支持两种模板代码的组织方式,分别是包含方式和分离方式。这两种组织方式没有太根本的区别,就是一个将代码全写在头文件中,分离方式是像写类一样声明和定义分别写在头文件(.h文件)和实现文件(cpp文件)中。
3.1、包含方式:
当然,将代码都写在头文件中还有一点点小要求:
1)、 如果模板的成员函数写在类外,则需要写成如下样式:
template<typename T>// 每个类外成员函数前都要有这句
T CDListT<T>::pop_back()
{
if( empty() )
throw("CDListT : list empty");
NodeT<T>* temp(m_pTail);
T data( m_pTail->m_Data );
m_pTail = m_pTail->m_pPrev;
if( m_pTail )
m_pTail->m_pNext = NULL;
else
m_pHead = NULL;
delete temp;
m_nCount--;
return data;
}
2)、 对于特化的代码则需要在.h文件中声明并在.cpp文件中定义,如果都写在.h文件中编译会报重定义错误。
3.2、分离方式
所谓的分离方式组织代码,就是将模板的声明和定义分别写在头文件(.h文件)和实现文件(cpp文件)中,需要注意的是,并不是所有的编译器都支持这种写法,目前我只知道GCC支持这种写法。当然,分离方式组织代码也有个小要求,就是在模板的声明和定义的template关键字前都加上export关键字。比如:
// .h 头文件中
export template <typename T>
class CDListT
{
//......
};
//.cpp实现文件
export template<typename T>
T CDListT<T>::pop_back()
{
//......
}
四、学习总结:
类模板不是真正的定义,它仅在编译时根据实例化本模板时,传递的实参来生成具体的类代码!若类模板没有被实例化也没有被调用,那编译器不会为本模板生成任何代码,也不对模板代码作任何语法检查。
因此,模板的重载、特化等多态性也都是在编译期间体现出来的,如果我们对编译生成的可执行文件进行反汇编时,我们不会找到任何与模板有关的代码,因为模板只是编译期间的产物。
如果将模板的定义部分和实现部分分离开来,编译器真正要去完成模板实体化的时候,就会因为找不到相应的代码而发生链接错误,所以这是编译器的问题,因为C++标准是要求能实现分离编译的。但是!可以通过export关键字来实现定义与实现的分离!