C++ 类模板小结(双向链表的类模板实现)

一、类模板定义

定义一个类模板:

 

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关键字来实现定义与实现的分离!

 

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hellokandy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值