模板
模板(template)是一個將數據類型參化的工具,它提供了一種將代碼與數據類相脫離的機制,即代碼不受具體的數據類型的影響。模板分爲函數模板和類模板兩種。
(1)函數模板
函數模板是一種不說明某些參數的數據類型的函數。例如,下面定義了一個可對任何類型變量進行操作(求絕對值)的函數模板:
- template <class T> //或寫成: template <typename T> 函數參數的類型T
- T abs(T val)
- {
- return val<0 ? -val : val;
- }
在template語句與函數模板定義語句之間不允許有別的語句。如下面的聲明是錯誤的:
template<class T>
int I; // 不該出現在此位置
T min(T x,T y)
{
//函數體
}
在函數模板被調用時,編譯器根據實際參數的類型確定模板參數T的類型,並自動生成一個對應的函數,即模板函數。模板參數的類型不同,生成的模板函數也不同。
模板函數類似於重載函數,但兩者有很大區別:函數重載時,每個函數體內可以執行不同的動作,但同一個函數模板實例化後的模板函數都必須執行相同的動作
例 1 函數模板的定義和使用
- #include <iostream.h>
- template <class T> //定義模板
- T abs(T val) //定義函數模板
- {
- return val<0 ? -val : val;
- }
- void main()
- {
- int i=100;
- cout <<abs(i)<<endl; //類型參數T替換爲int
- long l=-12345L;
- cout <<abs(l)<<endl; //類型參數T替換爲long
- float f=-125.78F;
- cout <<abs(f)<<endl; //類型參數T替換爲float
- }
定義函數模板時也可以使用多個類型參數,這時每個類型參數前面都要加上關鍵字class或typename,其間用逗分隔,其形式如下所示。
template <class T1,class T2,class T3>
例 2 使用多個類型參數的函數模板
- #include <iostream.h>
- template <class T1,class T2>
- T1 Max(T1 x,T2 y)
- {
- return x>y ? x: (T1)y;
- }
- void main()
- {
- int i=100;
- float f=-125.78F;
- cout <<Max(i,f)<<endl; //類型參數T1替換爲int,T2替換爲float
- }
(2)類模板
使用多個類型參數的類模板
- #include <iostream.h>
- template <class T1,class T2> //使用2個類型參數 類的數據變量參數類型T
- class MyTemClass //定義類模板
- {
- private:
- T1 x;
- T2 y;
- public:
- MyTemClass(T1 a,T2 b) { x=a;y=b; } //構造函數
- void ShowMax() //輸出最大的數據成員
- {
- cout <<"MaxMember="<<(x>y?x:y)<<endl;
- }
- };
- void main()
- {
- int a=100;
- float b=123.45F;
- MyTemClass<int,float> mt(a,b); //聲明類模板的對象
- mt.ShowMax();
- }
(3)模版特化
模板的“特化”(實例化),它發生在編譯期,
無論一個模板被實例化多少次,都不會影響最終結果,但是這會浪費編譯的時間.
不知道隱式特化是啥東西.但是顯式特化的意思是:當一類東西中出了一渣滓的時候,爲了對外接口的統一,或者說是爲了家醜不可外揚,有必要把它單獨拿出來寫一下,然後使他可以和這個類中的所有東西步伐一致。
爲了需要,針對特定的類型,需要對模板進行特化,也就是特殊處理, 是爲模板的特化.
template <class T>
class A {};
template < > class A<bool> { //…// };
上述定義中template < >告訴編譯器這是一個特化的類模板。
函數模板的特化
template <class T>
T mymax(const T t1, const T t2)
{
return t1 < t2 ? t2 : t1;
}
看下面的例子
main()
{
int highest = mymax(5,10);
char c = mymax(‘a’, ’z’);
const char* p1 = “hello”;
const char* p2 = “world”;
const char* p = mymax(p1,p2);
}
前面兩個mymax都能返回正確的結果.而第三個卻不能,因爲,此時mymax直接比較兩個指針p1 和 p2 而不是其指向的內容.
針對這種情況,當mymax函數的參數類型爲const char* 時,需要特化。
template <>
const char* mymax(const char* t1,const char* t2)
{
return (strcmp(t1,t2) < 0) ? t2 : t1;
}
現在mymax(p1,p2)能夠返回正確的結果了。
(4)模板的偏特化
模板的偏特化是指需要根據模板的某些但不是全部的參數進行特化
(1) 類模板的偏特化
例如c++標準庫中的類vector的定義
template <class T, class Allocator>
class vector { // … // };
template <class Allocator>
class vector<bool, Allocator> { //…//};
這個偏特化的例子中,一個參數被綁定到bool類型,而另一個參數仍未綁定需要由用戶指定。
(2) 函數模板的偏特化
嚴格的來說,函數模板並不支持偏特化,但由於可以對函數進行重載,所以可以達到類似於類模板偏特化的效果。
template <class T> void f(T); (a)
根據重載規則,對(a)進行重載
template < class T> void f(T*); (b)
如果將(a)稱爲基模板,那麼(b)稱爲對基模板(a)的重載,而非對(a)的偏特化。C++的標準委員會仍在對下一個版本中是否允許函數模板的偏特化進行討論。
/* 此處內容有待考證*/
模板函數可以有進行如下的偏特化定義:
template<> functionname<int>(int &t1 , int &t2){...} 或者
template functionname<int>(int &t1 , int &t2){...} 或者
template functionname(int &t1 , int &t2){...}
這三種定義是等同的.
這些聲明的意思是:不要使用functionname函數模板來生成一個函數定義,而應該使用獨立的、專門的函數定義顯示的爲數據類型int生成函數定義.
當編譯器找到與函數調用匹配的偏特化定義的時候,編譯器會優先使用該定義,而不再尋找模板定義。
(5)模板特化時的匹配規則
(1) 類模板的匹配規則
最優化的優於次特化的,即模板參數最精確匹配的具有最高的優先權
例子:
template <class T> class vector{//…//}; // (a) 普通型
template <class T> class vector<T*>{//…//}; // (b) 對指針類型特化
template <> class vector <void*>{//…//}; // (c) 對void*進行特化
每個類型都可以用作普通型(a)的參數,但只有指針類型才能用作(b)的參數,而只有void*才能作爲(c)的參數
(2) 函數模板的匹配規則
非模板函數具有最高的優先權。如果不存在匹配的非模板函數的話,那麼最匹配的和最特化的函數具有高優先權
例子:
template <class T> void f(T); // (d)
template <class T> void f(int, T, double); // (e)
template <class T> void f(T*); // (f)
template <> void f<int> (int) ; // (g)
void f(double); // (h)
bool b;
int i;
double d;
f(b); // 以 T = bool 調用 (d)
f(i,42,d) // 以 T = int 調用(e)
f(&i) ; // 以 T = int* 調用(f)
f(d); // 調用(h)
C++中,函數模板與同名的非模板函數重載時,應遵循下列調用原則:
• 尋找一個參數完全匹配的函數,若找到就調用它。若參數完全匹配的函數多於一個,則這個調用是一個錯誤的調用。
• 尋找一個函數模板,若找到就將其實例化生成一個匹配的模板函數並調用它。
• 若上面兩條都失敗,則使用函數重載的方法,通過類型轉換產生參數匹配,若找到就調用它。
• 若上面三條都失敗,還沒有找都匹配的函數,則這個調用是一個錯誤的調用。
至於函數的選擇原則, 可以看看C++ Primer中的說明.
I、 創建候選函數列表,其中包含與被調用函數名字相同的函數和模板函數。
II、使用候選函數列表創建可行的函數列表。這些都是參數數目正確的函數,並且有一個隱式的轉換序列(參數類型轉化),其中包括實參類型與相應的形參類型完全匹配情況。
III、確定是否有最佳的可行函數,有則調用它,沒有則報錯。
可行函數的最佳性,主要是判斷使用函數的參數與可行性函數的參數的轉換規則進行判斷,從最佳到最差的順序如下所示:
i、完全匹配,但常規函數優先於顯示定義模板函數,而顯示定義模板函數優先於模板函數。
ii、提升轉換,即從小精度數據轉換爲高精度數據類型,如char/short 轉換爲int , int轉化爲long,float轉換爲double。
iii、標準轉換,如int轉化爲char,long轉化爲double等
iiii、用戶自定義轉換。
(6)排序函數模板的實現
該函數模板使用冒泡法對集合元素進行排序,參數說明:
collection 集合對象,集合對象必須提供 [] 操作。
element 集合元素,該參數的作用僅僅是確定集合元素類型,參數的值沒有用,建議取集合的第一個元素。集合元素必須提供複製、賦值和比較操作。
count 集合元素的數目
ascend 表明排序時使用升序(true)還是降序(false)
該函數模板支持C++數組以及MFC集合CStringArray、CArray。
代碼如下:
template <typename COLLECTION_TYPE,typename ELEMENT_TYPE>
void BubbleSort(COLLECTION_TYPE collection[],ELEMENT_TYPE element,int count,bool ascend=true)
{int j,element_flag;
int k=count-1;
while(k>0)
{
element_flag=0;
for(j=element;j<k;j++)
{
if(ascend)
{
if(collection [j] > collection [j+1])
{
COLLECTION_TYPE temp=collection [j];
collection [j] = collection [j+1];
collection [j+1] = temp;
element_flag=j;
}
}
else
{
if(collection [j] < collection [j+1])
{
COLLECTION_TYPE temp=collection [j];
collection [j] = collection [j+1];
collection [j+1] = temp;
element_flag=j;
}
}
}
k=element_flag;
}
}
利用它對整型數組進行排序,
int ArrayInt[]={29,12,4,34,56,0,8,6,18,32};
BubbleSort(ArrayInt,0,10,false);//如果省去false講按默認的true排列
對整數集合按升序排序:
CArray <int, int> collectionInt;
collectionInt.Add(34);
collectionInt.Add(90);
collectionInt.Add(6);
collectionInt.Add(91);
collectionInt.Add(37);
collectionInt.Add(21);
collectionInt.Add(187);
BubbleSort(collectionInt, collectionInt[0],collectionInt.GetSize());
對字符串數組的排列:
string arrayString[4] = {"jackjones", "lee", "levi's","boss"};
BubbleSort(arrayString,0,4,false);
對一個字符串集合按降序排序:
CStringArray collectionString;
collectionString.Add("jackjones");
collectionString.Add("lee");
collectionString.Add("levi's");
collectionString.Add("boss");
BubbleSort(collectionString, collectionString[0],collectionString.GetSize(), false);