函數模板(四十七)
#include <iostream> #include <string> using namespace std; #define SWAP(t, a, b) do { t c = a; a = b; b = c; }while(0) void Swap(int& a, int& b) { int c = a; a = b; b = c; } void Swap(double& a, double& b) { double c = a; a = b; b = c; } void Swap(string& a, string& b) { string c = a; a = b; b = c; } int main() { int a = 0; int b = 1; SWAP(int, a, b); cout << "a = " << a << endl; cout << "b = " << b << endl; cout << endl; double m = 2; double n = 3; Swap(m, n); cout << "m = " << m << endl; cout << "n = " << n << endl; cout << endl; string c = "hello"; string d = "world"; Swap(c, d); cout << "c = " << c << endl; cout << "d = " << d << endl; cout << endl; Swap(a, b); cout << "a = " << a << endl; cout << "b = " << b << endl; return 0; }
我們來看看編譯結果
我們看到已經成功實現交換的功能了。宏定義代碼塊的優點是代碼可以復用,適合所有的類型,缺點是編譯器不知道宏的存在,並不會去進行類型檢查。定義函數的優點是編譯器會去對類型進行檢查,而缺點是根據類型需要重復定義函數,無法進行代碼復用。那麽在 C++ 中有沒有解決方案集合兩種方法的優點呢?就是泛型編程。泛型編程是指不考慮具體數據類型的編程方式,對於上面 Sawp 函數可以考慮下面的泛型寫法
void Sawp(T& a, T& b) { T t = a; a = b; b = t; }
Swap 泛型寫法中的 T 不是一個具體的數據類型,而是泛指任意的數據類型。C++ 中泛型編程便是指函數模板了。一種特殊的函數可用不同類型進行調用,看起來和普通函數很相似,區別是類型可被參數化
template <typename T> void Swap(T& a, T& b) { T t = a; a = b; b = t; }
函數模板的語法規則是:template 關鍵字用於聲明開始進行泛型編程,typename 關鍵字用於聲明泛指類型,如下
函數模板的使用:自動類型推導調用和具體類型顯示調用,如下
int a = 0; int b = 1; Swap(a, b) // 自動推導 float c = 2; float d = 3; Swap<float>(c, d); // 顯示調用
下來我們還是以代碼為例來進行分析
#include <iostream> #include <string> using namespace std; template < typename T > void Swap(T& a, T& b) { T c = a; a = b; b = c; } template < typename T > void Sort(T a[], int len) { for(int i=0; i<len; i++) { for(int j=i; j<len; j++) { if( a[i] > a[j] ) Swap(a[i], a[j]); } } } template < typename T > void Println(T a[], int len) { for(int i=0; i<len; i++) { cout << a[i] << ", "; } cout << endl; } int main() { int a[5] = {5, 2, 4, 1, 3}; Println(a, 5); Sort(a, 5); Println(a, 5); cout << endl; string s[5] = {"Java", "C++", "Pascal", "Ruby", "Basic"}; Println(s, 5); Sort(s, 5); Println(s, 5); return 0; }
我們看看能否實現數組和字符串的排序
這個函數模板已經實現了我們所想要的功能了。我們有必要再來深入的看下這個函數模板都做了什麽?編譯器從函數模板通過具體類型產生不同的函數,編譯器會對函數模板進行兩次編譯,第一次是對模板本身進行編譯,第二次是對參數替換後的代碼進行編譯。請註意哈,函數模板本身不允許隱式類型轉換,即自動推導類型時,必須進行嚴格匹配;顯示類型指定時,能夠進行隱式類型轉換。
下來我們還是以代碼為例來進行分析說明
#include <iostream> #include <string> using namespace std; class Test { public: Test() { } }; template < typename T > void Swap(T& a, T& b) { T c = a; a = b; b = c; } typedef void(FuncI)(int&, int&); typedef void(FuncD)(double&, double&); typedef void(FuncT)(Test&, Test&); int main() { FuncI* pi = Swap; // 編譯器自動推導 T 為 int FuncD* pd = Swap; // 編譯器自動推導 T 為 double FuncT* pt = Swap; // 編譯器自動推導 T 為 Test cout << "pi = " << reinterpret_cast<void*>(pi) << endl; cout << "pd = " << reinterpret_cast<void*>(pd) << endl; cout << "pt = " << reinterpret_cast<void*>(pt) << endl; return 0; }
我們看看是不是如我們在註釋中所寫的一樣,如果產生具體函數的話,那麽它打印的三個地址就會是不同的。我們來看看編譯結果
我們看到編譯器確實在編譯函數模板的時候產生具體的函數了,類的對象都可以作為參數。下來我們看看編譯器是不是會對函數模板進行二次編譯,我們在 Test 類中定義一個私有成員函數,拷貝構造函數。如果進行二次編譯的話,肯定會報錯,因為在 Swap 函數中會有臨時對象的產生,而這塊是私有的。我們來看看編譯結果
我們看到確實是報錯了,確實是如我們所分析的那樣。函數模板還可以定義任意多個不同的類型參數。如下
那麽對於多參數函數模板,它無法自動推導返回值類型,可以從左向右部分指定類型參數。在工程中將返回值參數作為第一個類型參數!所以第一個參數必須指定,下來我們還是以代碼為例來進行分析
#include <iostream> #include <string> using namespace std; template < typename T1, typename T2, typename T3 > T1 Add(T2 a, T3 b) { return static_cast<T1>(a + b); } int main() { // T1 = int, T2 = double, T3 = double int r1 = Add<int>(0.5, 0.8); // T1 = double, T2 = float, T3 = double double r2 = Add<double, float>(0.5, 0.8); // T1 = float, T2 = float, T3 = float float r3 = Add<float, float, float>(0.5, 0.8); cout << "r1 = " << r1 << endl; // r1 = 1 cout << "r2 = " << r2 << endl; // r2 = 1.3 cout << "r3 = " << r3 << endl; // r3 = 1.3 return 0; }
我們來看看編譯結果是不是這樣的
當函數重載遇見函數模板會發生什麽呢?函數模板可以像普通函數一樣被重載,C++ 編譯器優先考慮普通函數。如果函數模板擴產生一個更好的匹配,那麽選擇模板;可以通過空模板實參列表限定編譯器只匹配模板。如下
我們還是以代碼為例進行分析
#include <iostream> #include <string> using namespace std; template < typename T > T Max(T a, T b) { cout << "T Max(T a, T b)" << endl; return a > b ? a : b; } int Max(int a, int b) { cout << "int Max(int a, int b)" << endl; return a > b ? a : b; } template < typename T > T Max(T a, T b, T c) { cout << "T Max(T a, T b, T c)" << endl; return Max(Max(a, b), c); } int main() { int a = 1; int b = 2; cout << Max(a, b) << endl; // 普通函數 Max(int, int) cout << Max<>(a, b) << endl; // 函數模板 Max(int, int) cout << Max(3.0, 4.0) << endl; // 函數模板 Max(double, double) cout << Max(5.0, 6.0, 7.0) << endl; // 函數模板 Max(double, double, double) cout << Max('a', 100) << endl; // 普通函數 Max(int, int) return 0; }
我們看下這個程序,第一個顯然調用的是普通函數,因為編譯器會優先考慮普通函數;第二個顯然是通過空模板是來實現的,因此它調用的是函數模板;第三個是 double 類型的,因此它會調用函數模板;第四個有三個參數,因此我們肯定它調用的是函數模板;最後一個是兩個不同類型的參數,因為 char 會隱式轉換為 int 類型,所以它會調用普通函數。我們下來看看編譯結果
我們看到我們分析的完全是正確的。通過對函數模板的學習,總結如下:1、函數模板是泛型編程在 C++ 中的應用方式之一;2、函數模板能夠根據實參對參數類型進行推導,它支持顯示的指定參數類型;3、函數模板是 C++ 中重要的代碼復用方式;4、函數模板通過具體類型產生不同的參數,並且它可以定義任意多個不同的類型參數;5、函數模板中的返回值類型必須顯示指定;6、函數模板可以像普通函數一樣被重載。
歡迎大家一起來學習 C++ 語言,可以加我QQ:243343083。
函數模板(四十七)