1. 程式人生 > >C++ 類模板小結(雙向連結串列的類模板實現)

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關鍵字來實現定義與實現的分離!

相關推薦

C++ 模板小結雙向連結串列模板實現

一、類模板定義 定義一個類模板:template<class 模板引數表> class 類名{ // 類定義...... };其中,template 是宣告類模板的關鍵字,表示宣告一個模板,模板引數可以是一個,也可以是多個,可以是型別引數,也可以是非型別引數。型

c++stl的list雙向連結串列

1.list初始化: (1)list<int>  t;  //沒有任何元素 (2)list<int>  t(10);  //建立有10個元素的連結串列 (3)lis

連結串列倒序問題雙向連結串列的基礎運用

#include <stdio.h> #include <stdlib.h> struct node { int key; struct node *next,*before;//結構中包含前向指標before,後向指標next };

bzoj 4548: 小奇的糖果 && bzoj 3658: Jabberwocky雙向連結串列+樹狀陣列

Time Limit: 20 Sec  Memory Limit: 1024 MBSubmit: 263  Solved: 107 [Submit][Status][Discuss] Description 平面上有n個點,每個點有k種顏色中的一個。 你可以選擇一條

Objective-C之Autorelease Pool底層實現原理記錄雙向連結串列以及在Runloop中是如何參與進去的

最近需要重新整理知識點備用,把一些重要的原理都搞了一遍 前言 int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, a

大話資料結構——雙向連結串列的java實現

在實現了單向連結串列後,我們在使用單向連結串列中會發現一個問題:在單向連結串列中查詢某一個結點的下一個結點的時間複雜度是O(1),但是查詢這個結點的上一個結點的時候,時間複雜度的最大值就變成了O(n),因為在查詢這個指定結點的上一個結點時又需要從頭開始遍歷。 那麼該如何解決這個困難呢?

手寫LinkedList雙向連結串列

手寫LinkedList(雙向連結串列) 系統jdk裡的LinkedList是由一個個節點連線起來的,節點就相當於一個物件,裡面有資料域和指標域,資料域是存放資料用的,指標域就是指向下一個節點 從而相連線的 這裡是一個節點 那麼連結串列裡是什麼樣子的呢

HDU 6215 Brute Force Sorting雙向連結串列+佇列

Brute Force Sorting Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others) Total Submission(s): 2304    Acce

自己動手實現資料結構模板(1):雙向連結串列

今天忽然心血來潮,自己動手實現了一個簡單的雙向連結串列和基本功能,大致實現瞭如下功能 dtsListNode為結點類,包括值value、前向指標pre和後向指標next。 dtsList為雙向連結串列,能夠通過begin和end函式獲得首地址指標和指向最後一

Lava連結串列雙向連結串列---介面實現

在Java中連標配的結點需要用類來封裝,下面的簡單的雙向連結串列實現: class Node { private Object data; private Node next; public Node(Object data) {

LeetCode Sliding Window Maximum 滑動視窗雙向連結串列實現佇列效果

思路: 使用雙向連結串列(LinkedList,LinkedList類是雙向列表,列表中的每個節點都包含了對前一個和後一個元素的引用)。 雙向連結串列的大小就是視窗的個數,每次向視窗中增加一個元素時,如果比視窗中最後一個大,就刪除視窗中最後一個,以此類推,來

Leetcode 146 LRU Cache雙向連結串列+STL

解題思路:用一個雙向連結串列,維護一個最近訪問次序,用map記錄對應key的結點指標。對於get請求,需要將當前結點移動到連結串列的頭位置;對於put操作,如果是更新,則同樣將當前結點移動到頭位置,如果不是更新,則在頭位置插入一個新結點。如果連結串列長度超過快取上限,則刪除末

HDU 4286 Data Handler 雙向連結串列

                                                 Time Limit: 20000/10000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) P

資料結構與算法系列五雙向連結串列

1.引子 1.1.為什麼要學習資料結構與演算法? 有人說,資料結構與演算法,計算機網路,與作業系統都一樣,脫離日常開發,除了面試這輩子可能都用不到呀! 有人說,我是做業務開發的,只要熟練API,熟練框架,熟練各種中介軟體,寫的程式碼不也能“飛”起來嗎? 於是問題來了:為什麼還要學習資料結構與演算法呢?

判斷連結串列相交,若相交,求交點。假設連結串列可能帶環

首先我們分析,兩個連結串列是否相交,是否帶環,有以下幾種情況: 求兩個連結串列是否帶環可以分成三個情況: 1.都不帶環,可以轉換成兩個連結串列是否相交的問題。 2.一個帶環,一個不帶環。–>不相交 3.都帶環:    分別求環的入口點     1.入口點

把二元查詢樹轉變成排序的雙向連結串列 Go 語言實現

題目: 輸入一棵二元查詢樹,將該二元查詢樹轉換成一個排序的雙向連結串列。 要求不能建立任何新的結點,只調整指標的指向。       10      /   \     6  14 &

LeetCode自我總結連結串列進行插入排序

對連結串列進行插入排序。 插入排序的動畫演示如上。從第一個元素開始,該連結串列可以被認為已經部分排序(用黑色表示)。 每次迭代時,從輸入資料中移除一個元素(用紅色表示),並原地將其插入到已排好序的連結串列中。   插入排序演算法: 插入排序是迭代的,每次只移動一個元

圖知識小結6-十字連結串列的陣列實現與應用

十字連結串列是一種高效儲存稀疏圖並可以顯著提高查詢效率的一種儲存結構,解決了圖的遍歷過程中鄰接表空間消耗大而鄰接矩陣求一個點入度又需要遍歷全部表的問題,下面給出其陣列實現: //十字連結串列的陣列實現 #include <bits/stdc++.h> using namesp

二叉搜尋樹轉換為雙向連結串列的Java實現 出現的一些問題及解決

題目 二叉搜尋樹轉換為雙向排序連結串列,要求不能新增任何新的結點,只能調整樹中節點的指向。 思路 二叉搜尋樹的左子樹中結點的值總是小於根結點,右子樹中結點的值總是大於根結點,所以二叉搜尋樹的中序遍歷結果就是我們最終想要得到的連結串列順序,如圖1: 所以我們在轉

LeetCode203.Remove Linked List Elements 刪除連結串列中的節點

Remove all elements from a linked list of integers that have value val.    Example    Given: 1 --> 2 --> 6 --> 3 --> 4 --> 5