1. 程式人生 > >模板與泛型程式設計

模板與泛型程式設計

       當我們希望可以用同一個函式處理不同型別的引數時(比如寫一個加法函式,可以處理各種不同型別的資料)都有哪些方法呢? 1、函式過載(同一作用域;函式名相同;引數列表不同) 缺點:         a、只要有新型別出現,就必須新增對應的函式         b、除了型別外,所有的函式體都相同,程式碼的複用率太低         c、如果只是返回型別不同,函式過載不能實現         d、一個方法有問題,所有的方法都有問題,不好維護 2、使用公共基類,將所有通用的程式碼放在公共的基類裡面(通過繼承的方式將其複用) 缺點:         a、藉助公共基類來編寫通用程式碼將失去型別檢查的優點
        b、對於以後實現的許多類,都必須繼承自某個特定的基類,程式碼維護更賤困難 3、使用特殊的預處理程式(使用巨集) 缺點:         a、不是函式,不會進行型別檢測,安全性不高         b、不能除錯         c、經常會有副作用(除非將每一項都打上括號)         d、直接替換的方式,如果多次使用會使程式碼變得越來越長 接下來要介紹的另外一種新的方法--------泛型程式設計 泛型程式設計:編寫與型別無關的邏輯程式碼,是程式碼複用的一種手段。模板是泛型程式設計的基礎。模板相當於一個藍圖,它本身不是類或函式,編譯器用模板產生指定的類或函式的特定版本型別,產生模板特定型別的過程稱為函式模板的例項化。
模板分為函式模板和類模板。 函式模板:代表了一個函式家族,該函式與型別無關,在使用時被引數化,根據實參型別產生函式的特定型別版本。 格式: 模板形參名在同一模板形參列表中只能使用一次。所有模板前面必須加上class或者typename. 舉個例子
#include <iostream>
using namespace std;

template <class T>
T Add(T left, T right)
{
	return left + right;
}

int main()
{
	cout << Add(1, 2)<<endl;//T為int
	cout << Add(1.1, 2.2)<<endl;//T為double

	//cout << Add(1, 2.2) << endl;
	cout << Add(1, (int)2.2)<<endl;//T為int
	cout << Add<int>(1, 2.2)<<endl;//顯示例項化

	return 0;
}
上邊這個例子,當不將主函式中的第三行程式碼註釋的話是會因為兩個實參的型別不同而報錯,遇到這種情況,我們可以用強制型別轉化或顯示例項化的方式解決。 註釋前 將其註釋後通過編譯,沒有任何錯誤。 注意,模板被編譯了兩次,一次T為int ,一次T為double。通過函式呼叫給出的實參來確實能夠模板形參 T 的型別和值的過程稱為模板的實參推演 型別形參轉換 一般不會將實參轉換為已有的例項化的型別,(比如不會為了匹配已經例項化的函式而將上述例子中主函式的第二行程式碼中的double型別的實參轉換為 int),相反的,會產生新的例項。 編譯器只會執行兩種轉換: 1、const轉換:接收const引用或const指標的函式可以分別用非const物件的引用或指標來呼叫 2、陣列或函式到指標的轉換:如果模板形參不是引用型別,則對陣列或函式型別的實參應用常規指標轉換。陣列實參將會被當做指向第一個元素的指標,函式實參當做指向函式型別的指標。 模板引數有兩種型別:型別形參和非型別形參。 型別形參: 上述例子的模板就是型別形參。型別形參名字只能在模板形參列表後到模板宣告或定義的末尾之間使用,且遵循名字遮蔽規則。 非模板型別引數 非模板型別引數是在模板內部定義的常量,在需要常量表達式的時候,可以使用非模板型別引數 例如陣列長度 說明: 1、模板函式也可以過載

2、一個非模板函式可以和一個同名的函式模板同時存在,而且該函式模板可以被例項化為該非模板函式

3、對於非模板函式和同名函式模板,如果其他條件都相同,在呼叫時會優先呼叫該非模板函式,而不是從模板函式產生出一個模板。如果函式模板可以產生出一個更加匹配的函式,則選擇函式模板

4、顯示指定一個空的模板實參列表,該語法告訴編譯器只有模板才能匹配這個呼叫,而且所有的模板引數都應該根據實參演繹出來。

5、模板函式不允許自動型別轉換,普通函式可以進行自動型別轉換

模板函式特化

       有時候並不總是能夠寫出對所有可能被例項化的型別都最合適的模板,在某些情況下,通用模板定義對於某個型別可能是完全錯誤的,或者不能編譯,或者做一些錯誤的事情。 舉個例子:
#include <iostream>
using namespace std;

template <class T>
int Compare(T t1, T t2)
{
	if (t1 < t2)
		return -1;
	else
		return 1;
}

int main()
{
	char *str1 = "bcde";
	char *str2 = "abcd";
	int ret = Compare(str1, str2);

	return 0;
}
正常情況下結果為1,看一看執行結果 模板函式特化的形式: 1、關鍵自template後邊接一對尖括號 < > 2、函式名後邊接模板名和一對尖括號,尖括號中指定這個模板特化定義的模板形參 3、函式形參表 4、函式體 將上邊的這個比較函式特化出一個函式
#include <iostream>
using namespace std;

template <class T>
int Compare(T t1, T t2)
{
	if (t1 < t2)
		return -1;
	else
		return 1;
}

template<>
int Compare<const char *>(const char* p1, const char* p2)
{
	return strcmp(p1,p2);
}

int main()
{
	char *str1 = "bcde";
	char *str2 = "abcd";
	int ret1 = Compare(str1, str2);

	const char *pstr1 = "bcde";
	const char *pstr2 = "abcd";
	int ret2 = Compare(pstr1, pstr2);

	return 0;
}
由上圖可以看到,在函式特化版本的呼叫中,實參型別必須與特化版本函式的形參型別完全匹配,如果不匹配,編譯器將將為實參版本定義中例項化一個例項而不會呼叫特化的版本。
模板類         模板類也是模板,必須以關鍵字template開頭,後接模板形參表。 模板類格式:        
#include <iostream>
using namespace std;

template <class T>
class SeqList
{
	T* _data;
	int _size;
	int _capacity;
};

int main()
{
	SeqList<int> s1;
	SeqList<float> s2;

	return 0;
}
注意:類名 SeqList 是模板類的類名,它不是一個真正地類,只是一個類模板,而類名SeqList不是一個完整的類型別。在類名後邊加一對尖括號,尖括號裡邊是將要例項化的模板形參的實際型別,比如SeqList < int >
        還需注意的是在模板引數T同樣是在模板引數列表給出之後到宣告或定義的末尾有效,其他地方要使用模板引數T,需要在使用之前用 template < T >宣告。
PS:歡迎提出各種意見哦~吐舌頭