泛型機制——模板----總結筆記

  • 泛型程序設計:
    在面向對象的程序設計中,允許將類中成員的類型設爲一個可變的參數,使多個類變成一個類,這種程序設計機制稱爲泛型程序設計。泛型程序設計可以以獨立於任何特定類型的方式編寫代碼,使用泛型程序時,必須提供具體的所操作的類型或值。
  • 函數模板:
    一組重載函數僅僅是參數的類型不一樣,程序的邏輯完全一樣,代碼大致相同,類型不同,可以寫成一個函數,稱爲函數模板,減少代碼量。是實現類型的參數化(泛型化),即把函數中某些形式參數的類型設計成可變的參數,稱爲模板參數。編譯器根據函數實際參數的類型確定模板參數的值,生成不同的模板參數。
    定義形式:
    template<class T>
    T max(T a,T b){
      return a > b ? a : b ;
    }
    注意:通常每個模板的形式參數都要在函數的形式參數表中至少出現一次,這樣編譯器才能通過函數調用確定模板參數的值。如果有些模板參數在函數的形式參數表中沒有出現,編譯器就無法推斷出模板實際參數的類型,可以通過顯式指定模板實參來解決。
    template<class T1, class T2, class T3>
    T3 calc(T1 x, T2 y){
      return x+y;
    }
    //調用時
    calc<int, char, char>(5, 'a');   //模板實例化
    calc<int, char, int>(5, 'a');            
  • 類模板:
    類中不確定的類型設計成一個模板參數,用模板參數代替類中的某些成員的類型。
    定義形式:

    template<模板形式參數表>
    class 類名(...);
    類模板可以定義數據成員和成員函數,構造函數和析構函數,也可以重載運算符。
    成員函數的參數類型或返回值類型也可以是模板的形式參數,成員函數的某些局部變量的類型也可以是模板的形式參數。
  1. 類模板成員函數定義形式:
    必須以template開頭,後接模板形式參數表。
    必須用作用域限定符 :: 說明其是哪個類的成員函數。
    類名必須包含其模板形式參數。
    // 可指定下標範圍的、支持任意類型的、安全的動態數組的定義
    template <class T>                         // 模板參數T是數組元素的類型
    class Array
    {
        int low;
        int high;
        T *storage;
    public:
        //根據low和high爲數組分配空間。分配成功,返回值爲true,否則返回值爲false
        Array(int lh = 0, int rh = 0):low(lh),high(rh)
        {  storage = new T [high - low + 1];  }
        //複製構造函數
        Array(const Array &arr);
        //賦值運算符重載函數
        Array &operator=(const Array & a);
        //下標運算符重載函數
        T & operator[](int index);
        //回收數組空間
        ~Array() { if (storage) delete [] storage; }
    };
    
    // 類模板Array的成員函數的實現
    
    // 複製構造函數
    template <class T>
    Array<T>::Array(const Array<T> &arr)     //函數名爲Array
    {
        low =arr.low;
        high = arr.high;
        storage = new T [high - low + 1];
        for (int i = 0; i < high -low + 1;++i) 
            storage[i] = arr.storage[i];
    }
    
    // 賦值運算符重載函數
    template<class T>
    Array<T> &Array<T>::operator=(const Array<T> &other )  //函數名爲operator=
    {
        if (this == &other) return *this;                    //防止自己複製自己
        delete [] storage;                                   //歸還空間
        low = other.low;
        high = other.high;
        storage = new T[high - low + 1];                      //根據新的數組大小重新申請空間
        for (int i=0; i <= high - low; ++i)                  //複製數組元素
        storage[i] = other.storage[i];
        return *this;
    }
    
    // 下標運算符重載函數
    template<class T>
    T &Array<T>::operator[](int index)    //函數名爲operator[]
    {
        if(index < low || index > high) 
          {  cout<< "下標越界"; exit(-1); }
        return storage[index - low];
    }
  2. 類模板實例化:
    函數模板的實例化通常由編譯器自動完成,編譯器根據函數調用時的實際參數類型推斷出模板參數的值,將模板參數的值代入函數模板生成一個真正可執行的模板函數。
    對於類模板,編譯器無法根據對象定義的過程確定模板參數的類型,因而需要用戶明確指出模板形式參數的值,定義格式:
    類模板名<模板實際參數表> 對象表;
    Array<int> array1(20,30);
    
    編譯器首先將模板的實際參數值代入類模板,生成一個可真正使用的類,然後定義這個類的對象。
  3. 非類型參數和參數的默認值:
    類型參數:模板參數都是用來表示一個尚未明確的類型,這些參數是類型參數。
    非類型參數:模板的形式參數不一定都是類型,可以是整數型或實數型。
    非類型模板實例化,非類型參數將用一個常量表達式作爲實際參數。
    // 將數組的下標範圍作爲模板的非類型參數
    template <class T, int low, int high>
    class Array{
        T storage[high - low + 1];
    public:    
        T & operator[](int index) ;                       //下標運算符重載函數
    };
    
    template <class T, int low, int high>
    T & Array<T, low, high>::operator[](int index)
    {
       if (index < low || index > high)
          {   cout << "下標越界"; exit(-1); }
    
        return storage[index - low];
    }
    
    Array<int, 10, 20>array;
    模板參數和普通的函數參數一樣,也可以指定默認值,可在類模板定義時指定默認值。
    template<class T = int> 
    class Array{...}; 
    這樣就可以不指定模板的實參
    Array<> array
  4. 類模板友元:(兩種友元)

    普通友元:聲明某個普通的類或全局函數爲所定義的類模板的友元。
    定義形式:
    template<class type>
    class A{
      friend class B;
      friend void f();
      ..;
    };
    意味類B和函數f是類模板A所有的實例的友元,B的所有的成員函數和全局函數f可以訪問類模板A的所有實例的私有成員。

    模板的特定實例的友元:聲明某個類模板或函數模板的特定實例是所定義類模板的友元。
    將類模板B和函數模板f對應於模板參數爲int時的那個實例,作爲類模板A的所有實例的友元(如下)
    template<class T>class B;   //類模板B的聲明,B有一個模板參數
    template<class T>void f(const T &);   //函數模板f的聲明,f有一個模板參數
    template<class type>
    class A{
      friend class B <int>;
      friend void f(const int &);
      ...
    };
    將使用某一模板實參的類模板B和函數模板f的實例是使用同一模板參數的類模板A的特定實例的友元(如下)
    template<class T>class B;
    template<class T>void f(const T &);
    template<class type>
    class A{
      friend class B <type>;
      friend void f(const type &);
      ...
    };
    當聲明類模板B和函數模板f爲類模板A的友元時,編譯器必須知道有這樣一個類模板和函數模板的存在,並且知道類模板B和函數模板f的原型,因此,必須在友元聲明之前先聲明B和f的存在。
    // 類模板的友元實例:重載輸出運算符
    template<class type>
    ostream &operator<<(ostream &os, const Array<type> &obj)
    {
        os << endl;
        for (int i=0; i < obj.high - obj.low + 1; ++i)  
            os << obj.storage[i] << '\t';
        return os;
    }
    
    // 類模板的友元實例:重載輸出運算符
    template <class T> class Array;	                              //類模板Array的聲明
    template<class T> ostream &operator<<(ostream &os, const Array<T>&obj); //輸出重載聲明
    //聲明瞭函數模板operator<<的一個實參爲T的實例是類模板Array的實參爲T的實例的友元。
    
    template <class T>
    class Array {
        friend ostream &operator<<(ostream &, const Array<T> &);
    
    private:
        int low;  
        int high;
        T *storage;
    
    public:
        //根據low和high爲數組分配空間。分配成功,返回值爲true,否則返回值爲false
        Array(int lh = 0, int rh = 0):low(lh),high(rh)
            { storage = new T [high - low + 1]; }
    
       //複製構造函數
        Array(const Array &arr);
        
        //賦值運算符重載函數
        Array &operator=(const Array & a);
    
        //下標運算符重載函數
        T & operator[](int index) {return storage[index - low];}
    
        //回收數組空間
        ~Array() {delete [] storage; }  
    };
    
    對於Array的任何一個實例:
    Array <int> array(10, 20);
    可以用
    cout << array;
    輸出
  • 類模板應用:
  1. 單鏈表類及結點類:
    定義兩個獨立的類,由於鏈表操作經常需要訪問結點的數據成員,因此,將同一模板參數的鏈表類設置爲結點類的友元。
    // 單鏈表模板類中的結點類和鏈表類的定義
    template <class elemType> class linkList;
    template <class T> ostream &operator<<(ostream &, const linkList<T> &);
    template <class elemType> class Node ;
    
    template <class elemType>
    class Node {
        friend class linkList<elemType>;
        friend ostream &operator<<( ostream &, const linkList<elemType> &);
    private:
        elemType  data;
      	  Node <elemType> *next;
    public:
        Node(const elemType &x, Node <elemType> *N = NULL) { data = x; next = N;}
        Node( ):next(NULL) {}
        ~Node() {}
    };
    
    template <class elemType>
    class linkList {
        friend ostream &operator<<( ostream &, const linkList<elemType> &);
    protected:
        Node <elemType> *head;
        void makeEmpty();                         // 清空鏈表
    public:
        linkList() { head  = new Node<elemType>;  }
        ~linkList() {makeEmpty(); delete head;}
    
        void create(const elemType &flag);
    };
    
    // 單鏈表類的成員函數及友元函數的定義
    template <class elemType>
    void linkList<elemType>::makeEmpty()
    {
        Node <elemType> *p = head->next, *q;
        head->next=NULL;
        while (p != NULL) { q=p->next; delete p;  p=q;}
    }
    
    template <class elemType>
    void linkList<elemType>::create(const elemType &flag)
    {
        elemType tmp;
        Node <elemType> *p, *q = head;
     
        cout << "請輸入鏈表數據," << flag << "表示結束" << endl;
    
        while (true) {
            cin >> tmp;
            if (tmp == flag) break;
            p = new Node<elemType>(tmp);
            q->next = p;
            q = p;
        }
    }
    
    template <class T>
    ostream &operator<<(ostream &os, const linkList<T> &obj)
    {
        Node <T> *q = obj.head->next;	 
    
        os << endl;
        while (q != NULL){ os << q->data;  q = q->next;  }
    
        return os;
    }
    
    實現對任何類型的單鏈表的操作
    linkList <int> intList;   //實例化定義一個整型的單鏈表
    intList.create(0);    //輸入鏈表中的元素值,直到輸入0爲止。
    cout << intList;    //輸出鏈表的所有元素
  2. 棧的類模板:
    類模板可以作爲繼承關係的基類,類模板的繼承和普通的繼承方法類似,只是在涉及基類時,都必須帶上模板參數。
    棧是特殊的線性表,插入和刪除只能在表的一端進行,允許插入和刪除的一端稱爲棧頂,另一端爲棧底,常用操作爲進棧(push)和出棧(pop)。
    棧在單鏈表的基礎上增加兩個操作push和pop,push在單鏈表的表頭插入一個元素,pop是刪除單鏈表的表頭元素。
    // 在鏈表類的基礎上派生一個棧類
    template <class elemType>
    class Stack:public linkList<elemType> {
    public:
        void push(const elemType &data)
        {
             Node <elemType> *p = new Node<elemType>(data); 
             p->next = head->next; 
             head->next = p;
        }
        bool pop(elemType &data)                  //棧爲空時返回false,出棧的值在data中
        {
             Node <elemType> *p = head->next;
    
             if ( p == NULL) return false;
             head->next = p->next; 
             data = p->data; 
             delete p;
    
             return true;
        }
    };
    
    爲使Stack能訪問Node的數據成員,必須將Stack設爲Node的友元。
  • 編程規範:
    繼承和組合提供一種重用對象代碼的方法
    模板提供了重用源代碼的方法,模板通過將類型作爲參數,將處理不同類型數據的一組函數或類綜合成一個函數或一個類,即函數模板和類模板,減輕代碼量。
    類模板的形式參數可以是普通類型,稱爲非類型參數,非類型參數的實際參數必須是編譯時的常量。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章