[C++ Template]基礎--函式模板
2 函式模板
函式模板是那些被引數化的函式, 它們代表的是一個函式家族。
2.1 初探函式模板
如下就是一個返回兩個值中最大值的函式模板:
template <typename T>
inline T const& max(T const& a, T const& b)
{
// 如果a < b, 那麼返回b, 否則返回a
return a < b ? b : a;
}
使用此模板:
int main() { int i = 42; std::cout << "max(7, i) :" << ::max(7, i) << std::endl; double f1 = 3.4; double f2 = -6.7; std::cout << "max(f1, f2) :" << ::max(f1, f2) << std::endl; std::string s1 = "mathematics"; std::string s2 = "math"; std::cout << "max(s1, s2) :" << ::max(s1, s2) << std::endl; }
max()模板每次呼叫的前面都有域限定符 :: ,這是為了確認我們呼叫的是全域性名字空間中的max()。因為標準庫也有一個
std::max()模板。
針對 3 種類型中的每一種,max()都被編譯了一次。如果試圖基於一個不支援模板內部所使用操作的型別例項化一個模
板, 那麼將會導致一個編譯期錯誤,例如:
std::complex<float> c1, c2; //std::complex並不支援 operator <
//…
max(c1, c2); //編譯器錯誤
於是, 我們可以得出一個結論:
模板被編譯了兩次, 分別發生在:
1,例項化之前, 先檢查模板程式碼本身, 檢視語法是否正確; 在這裡會發現錯誤的語法, 如遺漏分號等。
2,在例項化期間, 檢查模板程式碼, 檢視是否所有的呼叫都有效。 在這裡會發現無效的呼叫, 如該例項化型別不支援某些函式呼叫等。
2.2 實參的演繹
我們為某些實參呼叫一個諸如 max()的模板時,模板引數可以由我們所傳遞的實參來決定。
如果我們傳遞了兩個int給引數型別T const&, 那麼C++編譯器能夠得出結論: T必須是int。 注意, 這裡不允許進行自動型別轉換; 每個T都必須正確地匹配。例如:
max(4, 7) //OK: 兩個實參的型別都是int
max(4, 4.2) //ERROR:第1個T是int, 而第2個T是double
有3種方法可以用來處理上面這個錯誤:
1,對實參進行強制型別轉換, 使它們可以互相匹配
max(static_cast<double>(4), 4.2) //OK
,2,顯式指定(或者限定) T的型別:
max<double>(4, 4.2) //OK
3,指定兩個引數可以具有不同的型別。(見2.3小節)
2.3 模板引數
函式模板有兩種型別的引數。
1,模板引數: 位於函式模板名稱的前面, 在一對尖括號內部進行宣告:
template <typename T> //T是模板引數
2,呼叫引數:位於函式模板名稱之後,在一對圓括號內部進行宣告:
//…
max(T const& a, T const& b) //a和b都是呼叫引數
你可以根據需要宣告任意數量的模板引數。然而,在函式模板內部(這一點和類模板有區別),不能指定預設的模板實參。
template <typename T1, typename T2>
inline T1 max(T1 const& a, T2 const& b)
{
//...
}
max(4, 4.2) //OK 但第1個模板實參的型別定義了返回型別
這看起來是一種能夠給 max()模板傳遞兩個不同型別呼叫引數的好方法, 但在這個例子中, 這種方法是有缺點的:
1,必須宣告返回型別
對於返回型別, 如果你使用的是其中的一個引數型別, 那麼另一個引數的實參就可能要轉型為返回型別, 而不會在意呼叫者的意圖。
2,把第2個引數轉型為返回型別的過程將會建立一個新的區域性臨時物件,這導致了你不能通過引用來返回結果。
因此, 在我們的例子裡, 返回型別必須是T1, 而不能是T1 const&。
針對返回型別的問題,兩個簡單的解決方案是:
1,針對某些特定的型別,你還可以顯式地例項化該模板:
max<double>(4, 4.2) //用double來例項化T 返回型別為double ok
2,引入第3個模板實參型別, 來定義函式模板的返回型別:
template <typename T1, typename T2, typename RT>
inline RT max (T1 const& a, T2 const& b);
然而, 模板實參演繹並不適合返回型別,因為RT不會出現在函式呼叫引數的型別裡面。 因此,函式呼叫並不能演繹出RT。 於是, 你必須顯式地指定模板實參列表。 例如:
template <typename T1, typename T2, typename RT>
inline RT max(T1 const& a, T2 const& b);
//…
max<int, double, double>(4, 4.2) //OK, 但是很麻煩
到目前為止, 我們只是考察了顯式指定所有函式模板實參的例子,和不顯式指定函式任何模板實參的例子。 另一種情況是只顯式指定第一個實參, 而讓演繹過程推匯出其餘的實參。 通常而言, 你必須指定“最後一個不能被隱式演繹的模板實參之前的”所有實參型別。 因此, 在上面的例子裡, 如果你改變模板引數的宣告順序, 那麼呼叫者就只需要指定返回型別:
template <typename RT, typename T1, typename T2>
inline RT max(T1 const& a, T2 const& b);
//…
max<double>(4, 4.2) //OK: 返回型別是double
在這個例子裡, 呼叫max<double>時顯式地把RT指定為double, 但其他兩個引數T1和T2可以根據呼叫實參分別演繹為int和double。
2.4 過載函式模板
下面的簡短程式敘述瞭如何過載一個函式模板:
//求兩個int值的最大值
inline int const& max(int const& a, int const& b)
{
return a < b ? b : a;
}
//求兩個任意型別值中的最大者
template <typename T>
inline T const& max(T const& a, T const& b)
{
return a < b ? b : a;
}
//求3個任意型別值中的最大者
template <typename T>
inline T const& max(T const& a, T const& b, T const& c)
{
return ::max(::max(a, b), c);
}
int main()
{
::max(7, 42, 68); // 呼叫具有3個引數的模板
::max(7.0, 42.0); // 呼叫max<double> (通過實參演繹)
::max('a', 'b'); // 呼叫max<char> (通過實參演繹)
::max(7, 42); // 呼叫int過載的非模板函式
::max<>(7, 42); // 呼叫 max<int> (通過實參演繹)
::max<double>(7, 42); //呼叫max<double> (沒有實參演繹)
::max('a', 42.7); //呼叫int過載的非模板函式
}
因為模板是不允許自動型別轉化的; 但普通函式可以進行自動型別轉換, 所以最後一個呼叫將使用非模板函式(‘a’和42.7都被轉化為int):
max('a',42.7) //對於不同型別的引數, 只允許使用非模板函式
下面這個更有用的例子將會為指標和普通的C字串過載這個求最大值的模板:
// 求兩個任意型別值的最大者
template <typename T>
inline T const& max(T const& a, T const& b)
{
return a < b ? b : a;
}
//求兩個指標所指向值的最大者
template <typename T>
inline T* const& max(T* const& a, T* const& b)
{
return *a < *b ? b : a;
}
//求兩個C字串的最大者
inline char const* const& max(char const* const& a,
char const* const& b)
{
return std::strcmp(a, b) < 0 ? b : a;
}
int main()
{
int a = 7;
int b = 42;
::max(a, b); // max() 求兩個int值的最大值
std::string s = "hey";
std::string t = "you";
::max(s, t); // max() 求兩個std:string型別的最大值
int* p1 = &b;
int* p2 = &a;
::max(p1, p2); // max() 求兩個指標所指向值的最大者
char const* s1 = "David";
char const* s2 = "Nico";
::max(s1, s2); // max() 求兩個c字串的最大值
}
一般而言, 在過載函式模板的時候, 最好只是改變那些需要改變的內容; 就是說, 你應該把你的改變限制在下面兩種情況: 改變引數的數目或者顯式地指定模板引數。
例如,對於原來使用傳引用的max()模板, 你用C-string型別進行過載; 但對於現在(即過載版本的) 基於C-strings的max()函式, 你是通過傳值來傳遞引數; 那麼你就不能使用3個引數的max()版本, 來對3個C-string求最大值:
// 兩個任意型別值的最大者 (通過傳引用進行呼叫)
template <typename T>
inline T const& max(T const& a, T const& b)
{
return a < b ? b : a;
}
//兩個C字串的最大者(通過傳值進行呼叫)
inline char const* max(char const* a, char const* b)
{
return std::strcmp(a, b) < 0 ? b : a;
}
//求3個任意型別值的最大者(通過傳引用進行呼叫)
template <typename T>
inline T const& max(T const& a, T const& b, T const& c)
{
return max(max(a, b), c); //注意: 如果max(a,b)使用傳值呼叫
//那麼將會發生錯誤
}
int main()
{
::max(7, 42, 68); // OK
const char* s1 = "frederic";
const char* s2 = "anica";
const char* s3 = "lucas";
::max(s1, s2, s3); // 錯誤
}
問題在於: 如果你對3個C-strings呼叫max(), 那麼語句:
return max (max(a,b),c);
將會產生一個錯誤。
這是因為對於C-strings而言, 這裡的max(a,b)產生了一個新的臨時區域性值, 該值有可能會被外面的max函式以傳引用
的方式返回, 而這將導致傳回無效的引用。
2.5 小結
•模板函式為不同的模板實參定義了一個函式家族。
•當你傳遞模板實參的時候, 可以根據實參的型別來對函式模板進行例項化。
•你可以顯式指定模板引數。
•你可以過載函式模板。
•當過載函式模板的時候, 把你的改變限制在: 顯式地指定模板引數。
•一定要讓函式模板的所有過載版本的宣告都位於它們被呼叫的位置之前。