話題從重用開始說起:
最基本的重用,重用一個方法,被重用的邏輯被抽取封裝成為方法,之後我們把方法當成一種工具來使用(處理數據,輸入輸出,或者改變狀態)。
來到了面向對象的時代,如果這個方法出現父類上面就是繼承,如果這個方法出現在其他對象上就是代理,如果子類想要重用父類的接口又不想重用實現那麼就是多態。
但是這些重用都是基於相同的數據類型,方法創建出來後接收參數都是固定的類型,對於多態,可以通過子類來實現不同行為,但是方法總歸還是接收一個固定父類型(或者接口)參數。
想想有沒有這樣的需求,兩個完全不同的類型,他們之間不存在繼承關系,但是卻需要同樣的處理邏輯,比如說各種類型都需要排序,比如說各種類型都有集合處理的需求。
那麼能不能讓方法接收不同類型來實現重用呢?C++的Template就是解決這樣問題。
模板方法
既然這樣需求不同類型卻需要相同處理方法,所以有了模板方法,
template<typename T> T Add(T a, T b) { return a + b; }; int main() { string s1 = "Hello"; string s2 = "World"; cout << Add(12,13) << endl; cout << Add(s1,s2) << endl;
return 0;
}; 輸出: 25 HelloWorld
模板類
方法都能模板化,那麼類怎麼能夠不模板化, 作為面向對象的C++,所以有了模板類
把這些模板方法組合起來,再加上模板成員就形成了一個模板類,這些概念和行為和一般的類是一樣的。
template<typename T> class Calculator { public: T m_variable; virtual T Add(T a, T b) { return a + b; }; T Minus(T a, T b) { return a - b; }; }; int main() { Calculator<int> c; cout << c.Add(2,3) << endl; cout << c.Minus(9,5) << endl; string s1 = "Hello"; string s2 = "World"; Calculator<string> d; cout << d.Add(s1,s2) << endl; //cout << d.Minus(s1,s2) << endl;
return 0
}
想想為什麼最後那句注釋可以編譯過,但是打開那句就編譯不過了。
我的理解,當模板類實例化的過程,如果沒有用到的方法不會被加入被實例化的模板類中,除非你顯示調用了模板類的方法。
模板類繼承
既然是類,當然不能少了繼承,模板類的繼承可以分為
直接從模板類繼承
template<typename T> class SuperCalculator : public Calculator<T> { public: T m_variable; virtual T Add(T a, T b)//多態 { return a + b + b; }; T Multi(T a, T b)//子類 { return a * b; }; }; int main() { SuperCalculator<int> sc; cout << sc.Add(2,3) << endl; cout << sc.Minus(9,5) << endl; cout << sc.Multi(9,5) << endl; return 0; }; 輸出 8,4,45
從具體類繼承
class SuperIntCalculator: public Calculator<int> { public: virtual int Add(int a, int b) { return a + b + a; }; int Multi(int a, int b) { return a * b; }; }; int main() { SuperIntCalculator sc; cout << sc.Add(2,3) << endl; cout << sc.Minus(9,5) << endl; cout << sc.Multi(9,5) << endl; return 0; }; 輸出 7, 4, 45
模板特例化(偏特化)
模板類可以通過繼承可以在垂直方向變化,但是類型本身也是一個水平的維度,C++為這個維度提供了變化,對於某種具體類的模板類可以擁有特殊的行為,因此我們成為特例化。
template<typename T> class TClass { public: void PrintInfo() { printf("Hello common\n"); }; }; template<> class TClass<int>//特例化 { public: void PrintInfo() { printf("Hello int\n"); }; }; template<typename T> TClass<T> * GetTClassObject(T a) { return new TClass<T>; }; int main() { GetTClassObject("Hello")->PrintInfo(); GetTClassObject(4.5)->PrintInfo(); GetTClassObject(5)->PrintInfo(); return 0; }; 輸出 Hello Common Hello Common Hello Int
如果一個模板類需要接受兩個或者兩個以上的類型來實現具體類,當其中一個類型是某個具體類的時候有特殊的行為,那麼就成為偏特化。
繼承是垂直方向上的特例化,但是特例化不是傳統面向對象體系中的概念,可以類比但不要混淆,特例化是是另外一個維度(水平),因此特例化不僅僅可以作用在類上,也能作用在模板方法上。
比較
>>模板和模板模式(Template Method Design Pattern)
這個設計模式也是來解決不同類型的卻有相同的處理邏輯,這個思想是一致的。但是實現卻不同,模板模式由子類去實現模板方法的每一個步驟(或者某個步驟),而且這個模式的假設是所有的類型都是來自於相同的基類,沒有解決我們最開始非固定類型。C++模板實現邏輯在類型外面(怎麼感覺又有點像策略模式,但是策略模式需要相同的接口或者基類),而類型本身可以針對不同基類的類型。其實對於模板類(方法)而言,他們雖然沒有共同的基類,但是再仔細想想,要在一個模板類中類型需要有一個抽象共性,但是這個共性不是以基類形式來表達,比如說排序,那麼輸入類型必須都要能比較大小,比如說集合,那麼輸入類型都要有“一個一個”的概念(好像不好理解,vector能處理流體問題嗎?)
>>模板和宏
這兩個東西很容易放在一起說,比如一個簡單模板類可以通過宏來實現沒有問題。
但是兩者是從不同角度來解決重用問題的,模板是為不同類型提供相同邏輯的重用,是站在類型的角度上看問題。宏是為代碼級別的重用,站在少寫代碼的角度來看問題。
所以他們有重疊的部分。也提供對方不能提供的功能