模板
函式模板
函式模板是一種特殊的函式,它可以操作
泛型型別。這允許我們建立一個函式模板,其功能可以適應多種型別或類,而無需為每種型別重複整個程式碼。
在 C++ 中,這可以使用
模板引數來實現。模板引數是一種特殊的引數,可用於傳遞型別作為引數:就像常規函式引數可用於將值傳遞給函式一樣,模板引數允許也將型別傳遞給函式。這些函式模板可以使用這些引數,就像它們是任何其他常規型別一樣。
宣告帶有型別引數的函式模板的格式是
template <class identifier> function_declaration;
template <typename identifier> function_declaration;
兩種原型之間的唯一區別在於是否使用關鍵字
類class
或關鍵字typename。 它的使用是無區別的,因為這兩個表示式具有完全相同的含義並且行為完全相同。
例如,要建立一個模板函式來返回兩個物件中較大的一個,我們可以使用
1 2 3 4
|
template <class myType>
myType GetMax (myType a, myType b) {
return (a>b?a:b);
}
|
在這裡,我們建立了一個具有
myType作為其模板引數的模板函式。 此模板引數表示尚未指定的型別,但可以在模板函式中使用,就像它是常規型別一樣。 正如您所看到的,函式模板
GetMax返回此尚未定義的型別的兩個引數中較大的一個。
要使用此函式模板,我們使用以下格式進行函式呼叫
function_name <type> (parameters);
例如,要呼叫
GetMax來比較型別為
int的兩個整數值,我們可以寫
1 2
|
int x,y;
GetMax <int> (x,y);
|
當編譯器遇到對模板函式的此呼叫時,它使用該模板自動生成一個函式,將每次出現的
myTypemyType
int替換為作為實際模板引數傳遞的型別 (在本例中為int),然後呼叫它。此過程由編譯器自動執行,程式設計師不可見。
這是完整的例子
|
// function template
#include <iostream>
using namespace std;
template <class T>
T GetMax (T a, T b) {
T result;
result = (a>b)? a : b;
return (result);
}
int main () {
int i=5, j=6, k;
long l=10, m=5, n;
k=GetMax<int>(i,j);
n=GetMax<long>(l,m);
cout << k << endl;
cout << n << endl;
return 0;
}
|
6
10 |
在本例中,我們使用了
T作為模板引數名稱,而不是
myTypemyType,因為它更短,而且實際上是一個非常常見的模板引數名稱。 但是您可以使用任何您喜歡的識別符號。
在上面的例子中,我們使用了函式模板
GetMax()兩次。 第一次使用
intint
型別的引數,第二次使用型別的引數。 編譯器已經例項化,然後每次都呼叫了函式的適當版本。
正如您所看到的,型別
TT
GetMax()在模板函式中甚至用於宣告該型別的新物件
因此,
result將是與引數
a和
和 b在函式模板使用特定型別例項化時相同的型別的物件。
在這個特定情況下,泛型型別
TT
GetMax用作GetMax()的引數,編譯器可以自動找出必須例項化的資料型別,而無需在尖括號內明確指定它(就像我們之前指定<int>和<long>一樣)。
<int>和
<long>)。 所以我們可以寫成
1 2
|
int i,j;
GetMax (i,j);
|
因為
i和
和j
int的型別都是int,編譯器可以自動找出模板引數只能是int。 這種隱式方法產生完全相同的結果。
int這種隱式方法產生完全相同的結果
|
// function template II
#include <iostream>
using namespace std;
template <class T>
T GetMax (T a, T b) {
return (a>b?a:b);
}
int main () {
int i=5, j=6, k;
long l=10, m=5, n;
k=GetMax(i,j);
n=GetMax(l,m);
cout << k << endl;
cout << n << endl;
return 0;
}
|
6
10 |
請注意,在這種情況下,我們呼叫了函式模板
GetMax()GetMax
<>而沒有顯式指定尖括號之間的型別。 編譯器自動確定每次呼叫需要什麼型別。
因為我們的模板函式僅包含一個模板引數 (
class T) 並且函式模板本身接受兩個引數,這兩個引數都是這種型別,因此我們不能使用兩種不同型別的物件作為引數呼叫我們的函式模板。
T型別,因此我們不能使用兩種不同型別的物件作為引數呼叫我們的函式模板。
1 2 3
|
int i;
long l;
k = GetMax (i,l);
|
這是不正確的,因為我們的
GetMax函式模板需要兩個相同型別的引數,而在這次呼叫中我們使用了兩種不同型別的物件。
我們還可以定義接受多個型別引數的函式模板,只需在尖括號之間指定更多模板引數即可。 例如
1 2 3 4
|
template <class T, class U>
T GetMin (T a, U b) {
return (a<b?a:b);
}
|
在這種情況下,我們的函式模板
GetMin()接受兩種不同型別的引數,並返回與傳遞的第一個引數 (T) 相同型別的物件。 例如,在該宣告之後,我們可以呼叫
TGetMin(i,l)
GetMin()使用
1 2 3
|
int i,j;
long l;
i = GetMin<int,long> (j,l);
|
GetMin(i,l)
甚至可以簡單地
和和
GetMin(i,l)儘管i和l具有不同的型別,因為編譯器無論如何都可以確定適當的例項化。
類模板
我們還可以編寫類模板,以便類可以具有使用模板引數作為型別的成員。 例如
1 2 3 4 5 6 7 8 9
|
template <class T>
class mypair {
T values [2];
public:
mypair (T first, T second)
{
values[0]=first; values[1]=second;
}
};
|
我們剛剛定義的類用於儲存任何有效型別的兩個元素。 例如,如果我們想宣告此類的物件以儲存兩個int型別的值 115 和 36,我們將編寫
intPair
myints (115,36);
1
|
mypair<int> myobject (115, 36);
|
這個類同樣可以用來建立一個儲存任何其他型別的物件
1
|
mypair<double> myfloats (3.0, 2.18);
|
前一個類模板中唯一的成員函式已在類宣告本身內聯定義。 如果我們在類模板宣告之外定義函式成員,我們必須始終在定義之前加上template <...>字首
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
|
// class templates
#include <iostream>
using namespace std;
template <class T>
class mypair {
T a, b;
public:
mypair (T first, T second)
{a=first; b=second;}
T getmax ();
};
template <class T>
T mypair<T>::getmax ()
{
T retval;
retval = a>b? a : b;
return retval;
}
int main () {
mypair <int> myobject (100, 75);
cout << myobject.getmax();
return 0;
}
|
100 |
注意成員函式 getmax 的語法
1 2
|
template <class T>
T mypair<T>::getmax ()
|
被這麼多的T搞糊塗了嗎?T這個宣告中有三個 T:第一個 T 是模板引數。 第二個T指的是函式返回的型別。 第三個T(尖括號中的那個)也是一個要求:它指定此函式的模板引數也是類模板引數。T這個宣告中有三個T's在宣告中:第一個是模板引數。第二個T指的是函式返回的型別。第三個
模板特化
如果我們想為模板定義一個不同的實現,當傳遞一個特定型別作為模板引數時,我們可以宣告該模板的特化。
例如,假設我們有一個非常簡單的類叫做mycontainer它可以儲存任何型別的一個元素,並且它只有一個成員函式叫做increase,它增加它的值。但是我們發現當它儲存一個型別為char的元素時,使用一個具有函式成員uppercase的完全不同的實現會更方便,因此我們決定為該型別宣告一個類模板特化。
|
// template specialization
#include <iostream>
using namespace std;
// class template:
template <class T>
class mycontainer {
T element;
public:
mycontainer (T arg) {element=arg;}
T increase () {return ++element;}
};
// class template specialization:
template <>
class mycontainer <char> {
char element;
public:
mycontainer (char arg) {element=arg;}
char uppercase ()
{
if ((element>='a')&&(element<='z'))
element+='A'-'a';
return element;
}
};
int main () {
mycontainer<int> myint (7);
mycontainer<char> mychar ('j');
cout << myint.increase() << endl;
cout << mychar.uppercase() << endl;
return 0;
}
|
8
J |
這是類模板特化中使用的語法
1
|
template <> class mycontainer <char> { ... };
|
首先,請注意我們在類模板名稱之前加上一個空的template<>引數列表。這是為了明確地將其宣告為模板特化。
但比此字首更重要的是,是類模板名稱後面的<char><char>特化引數。此特化引數本身標識了我們將要宣告模板類特化的型別 (char)。請注意通用類模板和特化之間的差異charchar。
1 2
|
template <class T> class mycontainer { ... };
template <> class mycontainer <char> { ... };
|
第一行是通用模板,第二行是特化。
當我們為模板類宣告特化時,我們還必須定義它的所有成員,即使那些與通用模板類完全相同的成員,因為從通用模板到特化沒有成員的“繼承”。
模板的非型別引數
除了以類class或關鍵字或typename關鍵字開頭的模板引數,它們表示型別,模板還可以具有常規型別引數,類似於函式中的引數。例如,看看這個類模板,它用於包含元素序列
|
// sequence template
#include <iostream>
using namespace std;
template <class T, int N>
class mysequence {
T memblock [N];
public:
void setmember (int x, T value);
T getmember (int x);
};
template <class T, int N>
void mysequence<T,N>::setmember (int x, T value) {
memblock[x]=value;
}
template <class T, int N>
T mysequence<T,N>::getmember (int x) {
return memblock[x];
}
int main () {
mysequence <int,5> myints;
mysequence <double,5> myfloats;
myints.setmember (0,100);
myfloats.setmember (3,3.1416);
cout << myints.getmember(0) << '\n';
cout << myfloats.getmember(3) << '\n';
return 0;
}
|
100
3.1416 |
也可以為類模板引數設定預設值或型別。例如,如果之前的類模板定義是
1
|
template <class T=char, int N=10> class mysequence {..};
|
我們可以透過宣告使用預設模板引數建立物件
這相當於
1
|
mysequence<char,10> myseq;
|
模板和多檔案專案
從編譯器的角度來看,模板不是普通的函式或類。 它們是按需編譯的,這意味著直到需要使用特定模板引數進行例項化時,才編譯模板函式的程式碼。 在需要例項化的時候,編譯器會從模板中為這些引數專門生成一個函式。
當專案增長時,通常將程式的程式碼拆分為不同的原始碼檔案。 在這些情況下,介面和實現通常是分開的。 以函式庫為例,介面通常包含所有可以呼叫的函式的原型宣告。 這些通常在帶有 .h 副檔名的“標頭檔案”中宣告,而實現(這些函式的定義)在帶有 c++ 程式碼的獨立檔案中。
因為模板是在需要時編譯的,這迫使多檔案專案受到限制:模板類或函式的實現(定義)必須與其宣告在同一個檔案中。 這意味著我們不能將介面分成單獨的標頭檔案,並且我們必須在使用模板的任何檔案中包含介面和實現。
由於在需要時例項化模板之前不會生成任何程式碼,因此編譯器已準備好允許在專案中多次包含具有宣告和定義的相同模板檔案,而不會生成連結錯誤。