C++之函數/結構體/類 模板
1. 模板的作用
模板是通用語言的特性,模板又叫參數化類型(parameterized types)。利用模板機制可以顯著減少冗餘信息,能大幅度地節約程序代碼,進一步提高面向對象程序的可重用性和維護性。
模板(Templates)使得我們可以生成通用的函數,這些函數能夠接受任意數據類型的參數,可返回任意類型的值,而不需要對所有可能的數據類型進行函數重載。這在一定程度上實現了宏(macro)的作用。它們的原型定義可以是下面兩種中的任何一個:
template <class T> function_declaration;template <typename T> function_declaration;
函數模板舉例:
int sum(int a,int b){return a+b;}double sum(double a,double b){return a+b;}//避免上面重複的操作,使用函數模板進行函數聲明。template <typename T>T sum(T a,T b){return a+b;}
在函數模板聲明第一行中,生成了一個通用數據類型T,在後面的函數中,T就成為了一個有效的數據類型,它被用來定義了兩個參數和函數返回值的類型。但是,此時T不代表任何的具體的數據類型。
當函數sum被調用的時候,我們可以使用任何有效的數據類型來調用它。實參的數據類型將作為pattern來代替函數模板中所有出現過的T。調用模板函數的方法是:function <type> (parameters); 例如,要調用sum函數求兩個int型數據的和:
int a, b, all;double e, f, g;all = sum<int>(a, b);g = sum<double>(e, f);
函數模板只是聲明了一個函數的描述即模板,不是一個可以直接執行的函數,只有根據實際情況用實參的數據類型代替類型參數標識符之後,才能產生真正的函數,也即模板函數。
函數模板的數據類型參數標識符實際上是一個類型形參,在使用函數模板時,要將這個形參實例化為確定的數據類型。將類型形參實例化的參數稱為模板實參,用模板實參實例化的函數稱為模板函數。模板函數的生成就是將函數模板的類型形參實例化的過程。
2. 函數/結構體/類模板的定義樣例
//函數模板---使用體現:調用函數時傳遞的參數類型。template<class T><返回類型>func(參數表){ //函數體}//結構體模板---使用體現:聲明結構元素時 StackNode<類型> s;template<class T>struct StackNode{ struct T data; struct StackNode<T> *next;};//類模板---使用體現:聲明類對象時 Stack<類型> s;template<class T>class Stack{ public: T pop(); bool push(T e); private: StackNode<T> *p;}template<class T> //類模板外的成員函數實現T Stack<T>::pop(){...}
其中,
- template是定義模板函數的關鍵字;template後面的尖括弧<>不能省略;
- template<class T>後面沒有分號;
- class(或typename)是聲明數據類型參數標識符T的關鍵字。這樣,在後面的定義中,凡希望根據實參數據類型來確定數據類型的變數,都可以用T來說明,從而使這個變數可以適應不同的數據類型。
- 使用模板來定義(實例化)結構體/類對象的方法是:Stack<int> a; Stack<char> b; 相比不使用模板定義結構體/類對象的形式,多了<實際類型>,且必須用實際類型名去取代虛擬的類型T。
- 調用函數模板方法1:顯式生成模板函數:function <int> (6);
- 調用函數模板方法2:隱式生成模板函數:function(6),由編譯器進行模板參數推導。
- 類模板的成員函數可以在類模板內部或外部定義。但如果在類模板外定義,不能用一般定義類成員函數的形式,而應該寫成類模板的形式。上例中不能寫成 T Stack::pop() {}。
- 函數/結構體/類模板允許使用多個類型參數,但在template定義部分的每個類型參數前都必須有關鍵字typename或class,即:template<class T1,…,class Tn>
- 在template語句與函數/結構體/類的模板定義語句之間不允許有別的語句。
- 模板函數類似於重載函數,但兩者有很大區別:函數重載時,每個函數體內可以執行不同的動作,但同一個函數模板實例化後的模板函數都必須執行相同的動作。
- 雖然函數模板中的類型參數T可以實例化為各種類型,但是採用類型參數T的每個參數必須實例化成完全相同的類型。模板類型不具有隱式的類型轉換。
使用形式:
template <class T> // 最常用的:一個class 參數。template <class T, class U> // 兩個class 參數。template <class T, int N> // 一個class 和一個整數。template <class T = char> // 類型參數有默認值。template <int Tfunc (int)> // 參數為一個函數。
示例:
//函數模板template <typename T>T sum(T a,T b){ return a+b;}//結構體模板template <class T>struct node{ T data; struct node<T> *lchild; struct node<T> *rchild;};//類模板template <class T>class bin{private: node <T> h; //定義結構體模板對象hpublic: T data; bin(T a) {data = a;} void pri();};//類外成員函數的實現template <class T>void bin<T>::pri(){ node<T> *p = new node; cout<<"something."<<endl;}int main(int argc, char** argv){ bin<int> b1(12); //定義類模板的對象b1,b2 bin<int> b2(24); int sum_1=0; sum_1 = sum<int>(b1.data, b2.data); //調用函數模板}
3. 函數/結構體/類模板 vs 模板函數/結構體/類
- 類模板,描述重點是模板,表示的是一個模板;
- 模板類,描述重點是類,表示的是由一個模板生成的類。
對函數、結構體,有與類一致的解釋。
4. 類模板成員函數的聲明與定義要放在.h文件中
在定義模板的頭文件.h時,模板的成員函數實現也必須寫在頭文件.h中,而不能像普通的類(class)那樣,class的聲明(declaration)寫在.h文件中,class的定義(definition)寫在.cpp文件中。
具體說明: http://www.parashift.com/c++-faq-lite/containers-and-templates.html#faq-34.12
關鍵的段落摘錄如下:
In order for the compiler to generate the code, it must see both the template definition (not just declaration) and the specific types/whatever used to "fill in " the template. For example, if you re trying to use a Foo <int> , the compiler must see both the Foo template and the fact that you re trying to make a specific Foo <int> .
Suppose you have a template Foo defined like this:
template <class T> class Foo { public: Foo(); void someMethod(T x); private: T x; };
Along with similar definitions for the member functions:
template <class T> Foo <T> ::Foo() { ... } template <class T> void Foo <T> ::someMethod(T x) { ... }
Now suppose you have some code in file Bar.cpp that uses Foo <int> :
// Bar.cpp void blah_blah_blah() { ... Foo <int> f; f.someMethod(5); ... }
Clearly somebody somewhere is going to have to use the "pattern " for the constructor definition and for the someMethod() definition and instantiate those when T is actually int. But if you had put the definition of the constructor and someMethod() into file Foo.cpp, the compiler would see the template code when it compiled Foo.cpp and it would see Foo <int> when it compiled Bar.cpp, but there would never be a time when it saw both the template code and Foo <int> . So by rule above, it could never generate the code for Foo <int> ::someMethod().
5. 帶有預設參數的類模板的使用
template <typename T = int> class Array { ...};
認為類型參數有預設值,就可以這樣用:
Array books; //我認為有預設模板參數,這就相當於Array <int> books
但是,上面的用法是錯誤的,編譯不會通過,原因是Array不是一個類,只是一個類模板。正確的用法是:
Array <> books;
這裡Array <> 就是一個用預設模板參數的類模板所生成的一個具體類。
6. template<typename T> 和 template<class T>區別
C++ 標準:
- template<typename T> 用於基礎數據類型, T可以是int char 等
- template<class T> 用於複製數據類型,T :string ,類等
實際情況:二者都可以用於各種數據類型。
Bjarne.Stroustrup說:
The typename keyword can also be used as an alternative to class in template declarations. For example:
template <typename T > void f (T );Being an indifferent typist and always short of screen space, I prefer the shorter:template <class T > void f (T );
在聲明一個 template type parameter(模板類型參數)的時候,class 和 typename 意味著完全相同的東西。
- 一些程序員更喜歡在所有的時間都用 class,因為它更容易輸入。
- 有人更喜歡 typename,因為它暗示著這個參數不必要是一個 class type(類類型)。
- 少數開發者在任何類型都被允許的時候使用 typename,而把 class 保留給僅接受 user-defined types(用戶定義類型)的場合。
但是從 C++ 的觀點看,class 和 typename 在聲明一個 template parameter(模板參數)時意味著完全相同的東西。
然而,C++ 並不總是把 class 和 typename 視為等同的東西。有時你必須使用 typename。
具體以後補充。
帳號登錄
參考:
帳號登錄帳號登錄帳號登錄推薦閱讀:
※如何進行系統性的編程學習?
※使用互動式 shell 來增強你的 Python
※理論上最好的編程語言: 哲學基礎篇
※入坑Go語言(一)—— 基礎語法
※如果每一種編程語言都是一位動漫美女,你會為誰打Call?