1 函數模版 ios
函數模版:獨立於類型的函數,可做爲一種方式產生函數特定類型版本。數組
格式:template <typename T1, …> + 正常的函數聲明三要素。<>中的爲模形參表,使用逗號分割。數據結構
注:模版形參表不能爲空,爲空爲模版特化形式。框架
示例:函數
template <typename T>性能
int compare(const T &val1, const T &val2)測試
{this
if (v1 < v2)spa
return -1;指針
if (v2 < v1)
reutn 1;
return 0;
}
1.1 函數模版使用
使用函數模版時,編譯器會推斷模版實參的類型(類模版不會推斷),肯定了函數模版就實例化了函數模版的一個實例。即編譯器承擔了爲咱們使用的每種類型編寫函數的工做。
compare(3, 4); //編譯器實例化了 int compare(const int&, const int&);用int代替T
compare(string("hancm"), string("hi")); //編譯器實例化了 int compare(const string&, const string&);用string代替T
1.2 inline 函數模版
inline關鍵字放在template以後,例如
//正確
template<typename T> inline T min(const T&, const T&);
//錯誤
inline template<typename T> T min(const T&, const T&);
2 類模版
一樣以關鍵字template開頭,後接模版形參表,類模版的定義與其它類類似。
類模版的使用:STL中的容器都是使用類模版定義的,能夠做爲使用參考。
下面以自定義一個Queue類做爲實例:
template <typename Type>
class Queue {
public:
//default constructor
Queue();
//operation
void push(const Type&);
void pop();
Type& front();
const Type& front() const;
private:
//...
};
Queue<int> qi; //Type 被替換爲int型,類模版必須指定現實模版參數。
3 模版形參表
模版形參:類型形參,跟在class或typename後面,表明一個未知類型。class與typename沒有區別,只是typename是標準C++組成部分。
非類型形參,跟在類型說明符以後,表明一個未知的常量表達式。
注:模版形參的名稱沒有任何不一樣,就如同函數形參同樣。不一樣之處在於函數形參類型取決於類型說明符,而模版形參取決因而類型形參仍是非類型形參。
模版形參做用域
模版形參的名字:在聲明爲模版形參以後到模版聲明或定義的末尾。遵循常規名字屏蔽規則。
使用模版形參名字的限制:模版形參的名字不能在模版內部重用。同時意味着同一模版形參表中名字只能使用一次。
//錯誤
template <typename T>
class example {
typedef double T; //不容許
};
template <typename T, typename T>... //錯誤
模版聲明
模版能夠只聲明而不定義,可是必須指出函數或類是模版。
//ok: 聲明而不定義
template <typename T> int compare(const T&, const T&);
同一個模版聲明和定義中,模版形參名字能夠不相同。與名字無關只取決於類型: 類型形參 or 非類型形參。
模版中typename或class不能省略:
// error: 必須有class or typename
template <typename T, U>...
3.1模版的類型形參
類型形參:由關鍵字class或typenaem後接說明符構成,模版形參表中兩個關鍵字含義相同,指出後面接的名字表示未知類型。
能夠做爲類型說明符在模版的任何位置,與內置類型說明符或類類型說明符使用方式相同。
注:typename能夠在模版內部指定類型,例如
template <class parm, class T>
parm fcn(parm *arr, T val)
{
typename parm::size_type *p; //指出p爲指向parm內部的size_type的指針。
//若沒有typename, 編譯器默認解釋size_type爲parm中static類型變量,這就變成了一個乘法表達式。
}
提示:在類型前指定typename是一個好方式。
3.2 模版的非類型形參
非類型形參:用值代替,值的類型在模版形參表中指定,例如:
//T (&arr)[N]中arr爲數組的應用,N爲數組的長度,是模版內部的常量。
template <class T, size_t N> void arr_init(T (&arr)[N])
{
for (int i = 0; i != N; ++i) {
arr[i] = i;
}
}
int x[3];
arr_init(x); //arr類型爲int[3], T = int, N = 3; 數組傳遞引用時,檢測數組長度。
4 編寫泛型函數
編寫模版代碼時,對實參類型的要求儘量少是有益的。
重要原則:模版形參是引用形參。
函數體測試只用<比較。減小類型依賴,使模版中的一組有效表達式要求下降。
5 實例化
模版自己不是類或函數。編譯器用模版產生類或函數的特定類型版本。
實例化:產生模版的特定類型實例的過程。
模版在使用時進行實例化:
類模版:引用實際模版類類型時
函數模版:調用函數模版時
對函數指針進行初始化或賦值時
1.類的實例化
當編寫Queue<int> iq;時,編譯器建立Queue<int>的類。編譯器經過用int代替模版形參的每次出現從新編寫Queue模版而建立Queue<int>類。
類模版每次實例化都產生一個獨立的類型,各獨立化的類型之間沒有任何關係,相互之間也沒有特殊的訪問權。
注:類模版形參是必須的,不能省略。例如:Queue不是類型,而Queue<int>是類類型。
2. 函數模版實例化
使用函數模版時,編譯器通常會推斷模版實參。例如:
compare(3.2, 3.4); //編譯器推斷模版實參爲double型
5.1 函數模版實參推斷
模版實參推斷:從函數實參肯定模版實參的類型和值的過程。
1. 多個類型形參的實參必須徹底匹配
template <typename T> int compare(const T&, const T&);
compare(short, int);是錯誤的,實參類型不匹配。推斷出的模版實參必須同一個類型(能進行轉換也不行)。想要這個調用成立,必須定義兩個模版形參:
template <typename T, typename U> int complate(const T&, const U&);
2. 類型形參的實參的受限轉換
通常,不會轉換實參以匹配已有的實例化,相反產生新的實例。
編譯器會執行兩種轉換:
(1) const轉換:接受const引用或const指針的函數,能夠分別用非const對象的應用或指針來調用,不產生新實例。
例如:
template <typename T> T fun(const T&, const T&);
const string s1("hancm");
string s2("hi");
fun(s1, s2); //ok: s2的非const對象轉換爲const的引用
接受非引用類型的函數,形參類型和實參都忽略const,即不管傳遞const或非const對象給接受非引用類型的函數,都使用相同的實例化。
例如:
template <typename T> T fun(T, T);
fun(s1, s2); // ok: s1爲const string, s2爲非const,調用fun(string, string); const被忽略,傳值方式,傳遞一個副本。
(2) 數組或函數到指針的轉換:若模版形參不是引用類型,對數組或函數類型的實參應用常規指針轉換。數組實參轉換爲指向第一個元素的指針,函數實參被看成指向函數類 型的指針。
例如:
template <typename T> T fun(T, T);
int a[4], b[3];
fun(a, b); //ok: calls fun(int*, int*);
template <typename T> T fun&(const T&, const T&);
fun&(a, b); //error: 數組類型不匹配,實參不轉換爲指針,數組引用檢測長度,長度做爲參數類型的一部分。
3. 非模版實參的常規轉換
類型轉換的限制只適用於類型爲模版形參的那些實參。
普通類型定義的形參可使用常規轉換。
template <class Type> Type sum(const Type&, int op2);
sum(189, double); //ok: double轉換爲int,instantiates sum(int, int);
sum(33, string("hancm")); //error: string 不能轉換爲int
4. 模版實參推斷與函數指針
使用函數模版初始化或賦值函數指針,編譯器使用指針的類型實例化具備適當模版實參的模版版本。
template <typename T> int compare(const T&, constructionT&);
//pf指向實例化的 int compare(cosnt int&, cosnt int&);
int (*pf) (const int&, const int&) = compare;
若不能從函數指針類型肯定模版實參,就會出錯。
void fun(int (*) (const string&, const string&));
void fun(int (*) (const int&, cosnt int&));
fun(compare); //error: 不知道實例化哪一個版本
5.2 函數模版的顯示實參
當不能推斷模版實參時,必須覆蓋模版實參推斷機制,顯示指定模版形參的類型或值。
最常出現:函數返回類型與形參表中所用的全部類型都不一樣時。
1. 指定顯示模版實參
考慮:
template <class T, class U> ??? sum(T, U);
sum(3, 4L); //4L更大,want U sum(T, U);
sum(3L, 4); //3L更大,want T sum(T, U);
解決辦法:
sum(static_cast<int>short, int); //返回類型都同樣
2. 在返回類型中使用類型形參
另外一個解決方法:引入第三個模版形參,由調用者顯示指定
template <class T1, class T2, class T3>
T1 sum(T2, T3);
問題:沒有實參的類型用於推斷T1類型
解決方法:調用者顯示提供實參,相似類模版使用
//ok: T1 explicitly specified: T2, T3 inferred from argument types
long val = sum<long>(int, long);
顯示模版實參與模版形參表從左到右相對應。<long>對應T1。
3. 顯示實參與函數模版指針
void fun(int (*) (const string&, const string&));
void fun(int (*) (const int&, cosnt int&));
fun(compare<int>); //ok: 顯示指定int版本
6 模版編譯模型
編譯器看到模版定義時,不當即產生代碼。只有當用到模版時,如調用了函數模版或定義了類模版的對象的時候,編譯器才產生特定類型的模版實例。
調用函數:編譯器要看到函數聲明。
定義類類型對象時:類定義必須可見,成員函數的定義不是必須存在。
結果:類定義和函數聲明放在頭文件中,普通函數和類成員函數定義在源文件中。這是分別編譯模型。
模版不一樣:要進行實例化編譯必須可以訪問定義模版的源文件。當調用函數模版或類模版的成員函數時,編譯器須要函數定義,須要那些一般放在源文件中的代碼。
即,模版的相關定義也放在頭文件中。這是包含編譯模型,全部編譯器都支持。
1. 包含編譯模型
編譯器必須看見全部模版的定義:
//header file Queue.h
#ifndef __QUEUE_H__
#define __QUEUE_H__
template <class T>
class Queue {
...
};
#include "Queue.cc"
#endif
//implemenation file Queue.cc
//成員函數定義,靜態成員的定義
問題:某些包含編譯模型的編譯器(特別是較老的編譯器),能夠產生多個實例。若是多個單獨編譯的源文件使用同一模版,這些編譯器將爲每一個文件中的模版產生一個實例。一般這意味着給定模版實例化超過一次。連接時或預連接階段,編譯器會選擇一個實例而丟棄其它的,若是有許多實例化同一模版的文件,編譯是性能會顯著降低。
解決方法:看看編譯器提供什麼支持以免多餘的實例化,避免同一模版的多個實例化中隱含的編譯時開銷。
2. 分別編譯模型
編譯器爲咱們跟蹤相關的模版定義。使用關鍵字export使編譯器記住給定的模版定義。
export關鍵字指明給定的定義可能須要在其餘文件中產生實例化。一個程序中,一個模版只能定義爲導出一次。export沒必要在模版聲明中出現。
函數模版:在函數模版的定義中template以前包含export關鍵字,指明函數模版爲導出的。
// 在分別編譯源文件的函數模版定義中
export template <typename Type>
Type sum(Type t1, Type t2);
函數模版的聲明放在頭文件中,沒必要聲明爲export。
類模版:類模版聲明放在頭文件中,頭文件的類定義體不該該使用關鍵字export,若用了,該頭文件只能被程序的一個源文件使用。
應該在類的實現文件中使用export
//類模版頭文件Queue.h
template <typename T> class Queue {};
//類模版實現文件Queue.cc
export template <class Type> Queue;
#include "Queue.h"
//Queue成員函數定義
導出類模版的成員自動生明爲導出的。類模版的個別成員能夠聲明爲導出的,此時,export再也不類模版自己指定,在要導出的特定成員定義上指定。導出成員函數的定
義沒必要在使用成員時可見。全部非導出成員的定義必須定義在頭文件中。
7 類模版成員
下面以Queue類模版爲例介紹類模版成員。
注:本Queue以低級數據結構鏈表實現,標準庫默認以deque實現。
1. 模版做用域內引用模版類型
在類模版的做用域內部,能夠用類模版的非限定名。例如:
Queue (const Queue<Type> &q); //Queue的複製構造函數
編譯器推斷:引用類的名字時,引用的是同一個版本。
Queue的複製構造函數等價於:
Queue<Type> (const Queue<Type> &q);
注:編譯器不會爲類中使用的其它模版的模版形參進行這樣的推斷。例如:在夥伴類Queueitem中,必須指定形參類型。
Queueitem<Type> *head;
Queueitem<Type> *tail;
<Type>不能去掉。
2. 類模版成員函數
形式以下:
(1) 以關鍵字template開頭,後接類的模版形參表。
(2) 必須指定是哪一個類的成員。
(3) 類名必須包含去模版形參。
template <class T> return_val Queue<T>::member_function_name;
3. 類模版成員函數的實例化
類模版成員函數自己是函數模版,須要使用類模版的成員函數產生成員的實例化。實例化類模版成員函數時,編譯器不進行模版實參推斷,模版形參由調用該函數的對象肯定。
注:模版形參定義的函數形參的實參容許常規類型轉換。
4. 什麼時候實例化類和成員
類模版的成員函數: 爲程序所用時進行實例化。若成員函數從未使用,則不進行實例化。
定義模版類型對象: 實例化類模版。實例化用於初始化該對象的任一構造函數(此時構造函數被調用),以及構造函數調用的任意成員。
例如:
Queue<string> sq; //實例化類模版,實例化默認構造函數。
sq.push_back("hancm"); //實例化成員函數push_back。
7.1 非類型形參的模版實參
考慮標準庫中的bitset,使用的就是非類型形參。
template <int n> class bitset { };
bitset<10>定義一個10爲的bitset。
注:非類型模版形參: 編譯時常量表達式。
7.2 類模版的友元聲明
三種友元聲明:
(1)1->n: 普通非模版類型或函數的友元聲明,將友元關係授予明確指定的類或函數。
例如:
template <class Type>
class bar {
//授予對普通的非模版類或函數的訪問權
friend class foobar;
freind void fun();
};
(2)n->n: 類模版或函數模版的友元聲明,授予對友元全部實例的訪問權。
例如:
template <calss Type>
class bar {
//授予任意類模版或函數模版訪問權,使用<class T>指明模版形參能夠和<class Type>不一樣。
template <class T> friend class foobar;
template <class T> friend void templ_fun(const T&);
};
(3)1->1: 只授予對類模版或函數模版的特定實例的訪問權的友元聲明。
例如:
template <class T> class foo;
template <class T> void temp_fun(const T&);
template <calss Type>
class bar {
//授予特定的實例訪問權
//前面必須有模版的聲明,不然編譯器會認爲該友元是一個普通非模版或非模版函數。
friend class foo<char*>;
friend void temp_fun<char*>(char* const&);
更常見的: 聲明相同實參的友元。只授予相同類型的模版訪問權。
friend class foo<Type>;
friend void temp_fun<Type>(const Type&);
};
7.3 成員模版
成員模版:類(模版或非模版)的成員,該成員爲類模版或函數模版。
例如:
template <class Type>
class Queue {
public:
//成員模版:自己就是一個模版,只不過是一個類的成員,它的形參類型與所屬類的形參類型無關。
template <class Iter> void assign(Iter, Iter);
};
在類外定義成員模版:
template <class T> //所屬類的模版形參
template <class Iter> //成員模版自己的模版形參
void Queue<T>::assign(Iter beg, Iter end)
{
}
注:成員模版遵循常規訪問控制
實例化:類模版形參由調用函數的對象類型肯定
成員模版的模版形參由模版實參推斷出。
7.4 類模版的static成員
例如:
template <class T>
class foo {
public:
static std::size_t count() { return ctr; }
private:
static std::size_t ctr;
};
1.使用
//ok
foo<int>::ctr;
foo<int>::count();
//error
foo::ctr; //foo是類模版不是類,只有foo<int>這種類才能夠
2.定義static成員
template <class T>
size_t foo<T>::ctr = 0; //與普通類的static相似
8 Queue的完整實現
//Queue.h頭文件
#ifndef __Queue_H__ #define __Queue_H__ #include <iostream> //類模版聲明,定義在Queue.cc文件夾中 template <typename Type> class Queue; //重載輸出操做符,不能爲類的成員函數 template <typename Type> std::ostream& operator<<(std::ostream&, const Queue<Type>&); //夥伴類模版,用於實現底層數據結構,標準庫使用deque實現 template <typename Type> class Queueitem { //須要訪問Queueitem的構造函數,next指針 friend class Queue<Type>; //須要訪問item和next friend std::ostream& operator<< <Type>(std::ostream&, const Queue<Type>&); //private section Queueitem(const Type &t): item(t), next(0) { }; Type item; Queueitem *next; }; template <class Type> class Queue { //Needs access to head //須要訪問Queue的head
//使用與類模版相同類型的實參,前面必須有operator<<的聲明 //不然編譯器會認爲這是一個非模版類型 friend std::ostream& operator<< <Type> (std::ostream&, const Queue<Type>&); public: //默認構造函數 Queue(): head(0), tail(0) { } //使用一對迭代器的構造函數,屬於成員模版 template <class It> Queue(It beg, It end): head(0), tail(0) { copy_elems(beg, end); } //複製構造函數 Queue(const Queue &q): head(0), tail(0) { copy_elems(q); } //賦值操做符 Queue& operator=(const Queue&); //析構函數 ~Queue() { destroy(); }; //使用一對迭代器的賦值成員模版,標準queue沒有這個函數 template <class Iter> void assign(Iter, Iter); //對列尾部添加一個元素 void push(const Type&); //從對頭刪除元素 void pop(); //取對頭元素 Type &front() { return head->item; }; const Type &front() const { return head->item; }; //隊列是否空 bool empty() const { return head == 0;}; private: Queueitem<Type> *head; Queueitem<Type> *tail; //utility functions used by copy constructor, assignment, and destructor //供析構函數調用 void destroy(); //供複製構造和複製操做符調用 void copy_elems(const Queue&); //供template <class It> Queue(It beg, It end)調用 template <class Iter> void copy_elems(Iter, Iter); }; //Include Compilation Model: include member function definitions as we;; //包含編譯,大多數編譯器不支持export分別編譯 #include "Queue.cc" #endif
//Queue.cc實現文件
/*編寫要考慮如下內容 * 1.通常先編寫基本功能函數, * 基本功能函數有增長,刪除,訪問, 是否空, * 構造函數和賦值操做符要調用複製元素的函數,析構函數要調用的析構元素的函數 * 對應Queue中的push(), pop(), front()(inline), empty()(inline), copy_elems(), destroy() * 其餘函數調用它們 * 2.注意哪些函數能夠是inline的,能夠的儘可能知足, 而且放在頭文件中。 * Queue中inline有front(), empty(),構造函數和複製構造函數,析構函數 * 3.編寫函數時從需求條件最少的開始, * 以功能型函數形式進行編寫,例如push()只須要inline的empty(),pop()都不須要能夠先編寫 */ #include <iostream> using std::ostream; template <typename Type> void Queue<Type>::push(const Type &val) { Queueitem<Type> *p = new Queueitem<Type>(val); if (empty()) { head = tail = p; } else { tail->next = p; tail = p; } } template <typename Type> void Queue<Type>::pop() { Queueitem<Type> *p = head; head = head->next; delete p; } template <typename Type> void Queue<Type>::destroy() { while (!empty()) { pop(); } } template <typename Type> void Queue<Type>::copy_elems(const Queue &orig) { for (Queueitem<Type> *p = orig.head; p; p = p->next) { push(p->item); } } template <typename Type> Queue<Type>& Queue<Type>::operator=(const Queue &rhs) { if (this != &rhs) { destroy(); copy_elems(rhs); } return *this; } /* * 注意成員模版的編寫方式 * 好處:能夠應用隱式類型轉換, * 即只要*Iter能夠轉換爲Type,就可使用assign,不須要*Iter必定和Type相同 */ template <typename Type> //類模版的模型形參 template <typename Iter> //成員模版的模版形參 void Queue<Type>::assign(Iter beg, Iter end) { destroy(); while (beg != end) { push(*beg); ++beg; } } template <typename Type> ostream& operator<<(ostream &os, const Queue<Type> &q) { os << "< "; Queueitem<Type> *p; for (p = q.head; p; p = p->next) { os << p->item << " "; } os << ">"; }
//Queue_main.cc : 使用Queue的主函數
1 #include "Queue.h" 2 #include <iostream> 3 #include <vector> 4 5 using std::vector; 6 using std::cout; 7 using std::endl; 8 9 int main(void) 10 { 11 Queue<int> iq; 12 13 cout << "在Queue中添加0..9十個元素:" << endl; 14 for (int i = 0; i != 10; ++i) { 15 iq.push(i); 16 } 17 cout << "實例化oprator<<() 輸出元素:" << iq << endl << endl; 18 19 cout << "實例化front()(用於輸出元素), pop(), empty().pop後iq變爲空:" << endl; 20 for (int i = 0; i != 10; ++i) { 21 cout << iq.front() << " "; 22 iq.pop(); 23 } 24 cout << endl << "iq 是否爲空" << endl; 25 cout << "iq.empty() == " << iq.empty(); 26 cout << endl << endl; 27 28 cout << "從新初始化iq爲0..4:" << endl; 29 for (int i = 0; i != 5; ++i) { 30 iq.push(i); 31 } 32 cout << "輸出iq:" << iq << endl << endl; 33 34 Queue<int> iq2; 35 vector<int> ivec; 36 cout << "初始化vector 0..12:" << endl; 37 for (int i = 0; i != 13; ++i) { 38 ivec.push_back(i); 39 cout << ivec[i] << " "; 40 } 41 cout << endl << "實例化assign(), 用vector初始化Queue:" << endl; 42 iq2.assign(ivec.begin(), ivec.end()); 43 cout << "After assign(vector); iq2:" 44 << iq2 << endl << endl; 45 46 Queue<int> iq3 = iq2; 47 cout << "實例化複製構造函數Queue(const Queue&), Queue<int> iq3 = iq2; iq3: " << endl; 48 cout << iq3 << endl << endl; 49 50 iq = iq2; 51 cout << "實例化複製操做符operator=(const Queue&),After iq = iq2:" << endl; 52 cout << iq << endl << endl; 53 54 return 0; 55 }
使用G++編譯:
g++ Queue_main.cc //使用GCC編譯
./a.out //運行
輸出:
注:輔助函數能夠改寫爲list,deque,基本框架不變。
9 模版特化
略