什麼是模板?
模板(template)指c++中的函數模板與類模板,大體對應於C#和Java眾的泛型的概念。目前,模板已經成為C++的泛型編程中不可缺少的一部分。
模板定義以關鍵字template開始,後接模板形參表,模板形參表是用尖括號括住的一個或者多個模板形參的列表,形參之間以逗號分隔。 模板形參可以是表示類型的類型形參,也可以是表示常量表達式的非類型形參。非類型形參跟在類型說明符之後聲明。類型形參跟在關鍵字class或typename之後定義(至於class與typename的區別實際並不大,c++的早期版本中只有class,沒有typename。在絕大多數場景下兩者是通用的,只有少數特殊情況下必須使用typename。總之,使用typename是萬無一失的。兩者的區別可以參考這篇文章)。
模板是C++程序員絕佳的武器, 特別是結合了多重繼承(multiple inheritance)與運算符重載(operator overloading)之後。C++ 的標准庫提供許多有用的函數大多結合了模板的觀念,如STL以及IO Stream。
函數模板
所謂函數模板,實際上是建立一個通用函數,其函數類型和形參類型不具體指定,用一個虛擬的類型來代表。這個通用函數就稱為函數模板。凡是函數體相同的函數都可以用這個模板來代替,不必定義多個函數,只需在模板中定義一次即可。在調用函數時系統會根據實參的類型來取代模板中的虛擬類型,從而實現了不同函數的功能。
網上大多數介紹都是從比較兩個數大小入手的,本文章介紹依然如此,假設有一個需要要比較兩個數的大小,但是這兩個數的類型是不確定的,可能是int、float、double類型的。
當然有一種方式就是可以用函數的重載來實現,但用重載的方式造成的問題是:有多少類型的可能性,就要寫多少個重載函數。假設當前需求裡可能要求只有float和double兩種類型,但有一天增加了對int類型的允許,則要在代碼中增加對int類型參數的重載函數。
這個時候,函數模板就排上用場了。只需要定義一個帶有泛型參數的函數,就可以實現多種類型參數的比較,直接看下面的代碼吧:
1 class MyTemplate 2 { 3 public: 4 MyTemplate(void); 5 ~MyTemplate(void); 6 7 //以關鍵字template開頭 後面接<typename T>或<class T> 返回值類型和參數類型都是T 8 template <typename T> T Max(T a,T b) 9 { 10 return a>=b?a:b; 11 }; 12 };
這裡的T會在程序編譯的時候特化為代碼調用處傳入的實際參數,例如
如果比較兩個int類型的大小:
MyTemplate mytemplate; int x = 10; int y = 100; int val = mytemplate.Max(x,y);
程序裡的T在編譯時就用int替換,替換後的程序應該是下面這個樣子:
template <typename int> int Max(int a,int b) { return a>=b?a:b; };
float和double類型的是同樣的道理,這裡就不重復了。
類模板
當我們有更加復雜的需求的時候,例如要實現一個隊列,這個隊列中可能不止有int類型的數據,還有可能有string類型、double類型、或者更復雜的自定義類型。例如下面這個隊列中存儲了多種數據類型的對象,這個時候就需要定義一個類模板了。
類模板實現的簡單隊列
調用代碼如下:
1 if(valIndexs.empty()) 2 { 3 for (int zi = 10;zi!=15;zi++) 4 { 5 valIndexs.push(zi); 6 } 7 } 8 FZQueue<int> clone_valZindexs(valIndexs); 9 10 cout<<"valIndexs:"<<valIndexs.front()<<"______clone_valZindexs:"<<clone_valZindexs.front()<<endl; 11 12 cout<<"valIndexs:"<<valIndexs.front()<<"______clone_valZindexs:"<<clone_valZindexs.front()<<endl; View Code以上就是用類模板實現簡單隊列的完整代碼。
問題與總結
1.把類中的構造函數重載(FZQueue(const T &t);)和操作符重載(FZQueue& operator=(const FZQueue&);)去掉後都是一樣正常執行,不知道這個構造函數重載和操作符重載在什麼情況下使用。
總結:參考《C++ Primer》第四版第13章 復制控制 裡介紹的復制構造函數一節,對復制構造函數的描述是這樣的:
復制構造函數是一種特殊構造函數,具有單個形參,該形參(常用const修飾)是對該類類型的引用。當定義一個新對象並用一個同類型的對象對它進行初始化時,將顯式使用復制構造函數。當將該類型的對象傳遞給函數或從函數返回該類型的對象時,將隱式使用復制構造函數。可用於: 1.根據另一個同類型的對象顯示或隱式初始化一個對象 2.復制一個對象,將它的作為實參傳遞給一個函數 3.從函數返回時復制一個對象 4.初始化順序容器中的元素 5.根據元素初始化式列表初始化數組元素
並且:如果程序中沒有顯示定義並實現復制構造函數,編譯器會自動生成。賦值操作符重載與析構函數都是如此。
不能將自定義的類聲明為指針形式,例如FZQueue<int> *clone_zindexs,如果這樣做,之後將這個指針當參數調用復制構造函數時,復制構造函數不起作用,因為這裡只是聲明了一個指針而已。