C++中模板與泛型編程

 目錄

    • 定義一個通用模板
    • 模板特化和偏特化
    • 模板實例化與匹配
    • 可變參數模板

  泛型編程是指獨立與任何類型的方式編寫代碼。泛型編程和麪向對象編程,都依賴與某種形式的多態。面向對象編程的多態性在運行時應用於存在繼承關係的類,一段代碼能夠能夠忽略基類和派生類之間的差別。在泛型編程中,編寫的代碼能夠用做多種類型的對象。面向對象編程所依賴的多態性稱爲運行時多態性,泛型編程所依賴的多態性稱爲編譯時多態性或參數式多態性。 html


1 模板定義

1.1 函數模板

  • 模板定義以關鍵字 template 開始,後接模板形參表,模板形參表是用尖括號括住的一個或多個模板形參的列表,形參之間以逗號分隔。模板形參表不能爲空。
  • 模板函數的類型形參跟在關鍵字 class 或 typename 以後定義.在函數模板形參表中,關鍵字 typename 和 class 具備相同含義,能夠互換使用,兩個關鍵字均可以在同一模板形參表中使用
  • 函數模板能夠用與非模板函數同樣的方式聲明爲 inline。說明符放在模板形參表以後、返回類型以前,不能放在關鍵字 template 以前
  • 函數模板調用方式。在發生函數模板的調用時,不顯示給出模板參數而通過參數推演,稱之爲函數模板的隱式模板實參調用(隱式調用)在發生函數模板的調用時,顯示給出模板參數而不須要通過參數推演,稱之爲函數模板的顯示模板實參調用(顯示調用)。顯示模板實參調用在參數推演不成功的狀況下是有必要的。
  • 函數模板與函數重載。函數模板其實是創建一個通用函數,其函數類型和形參類型不具體指定,用一個虛擬的類型來表明,凡是函數體相同的函數均可以用這個模板來代替,沒必要定以多個函數。重載函數的參數個數、參數類型或參數順序3者中必須至少有一種不一樣,函數返回值類型能夠相同也能夠不一樣,函數體能夠相同
1 template<typename T>
2 inline bool isEqual(const T& t1, const T& t2) {
3     return t1 == t2;
4 }

1.2 類模板

  • 類模板也是模板,所以必須以關鍵字 template 開頭,後接模板形參表
  • 除了模板形參表外,類模板的定義看起來與任意其餘類問類似。類模板能夠定義數據成員、函數成員和類型成員,也可使用訪問標號控制對成員的訪問,還能夠定義構造函數和析構函數等等。
  • 與調用函數模板造成對比,使用類模板時,必須爲模板形參顯式指定實參,類模板的形參不存在實參推演的
1 const size_t MAXSIZE = 100;
2 template<class T>
3 class Stack{
4 private:
5     T elements[MAXSIZE];
6 public:
7     //others
8 };

1.3 模板參數

  • 類型模板形參:類型形參由關見字class或typename後接說明符構成,如template<class T> void getMaxVal(const T& a,const T& b){};其中T就是一個類型形參,類型形參的名字由用戶自已肯定。
  • 非類型模板形參:模板的非類型形參也就是內置類型形參,如template<class T, int X> greaterThanX(const T& a);其中int X就是非類型的模板形參。非類型形參在模板定義的內部是常量值,也就是說非類型形參在模板的內部是常量。非類型的模板參數是有限制的,通常是一個整型,它們能夠是常整數(包括枚舉類型)或者指向外部連接對象的指針。浮點數和類對象是不容許做爲非類型模板參數的
  • 模板的默認參數。能夠爲類模板的類型形參提供默認值,但不能爲函數模板的類型形參提供默認值。函數模板和類模板均可覺得模板的非類型形參提供默認參數。類模板類型形參默認值和函數的默認參數同樣,若是有多個類型形參則從第一個形參設定了默認值以後的全部模板形參都要設定默認值。類模板的類型形參默認值形式爲:template<class T1, class T2=int> class A{};爲第二個模板類型形參T2提供int型的默認值,在類模板的外部定義類中的成員時template 後的形參表應省略默認的形參類型。好比template<class  T1, class T2=int> class A{public: void h();}; 定義方法爲template<class T1,class T2> void A<T1,T2>::h(){}
 1 template<typename T,int X = 5>
 2 inline bool isEqualToX(const T& a) {
 3     return a == X;
 4 }
 5 
 6 template<class T,int MAXSIZE=100>
 7 class Stack {
 8 private:
 9     T elements[MAXSIZE];
10 public:
11     //others
12 };

2.模板特化與偏特化

  有時爲了須要,針對特定的類型,須要對模板進行特化,也就是特殊處理。 例如,stack類模板針對bool類型,由於實際上bool類型只須要一個二進制位,就能夠對其進行存儲,使用一個字或者一個字節都是浪費存儲空間的.。特化必須在同一命名空間下進行,能夠特化類模板也能夠特化函數模板,但類模板能夠偏特化和全特化,而函數模板只能全特化。模板的偏特化是指須要根據模板的某些但不是所有的參數進行特化。嚴格的來講,函數模板並不支持偏特化,但因爲能夠對函數進行重載,因此能夠達到相似於類模板偏特化的效果。模板實例化時會優先匹配」模板參數」最相符的那個特化版本。template < >告訴編譯器這是一個特化的模板ios

 1 template<class T,int MAXSIZE=100>
 2 class Stack {
 3 private:
 4     T elements[MAXSIZE];
 5 public:
 6     //others
 7 };
 8 
 9 //template specializations aim at bool
10 template<>
11 class Stack<bool>{
12 
13 };
 
1
template<typename T> 2 inline bool isEqual(const T t1, const T t2) { 3 return t1 == t2; 4 } 5 6 //針對int型的指針作特化 7 template<> 8 inline bool isEqual(const int* p1,const int* p2){ 9 return *p1 == *p2; 10 }

類模板的偏特化,例如c++標準庫中的類vector的定義,這個偏特化的例子中,一個參數被綁定到bool類型,而另外一個參數仍未綁定須要由用戶指定。c++

1 template <class T, class Allocator>
2 class vector { //// };
3 template <class Allocator>
4 class vector<bool, Allocator> { ////};

函數模板的偏特化,嚴格的來講,函數模板並不支持偏特化,但因爲能夠對函數進行重載,因此能夠達到相似於類模板偏特化的效果。根據重載規則,對(a)進行重載。若是將(a)稱爲基模板,那麼(b)稱爲對基模板(a)的重載,而非對(a)的偏特化。C++的標準委員會仍在對下一個版本中是否容許函數模板的偏特化進行討論。
template <class T> void f(T); (a)
template < class T> void f(T*); (b)編程


3 模板實例化與匹配規則

3.1 隱式實例化。在使用模板函數和模板類時,不存在指定類型的模板函數和模板類的實體時,由編譯器根據指定類型參數隱式生成模板函數或者模板類的實體稱之爲模板的隱式實例化。函數模板隱式實例化指的是在發生函數調用的時候,若是沒有發現相匹配的函數存在,編譯器就會尋找同名函數模板,若是能夠成功進行參數類型推演,就對函數模板進行實例化。類模板隱式實例化指的是在使用模板類時纔將模板實例化。函數

3.2 顯示實例化。顯示實例化也稱爲外部實例化。在不發生函數調用的時候將函數模板實例化,或者在不適用類模板的時候將類模板實例化稱之爲模板顯示實例化。對於函數模板而言,不論是否發生函數調用,均可以經過顯示實例化聲明將函數模板實例化,定義函數模板爲:template函數返回類型 函數模板名<實際類型列表>(函數參數列表),顯示實例化爲template void func<int>(const int&);類模板的顯示實例化,對於類模板而言,不論是否生成一個模板類的對象,均可以直接經過顯示實例化聲明將類模板實例化,定義類模板格式爲:template class 類模板名<實際類型列表>,顯示實例化爲template class theclass<int>;優化

3.3 匹配規則spa

(1) 類模板的匹配規則。最優化的優於次特化的,即模板參數最精確匹配的具備最高的優先權,每一個類型均可以用做普通型(a)的參數,但只有指針類型才能用做(b)的參數,而只有void*才能做爲(c)的參數。.net

template <class T> class vector{//…//}; // (a) 普通型
template <class T> class vector<T*>{//…//}; // (b) 對指針類型特化
template <> class vector <void*>{//…//}; // (c) 對void*進行特化指針

(2) 函數模板的匹配規則。非模板函數具備最高的優先權。若是不存在匹配的非模板函數的話,那麼最匹配的和最特化的函數具備高優先權rest

template <class T> void f(T); // (d)
template <class T> void f(int, T, double); // (e)
template <class T> void f(T*); // (f)
template <> void f<int> (int) ; // (g)
void f(double); // (h)
bool b;
int i;
double d;
f(b); // 以 T = bool 調用 (d)
f(i,42,d) // 以 T = int 調用(e)
f(&i) ; // 以 T = int* 調用(f)
f(d); // 調用(g)


4.可變參數模板

參考:http://www.cnblogs.com/qicosmos/p/4325949.html

       可變參數模板是C++11新增的特性之一,它對參數高度泛化,他能表示0到任意個數、任意類型的參數。可變模板參數以前會帶有省略號,把帶省略號的參數稱爲「參數包」,它裏面包含了0到N(N>=0)個模版參數。咱們沒法直接獲取參數包args中的每一個參數的,只能經過展開參數包的方式來獲取參數包中的每一個參數,這是使用可變模版參數的一個主要特色。可變模版參數和普通的模版參數語義是一致的,因此能夠應用於函數和類,便可變模版參數函數和可變模版參數類,然而,模版函數不支持偏特化,因此可變模版參數函數和可變模版參數類展開可變模版參數的方法還不盡相同。

4.1 可變模板參數函數與參數的展開

  • 遞歸函數方式展開參數包。經過遞歸函數展開參數包,須要提供一個參數包展開的函數和一個遞歸終止函數,遞歸終止函數正是用來終止遞歸的。
  • 逗號方式展開參數包
 1 #include <iostream>
 2 using namespace std;
 3 //遞歸終止函數
 4 void print()
 5 {
 6    cout << "empty" << endl;
 7 }
 8 //展開函數
 9 template <class T, class ...Args>
10 void print(T head, Args... rest)
11 {
12    cout << "parameter " << head << endl;
13    print(rest...);
14 }
15 
16 
17 int main(void)
18 {
19    print(1,2,3,4);
20    return 0;
21 }
 1 template <class T>
 2 void printarg(T t)
 3 {
 4    cout << t << endl;
 5 }
 6 
 7 template <class ...Args>
 8 void expand(Args... args)
 9 {
10    int arr[] = {(printarg(args), 0)...};
11 }
12 
13 expand(1,2,3,4);

4.2 可變模板參數類與參數展開

  可變參數模板類的參數包展開的方式和可變參數模板函數的展開方式不一樣,可變參數模板類的參數包展開須要經過模板特化和繼承方式去展開,展開方式比可變參數模板函數要複雜。可變參數模板類是一個帶可變模板參數的模板類,好比C++11中的元祖std::tuple就是一個可變模板類,它的定義以下,這個可變參數模板類能夠攜帶任意類型任意個數的模板參數。

1 template< class... Types >
2 class tuple; 
3 std::tuple<int> tp1 = std::make_tuple(1);
4 std::tuple<int, double> tp2 = std::make_tuple(1, 2.5);
5 std::tuple<int, double, string> tp3 = std::make_tuple(1, 2.5, 「」);
6 std::tuple<> tp;//可變參數模板的模板參數個數能夠爲0個,因此下面的定義也是也是合法的:

參考

  1. 模板特化和偏特化
  2. 泛化之美--C++11可變模版參數的妙用
相關文章
相關標籤/搜索