面向對象的多態與組合並不能完全滿足實際編程中對於代碼復用的全部要求,泛型編程應運而生,而且享有和面向對象等同的地位。面向對象將操作綁定到數據,泛型則是將操作應用於不同數據結構和類型。C++中泛型編程的體現就是模版。模板的技術核心體現在編譯期的動態機制,模板實例化則是在編譯的過程中,編譯器通過“類型推導”進行實例化。而運行的時候,系統不知道模板的概念。與之相比,面向對象就是在運行時調用構造函數進行類的對象的實例化。
模版的應用:當一個類或函數的概念適用於不同類或者不同基本數據類型時,應該用模版來實現。C++提倡少用宏,鼓勵使用模板。模板是C++語言內置,而且模板操作的類型在編譯時是已知,是類型安全的。而宏的本質則是純粹的文本替換,編譯器不會驗證宏參數是否為兼容類型,會在不進行任何特殊類型檢查的情況下擴展宏。
首先看一下這篇文章;IBM 編譯器中國開發團隊-究竟什麼是特化?模版的特化,某種程度上有點兒像函數的重載。
對於函數的重載,編譯器根據傳遞給函數的實參類型來決定調用哪個函數,這就是重載解析。在調用前,編譯器有一個候選函數調用列表,每個調用函數都有各自的參數,編譯器根據參數最匹配原則選擇相應的函數 。
對於模版的特化,同樣是對特定類型進行特殊的操作,編譯器來選擇最佳匹配。全特化限定模板實現的具體類型,偏特化只限定模版實現類型中一部分。一般,類模版ji既可以全特華又可以偏特化,而函數模版則只有全特化,函數模版偏特也沒有必要,函數重載即可實現。
#includeusing namespace std; template class Test { public: Test(T1 i, T2 j) : a(i), b(j) { cout << "模板類" << endl; } private: T1 a; T2 b; }; template<> class Test { public: Test(int i, char j) : a(i), b(j) { cout << "全特化" << endl; } private: int a; char b; }; template class Test { public: Test(char i, T2 j) : a(i), b(j) { cout << "偏特化" << endl; } private: char a; T2 b; }; int main() { Test test1(1,'a'); //輸出:模板類 Test test2(1,'a'); //輸出:模版類 Test test3(1,'a'); //輸出:全特化 Test test4('a',1.0); //輸出:偏特化 Test test5('a',1); //輸出:偏特化 }
在決定模板參數類型前,編譯器執行下列隱式類型轉換:左值變換、修飾字轉換、派生類到基類的轉換。實際使用中,參數可以顯示也可以隱式:
顯式類型參數:對於模板函數,在函數名後添加 < {類型參數表} >。對於模板類,在類後添加 < {類型參數表} >;隱式類型參數:對於模板函數,如果類型參數可以推導,那麼可以省略類型參數表,(不過一般還是不要省略為好);函數模版舉例:
templateT min(T x, T y) { return (x < y) ? x : y; } /* 對特定的類型全特化 */ template<> long min (long x, long y) { return (x < y) ? y : x; } int main() { int n1 = 1, n2 = 2; char a = 'a', b = 'b'; long n3 = 1, n4 = 2; std::cout << min (n1, n2) << "\n"; //輸出:1,顯示類型參數,模板的特化 std::cout << min(a, b) << "\n"; //輸出:a,隱式類型參數,編譯器可以自動類型推導 std::cout << min(n3, n4) << "\n"; //輸出:2,隱式類型參數,函數模板的全特化 return 0; } //這個簡單的模板在特化時基本只包含類型的查找與替換,作用類似於“類型安全的宏”。
前面說過,模版的動態機制體現在編譯器,模版實例化也是在編譯器進行類型的確定。所以,只有將模板類.cpp文件同調用程序.cpp文件一起作為一個編譯單元編譯運行,才能真正確定類的真正類型。具體可以有以下兩種做法:
類模版舉例,作容器:
#includeconst int DefaultSize = 10; /*一個簡單的Array容器 */ template class Array { public: Array(int size = DefaultSize); Array(const Array &rhs); ~Array() {delete[] pType;} Array& operator =(const Array&); T& operator[](int offset) { return pType[offset]; } // 運算符重載[] const T& operator[](int offset) const { return pType[offset]; } // 運算符重載[], const版本 int getSize() const { return size; } private: T *pType; int size; }; template Array ::Array(int size):size(size) { pType = new T(size); } /* 用此種類型對象做參數再構造一個此種類型的對象 */ template Array ::Array(const Array &rhs) { size = rhs.getSize(); pType = new T[size]; for (int i = 0; i < size; i++) { pType[i] = rhs[i]; } } /* 重載 = ,形參和返回值都是引用 */ template Array & Array ::operator =(const Array &rhs) { if (this == &rhs) { return *this; } delete[] pType; size = rhs.getSize(); pType = new T(size); return *this; } class Animal { public: Animal(int weight) :weight(weight) {} Animal():weight(0){} ~Animal() {} /* 函數後面有const,表示只讀,內部不能修改成員變量的值;*/ int getWeight() const { return weight; // 這裡如果是weight++,就報錯了 } void display() const { std::cout << weight; } private: int weight; }; int main() { Array integers; Array animals; Animal *pAnimal; for (int i = 0; i < integers.getSize(); i++) { integers[i] = i; pAnimal = new Animal(i * 2); animals[i] = *pAnimal; } for (int i = 0; i < integers.getSize(); i++) { std::cout << "the array[" << i << "]:\t"; std::cout << integers[i] << "\t\t"; std::cout << "animals[" << i << "]:\t"; animals[i].display(); std::cout << std::endl; } return 0; }
輸出:
the array[0]: 0 animals[0]: 0 the array[1]: 1 animals[1]: 2 the array[2]: 2 animals[2]: 4 the array[3]: 3 animals[3]: 6 the array[4]: 4 animals[4]: 8 the array[5]: 5 animals[5]: 10 the array[6]: 6 animals[6]: 12 the array[7]: 7 animals[7]: 14 the array[8]: 8 animals[8]: 16 the array[9]: 9 animals[9]: 18
標准庫經常用到函數對象,也叫仿函數(Functor) 。函數對象就是一個重載了”()”運算符的struct或class,利用對象支持operator()的特性,來達到模擬函數調用效果的技術。函數對象有兩大優勢:
函數對象可以包含狀態;函數對象屬於類型可用作模板參數;函數對象舉例:
#includeclass Add { public: int operator()(int a, int b) { return a + b; } }; int main() { Add add; std::cout << add(2, 3) << "\n"; // 輸出5 }
經過近階段對C++的了解,越來越理解:在C++語言裡,存在這一種很強的趨勢,就是如果你不明白C++語言的細節,你就無法做好任何事情。 – Larry Wall, developer of the Perl