模版與泛型編程簡介

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 模版特化

相關文章
相關標籤/搜索