大約是受宏(預先處理)的壓迫太深,所以有了模板以後的第一件事情便是定義模板函數來取代那些MAX和MIN。作者也樂得如此,下面我就馬上來看一個最簡單的模板函數max的實現:
template <typename T><br />inline T const& max (T const& a, T const& b)<br />{<br /> // if a < b then use b else use a<br /> return a < b ? b : a;<br />}
如果費力的逐個解釋一下的話,那大概是這樣:template表明了這是一個模板函數,<>指定了模板參數地區,typename表明了後面的參數是一個類型名,而T則表明了一種廣泛意義上的類型,它可以用來指定所有的類型(int, float, std::string)。所有的這些構成了一個最基本的模板函數,需要說明的一點是由於曆史的原因,這裡的typename可以用class來取代。(我們確實看到很多都是用class來描述的)。當然也有人問了,那struct是不是也可以,答案當然是不可以!
瞭解了這個函數的定義,第二步要做的就是探索如何調用它。這裡有幾個例子:
<br />max(1,2 );// max<int><br />max('1','2');// max<char><br />max(1, 2.5 );// compile error<br />max<double>(1,2.5 );// max<double>
第一個和第二個調用沒有任何問題,就像普通的函數調用,當然他們的調用是可以成功的。但是到第三個調用的時候,問題出現了,編譯器報了一個錯誤'const T &max(const T &,const T &)' : template parameter 'T' is ambiguous。可以看到這裡的參數類型出了問題,兩個參數的類型不一致。一個是int一個double,所以編譯器不知道怎麼指定T,注意這裡編譯器需要的是一個確切的類型,所以他是不會主動給你做任何類型轉化的。那怕從int32到int16都不會。所以在這裡你需要顯示地告訴編譯器你要怎麼做,這就是第四個調用max<double>(1, 2.5);或者你也可以強制轉化一下參數1,讓他轉化成double然後調用,比如說max(static_cast<double>1, 2.5)。
更進一步,我們來看看編譯器是如何協助我們實現這些調用的。簡單的說,編譯器採用的辦法就是存在什麼類型的調用,我就給你產生一堆什麼類型對應的max代碼。比如說max(1,2);調用來的時候,我就給你產生一段這樣的代碼:
// maximum of two int values<br />inline int const& max (int const& a, int const& b)<br />{<br /> return a < b ? b : a;<br />}
就是用int來替代了原來的T產生了具體類型的代碼,這樣原來程式員需要為不同類型版本寫函數的工作就交給了編譯器來做。編譯器為不同類型產生對應函數代碼的過程被稱之為執行個體化(Instantiation,很巧和OO裡面的執行個體化是同一個詞。當然意義是決然不同的)。接下來我們可以看看一個複雜一點的例子:
#include "stdafx.h"<br />#include <iostream><br />#include <cstring><br />#include <string><br />// maximum of two values of any type<br />template <typename T><br />inline T const& max (T const& a, T const& b)<br />{<br />return a < b ? b : a;<br />}<br />// maximum of two pointers<br />template <typename T><br />inline T* const& max (T* const& a, T* const& b)<br />{<br />return *a < *b ? b : a;<br />}<br />// maximum of two C-strings<br />inline char const* const& max (char const* const& a,<br /> char const* const& b)<br />{<br />return std::strcmp(a,b) < 0 ? b : a;<br />}<br />int _tmain(int argc, _TCHAR* argv[])<br />{<br />int a=7;<br />int b=42;<br />::max(a,b); // max() for two values of type int<br />std::string s="hey";<br />std::string t="you";<br />::max(s,t); // max() for two values of type std::string<br />int* p1 = &b;<br />int* p2 = &a;<br />::max(p1,p2); // max() for two pointers<br />char const* s1 = "David";<br />char const* s2 = "Nico";<br />::max(s1,s2); // max() for two C-strings</p><p>char* a1 = "CY";<br />char* a2 = "HY";<br />::max(a1,a2);<br />return 0;<br />}
這是一個完整的運行在VS2005下的例子。這裡的不同就是模板函數有了不同的重載的版本,這裡在執行個體化之前,就需要為不同的類型找到一個最合適的函數調用,比如說調用max(a,b);的時候,a,b都是int,編譯器發現第一個max函數,也就是以value作為參數的版本最為匹配,所以會就此執行個體化出一個第一個函數的int版本,注意這裡這種匹配的流程被稱之為演繹(Deduction)。而演繹的基本的原則就是首先匹配並存的非模板函數,如果不成功就尋找最接近的模板函數。比如max(s1,s2);的時候,匹配到的就是const char*的那個版本的max函數,而max(a1, a2);的時候就是第二個max函數,也就是以指標作為型別參數的版本。
到目前為止還是一派鳥語花香,聰明的編譯器做到我們希望的一切,max的表現和預想的出奇的一致。當然不得不提的一點就是平靜之下的驚濤暗湧。且看下面的例子:
#include <iostream><br />#include <cstring><br />#include <string><br />// maximum of two values of any type (call-by-reference)<br />template <typename T><br />inline T const& max (T const& a, T const& b)<br />{<br /> return a < b ? b : a;<br />}<br />// maximum of two C-strings (call-by-value)<br />inline char const* max (char const* a, char const* b)<br />{<br /> return std::strcmp(a,b) < 0 ? b : a;<br />}<br />// maximum of three values of any type (call-by-reference)<br />template <typename T><br />inline T const& max (T const& a, T const& b, T const& c)<br />{<br /> return max (max(a,b), c); // error, if max(a,b) uses call-by-value<br />}<br />int main ()<br />{<br /> ::max(7, 42, 68); // OK<br /> const char* s1 = "frederic";<br /> const char* s2 = "anica";<br /> const char* s3 = "lucas";<br /> ::max(s1, s2, s3); // ERROR<br />}
可以看到如果這些模板參數類型不符的話,帶來的問題有多大,可以看到max(7,42,68);的時候沒有問題,3個參數的max函數匹配到第一個max函數,這裡沒有問題,大家都是傳引用的語義。但是到max(s1,s2,s3);的時候,那個max(max(a,b),c);匹配到了const char*的max版本,這樣,max的傳回值就從引用改成了棧上值。儘管這裡的comment寫了ERROR,但實際上VS2005中只會報一個returning address of local variable or temporary的warning!那些以error為判斷條件的筒子們應該注意了,其實這樣的warning也會是致命的。所以聰明的編譯器帶給我們的並不是總是放心的。