進階篇_STL詳解(函式模板特化,類别範本特化,用模板實現自己的通用演算法)

Pop_Rain發表於2017-04-28

STL ~= 容器 + 演算法 + 迭代器。容器用於容納資料,演算法用於處理資料,迭代器像膠水一樣將容器和演算法緊密地結合在一起。演算法通過迭代器來定位和操控容器中的元素,事實上,每個容器都有自己的迭代器,只有容器自己才知道如何訪問自己的元素,這使得迭代器就像一個指向容器中元素的普通指標一樣。


#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//工資統計,計算收入大於1000的員工人數 

int main()
{
	vector<int> vecSalary;
	
	int put;
	do
	{
		cin>>put;
		if(0 == put)
			break;
		vecSalary.push_back(put);
	}while(true);

	int total = count_if(vecSalary.begin(), vecSalary.end(), bind2nd(greater<int>(), 1000)); //count_if:返回區間中滿足指定條件的元素數目。          
	cout<<total<<endl; 
	
	return 0;
}
容器vector的使用不像陣列那樣固定分配記憶體,其可以根據使用者的輸入動態地增長,保證記憶體空間的合理利用,又可以讓程式具有很大的可擴張性,處理很大範圍內的資料。由此可得到STL的主要幾個好處:封裝很多常用的資料結構(容器),提供很多常用的通用演算法(演算法),除錯程式更加安全和方便,支援跨平臺(使用STL的C++程式碼可以輕鬆地移植到其他平臺上)。模板是實現程式碼重用的一種重要機制,它可以實現型別的引數化,把型別定義為引數,而STL正是藉助模板的威力建構起來的。

函式模板

如果說把函式比作一個箱子,每個箱子都是專用的(實現具體的不同功能),那函式模板就是一個萬能箱子,它可以用來裝各種東西,處理各種型別的資料。比如當編譯器發現一個函式模板的呼叫後,將根據實參的實際資料型別來確認是否匹配函式模板中對應的形參,然後生成一個過載函式,稱該過載函式為模板函式。根據引數型別的不同,一個函式模板可以隨時變成各種不同的過載函式,從而實現對各種資料型別的處理。函式模板可以用來建立一個通用功能的函式,以支援各種不同的資料型別,簡化過載函式的設計。函式模板的宣告與使用如下:

#include <iostream>
using namespace std;
//模板函式範例
 
template <typename T>    //type理解為模板函式中的一個引數,代表抽象的資料型別 
T max(const T a, const T b)
{
    return a>b ? a : b;
}

int main()
{
    int a = 10;
    int b = 20;
    int c = std::max<int>(a, b);    //注意寫法,不加std::,會error 
    cout<<c<<endl; //output: 20 
    
    float aa = 0.3;
    float bb = 0.4;
    float cc = std::max(aa, bb);    //max<float>的<...>不寫編譯器會自動分析生成相應的過載函式,但是有時候必須要指明型別引數以滿足特殊需求,比如max<string> 
    cout<<cc<<endl; //output: 0.4 
    
    string aaa = "abcd";
    string bbb = "cde";
    string ccc = std::max(aaa, bbb); 
    cout<<ccc<<endl; //output: cde,這裡輸出cde,不是返回最長的而是返回字元較大的,所以如果需要應該對模板函式進行特化,實現特定型別的模板函式 
    
    return 0;
}

特化模板函式,實現特定型別的string模板函式,希望返回最長的字串:

#include <iostream>
using namespace std;
//特化函式模板string範例

template <typename T>	//普通函式模板 
T max(const T a, const T b)
{
	return a>b ? a : b;
}

template <>	//特化string的函式模板 
string max<string>(const string a, const string b)	//注意這裡及以上的寫法格式 
{
	return a.size()>b.size() ? a : b; 
}

int main()
{
	int a = 5;
	int b = 4;
	float c = 5.3;
	float d = 4.4;
	cout<<std::max(a, b)<<endl<<std::max(c, d)<<endl;	//output: 5  5.3
	
	
	string x = "chen";
	string y = "aisda";
	cout<<std::max(x, y)<<endl; //用普通函式模板output: cde,返回字元較大的
	cout<<std::max<string>(x, y)<<endl; //用特化函式模板output: chen,返回字串最長的 
	
	return 0;
}

類别範本

#include <iostream>
using namespace std;
//特化類别範本範例

//定義一個比較兩個數的類别範本compare<T> 
template <typename T>	//typename所定義的識別符號實際上就是類别範本的引數,模板引數可以是一個可以是多個 
class compare
{
public:
	compare(T a, T b)
	: m_a(a), m_b(b)
	{
	}
public:
	T min()
	{
		return m_a > m_b ? m_b : m_a;
	}
	T max()
	{
		return m_a > m_b ? m_a : m_b;
	}	
private:
	T m_a;
	T m_b;
};

int main()
{
	compare<int> intcompare(2, 3);
	cout<<intcompare.max()<<" > "<<intcompare.min()<<endl;
	
	compare<float> floatcompare(2.2, 4.4);
	cout<<floatcompare.max() <<" > "<<floatcompare.min()<<endl;

	return 0;
}
/*output: 3 >2  4.4 > 2.2*/ 

模板的例項化

模板的例項化分為:隱式例項化和顯式例項化。

compare<int> intcompare(2, 3);	//隱式例項化
大多數都採用隱式例項化,具體的顯式例項化如果需要時自行再查,注意的是顯式例項化類時,所有的類成員也必須例項化。

用模板實現自己的通用演算法

#include <iostream>
using namespace std;
/*軟體誰集中,通常有一個撤銷undo和恢復redo的通用功能*/

/*動作容器模板類,使用T作為模板引數*/
template <class T>
class actioncontainer
{
public:
	actioncontainer()
	{
		m_nredo = 0;
		m_nundo = 0;
	}
	void add(T value);	//向容器中新增新動作 
	T redo();	//恢復上一步動作 
	T undo();	//撤銷上一步動作 
private:
	int m_nredo;
	int m_nundo;
	const static int ACTION_SIZE = 5;
	T m_redoAction[ACTION_SIZE]; //使用陣列記錄恢復和撤銷的動作,陣列元素的型別也是模板引數,它在類别範本例項化時才確定 
	T m_undoAction[ACTION_SIZE];
};

/*模板類的成員函式實現*/
//向容器中新增動作,這裡的T可以是一個基本資料型別,也可以是一個自定義型別 
template <class T>
void actioncontainer<T>::add(T value)
{
	//判斷容器中的動作數目是否超過容器的容量 
	if(m_nundo >= ACTION_SIZE)
	{
		m_nundo = ACTION_SIZE - 1;
		for(int i=0; i<ACTION_SIZE; ++i)
			m_undoAction[i] = m_undoAction[i+1];	//將容器中已有的動作前移一個位置 
	}
	//將新動作新增到容器中
	m_undoAction[m_nundo++] = value; 
}

//撤銷上一步動作 
template <class T>
T actioncontainer<T>::undo()
{
	m_redoAction[m_nredo++] = m_undoAction[--m_nundo];	//將撤銷的動作複製到恢復陣列中 
	return m_undoAction[m_nundo];	//返回撤銷的動作 
}

//恢復上一步動作 
template <class T>
T actioncontainer<T>::redo()
{
	m_undoAction[m_nundo++] = m_redoAction[--m_nredo];	//將撤銷的動作複製到恢復陣列中 
	return m_redoAction[m_nredo];	//返回撤銷的動作 
}

int main()
{
	actioncontainer<int> intaction;	//定義一個int型別的動作容器,這樣這個動作容器就可以容納int型別的資料 
	
	intaction.add(1);	//向容器中新增新的動作,也就是整數 
	intaction.add(2);
	intaction.add(3);
	intaction.add(4);
		 
	
	int nundo = intaction.undo(); //撤銷上一步動作,這時,nundo的值為4
	nundo = intaction.undo();//再次撤銷身上一步動作,nundo的值為3
	
	int nredo = intaction.redo(); //恢復上一步動作,這時,nredo的值為3
	nredo = intaction.redo();//再次恢復身上一步動作,nredo的值為4
	
	return 0;
}
 

以上程式碼定義了一個類别範本actioncontainer<T>,這個類可以容納動作(add)並且能夠進行撤銷(undo)和恢復(redo)動作。在這個模板類的內部,使用了兩個陣列來分別記錄撤銷和恢復的動作。這裡的動作實際上是廣義上的一種資料型別,它可以是一個基本資料型別比如int float,也可以是自己定義的資料型別,這樣就使得這個動作容器具有廣泛的通用性,可以處理各種資料型別。在這個動作容器類别範本actioncontainer<T>中,還定義了它的成員函式,用於操作這個容器中的資料,其中包括新增新的動作到容器中、撤銷或恢復動作等。當然,這裡成員函式同樣是對抽象的模板引數進行操作,其中並不涉及具體的資料型別,也就是說,這些操作與具體的資料型別無關。



相關文章