1.題目
用兩個棧實現一個隊列。隊列的聲明如下,請實現它的兩個函數appendTail和deleteHead,分別完成在隊列尾部插入結點和在隊列頭部刪除結點的功能。
2.筆者解答
大概思路如下圖所示:
實現代碼如下:
template<typename T>class CQueue
{
public:
CQueue(void);
~CQueue(void);
void appendTail(const T& node);
T deleteHead();
private:
stack<T> stack1;
stack<T> stack2;
}
template<typename T>
void CQueue<T>::appendTail(const T& node)
{
stack1.push(node);
}
T CQueue<T>::deleteHead();
{
T temp;
while(stack1.size>0)
{
stack2.push(stack1.top());
stack1.pop();
}
temp=stack2.pop();
while(stack2.size>0)
{
stack1.push(stack2.top());
stack2.pop();
}
return temp;
}
3.涉及知識點
(一)泛型
重載幫助我們實現了同一函數可以實現不同的功能,也可以接收不同類型的參數。比如當我們想寫一個swap()交換函數時,通常這樣寫:
void swap(int& a,int&b)
{
int temp=a;
a=b;
b=temp;
}
這個函數僅僅只能支持int類型,當然,爲了讓這個函數能夠接收更多類型的參數,我們也可以手動重載,但這樣耗時耗力,有時可能因爲重載不合理而出錯。而通過函數模板和類模板來實現泛型編程,便可解決這一問題。
泛型編程是一種語言機制,通過它可以實現一個標準的容器庫。像類一樣,泛型也是一種抽象數據類型,但是泛型不屬於面向對象,它是面向對象的補充和發展。在面向對象編程中,當算法與數據類型有關時,面向對象在對算法的抽象描述方面存在一些缺陷。比如對棧的描述:
class stack
{
push(參數類型)//入棧算法
pop(參數類型)//出棧算法
}
如果把上面的僞代碼看作算法描述,沒問題,因爲算法與參數類型無關。但是如果把它寫成可編譯的源代碼,就必須指明是什麼類型,否則是無法通過編譯的。使用重載來來解決這個問題,即對N種不同的參數類型寫N個push和pop算法,這樣是很麻煩的,代碼也無法通用。那麼這裏我們可能會想能不能讓參數的類型也成爲參數呢?顯然可以的,那就是我們接下來要講的泛型。
若對上面的描述進行改造如下:
首先指定一種通用類型T,不具體指明是哪一種類型。
class stack<參數模板>
{
push(T);//入棧算法
pop(T);//出棧算法
}
這是我們可以稱class stack<參數模板T>是類的類,通過它可以生成具體參數類型不同的類。這裏的參數模板T相當於一個佔位符,又或者說是一個類型參數,當我們實例化類stack時,T會被具體的數據類型替換掉。
泛型在C++中的應用
泛型在C++中的主要通過函數模板和類模板來實現泛型編程。
1.函數模板
- 一種特殊的函數,可通過不同類型進行調用
- 函數模板是C++中重要的代碼複用方式
- 通過template關鍵字來聲明使用模板
- 通過typename關鍵字來定義模板類型
比如:
template<typename T>//聲明使用模板,並定義是一個模板類型
void swap(T& a,T& b)
{
T c=a;
a=b;
b=c;
}
當我們使用int類型參數來調用上面的Swap()時,則T就會自動轉換爲int類型
函數模板的使用
分爲自動調用和顯示調用
例如,我們寫了一個Swap函數模板,然後在main()函數裏寫入:
int a=0;
int b=1;
Swap(a,b);//自動調用,編譯器根據a和b的類型來推導
float c=0;
float d=1;
Swap<float>(c,d);//顯示調用,告訴編譯器,調用的參數是float類型
深入理解函數模板
- 其實編譯器對函數模板進行了兩次編譯
- 第一次編譯時,首先去檢查函數本身有沒有語法錯誤
- 第二次編譯時,會去找調用函數模板的代碼,然後通過代碼的真正參數,來生成真正函數。
- 所以函數模板其實只是一個模具,當我們調用它是,編譯器就會給我們生成真正的函數。
多參數函數模板
在我們之前小節學的函數模板都是單參數的,其實函數模板可以定義任意多個不同的類型參數,例如:
template<typename T1,typename T2,typename T3>
T1 Add(T2 a,T3 b)
{
return static_cast<T1>(a+b);
}
注意:
- 工程中一般都將返回值參數作爲第一個模板類型
- 如果返回值參數作爲了模板類型,則必須需要指定返回值模板類型。因爲編譯器無法推導出返回值類型
- 可以從左向右部分指定類型參數
//T1=int ,T2=double,T3=double
int r1=Add<int>(0.5,0.8);
//T1=int,T2=float,T3=double
int r2=Add<int,float>(0.5,0.8);
//T1=int,T2=float,T3=float
int r3=Add<int,float,float>(0.5,0.8);
2.類模板
和函數模板一樣,將泛型思想應用於類
編譯器對類模板處理方式和函數模板相同,都是進行兩次編譯
類模板通常應用於數據結構方面,使得類的實現不在關注數據元素的具體類型,而只關注需要實現的功能。
比如:數組類,鏈表類,Queue類,Stack類等。
使用方法
通過template關鍵字來聲明,然後通過typename關鍵字來定義模板類型,如下圖所示:
template<typename T>
class Operator
{
public:
T op(T a,T b);
};
類模板的使用
定義對象時,必須指定類模板類型,因爲編譯器無法推導類型
使用具體類型來定義對象
如下例所示:
Operator<int>op1;
Operator<string> op2;
int i=op1.op(1,2);
string s=op2.op("D.T.","Software");
類模板的工程應用
- 類模板必須在.h頭文件中定義
- 類模板的成員函數不能分開在不同的文件中實現
- 類模板外部定義的成員函數,和模板函數一樣,還需要加上模板template聲明,以及結構體聲明。
參考鏈接
(二)棧與隊列
4.就提論題
本題思路比較簡單,直接上參考答案
template<typename T>void CQUeue<T>::appendTail(const T& element)
{
stack1.push(element);
}
template<typename T> T CQueue<T>::deleteHead()
{
if(stack2.size()<=0)
{
while(stack1.size()>0)
{
T &data=stack1.top();
stack1.pop();
stack2.push(data);
}.
}
if(stack2.size()==0)
throw new exception("queue is empty");
T head=stack2.top();
stack2.pop();
return head;
}