C++學習筆記十六-模板和泛型編程(一)


概述:所謂泛型編程就是以獨立於任何特定類型的方式編寫代碼。使用泛型程序時,我們需要提供具體程序實例所操作的類型或值。第二部分中描述的標准庫的容器、迭代器和算法都是泛型編程的例子。在 C++ 中,模板是泛型編程的基礎。模板是創建類或函數的藍圖或公式。

一 、模板定義

        1.定義函數模板:

     compare 的模板版本:

     // implement strcmp-like generic compare function
     // returns 0 if the values are equal, 1 if v1 is larger, -1 if v1 is smaller
     template <typename T>
     int compare(const T &v1, const T &v2)
     {
         if (v1 < v2) return -1;
         if (v2 < v1) return 1;
         return 0;
     }

       a. 模板定義以關鍵字 template 開始,后接模板形參表,模板形參表是用尖括號括住的一個或多個模板形參的列表,形參之間以逗號分隔。 模板形參表不能為空。

       b.模板形參表很像函數形參表,函數形參表定義了特定類型的局部變量但並不初始化那些變量,在運行時再提供實參來初始化形參。

       c.模板形參可以是表示類型的類型形參,也可以是表示常量表達式的非類型形參。非類型形參跟在類型說明符之后聲明,類型形參跟在關鍵字 classtypename 之后定義,例如,class T 是名為 T 的類型形參,在這里 classtypename 沒有區別。

       2.使用函數模板:使用函數模板時,編譯器會推斷哪個(或哪些)模板實參綁定到模板形參。一旦編譯器確定了實際的模板實參,就稱它實例化了函數模板的一個實例。實質上,編譯器將確定用什么類型代替每個類型形參,以及用什么值代替每個非類型形參。推導出實際模板實參后,編譯器使用實參代替相應的模板形參產生編譯該版本的函數。編譯器承擔了為我們使用的每種類型而編寫函數的單調工作。

 

        2.類模板:類模板也是模板,因此必須以關鍵字 template 開頭,后接模板形參表。

            a.除了模板形參表外,類模板的定義看起來與任意其他類問相似。類模板可以定義數據成員、函數成員和類型成員,也可以使用訪問標號控制對成員的訪問,還可以定義構造函數和析構函數等等。在類和類成員的定義中,可以使用模板形參作為類型或值的占位符,在使用類時再提供那些類型或值。

            b.與調用函數模板形成對比,使用類模板時,必須為模板形參顯式指定實參.編譯器使用實參來實例化這個類的特定類型版本。

 

        3.模板形參: 

            a.像函數形參一樣,程序員為模板形參選擇的名字沒有本質含義。

            b.可以給模板形參賦予的唯一含義是區別形參是類型形參還是非類型形參。如果是類型形參,我們就知道該形參表示未知類型,如果是非類型形參,我們就知道它是一個未知值。

 

         4.模板形參作用域:

            a.模板形參的名字可以在聲明為模板形參之后直到模板聲明或定義的末尾處使用。

            b.模板形參遵循常規名字屏蔽規則。與全局作用域中聲明的對象、函數或類型同名的模板形參會屏蔽全局名字.

            c.使用模板形參名字的限制: 用作模板形參的名字不能在模板內部重用(不能再次作為類型來使用)。

     template <class T> T calc(const T &a, const T &b)
     {
         typedef double T; // error: redeclares template parameter T
         T tmp = a;
         // ...
         return tmp;
     }

            d.這一限制還意味着模板形參的名字只能在同一模板形參表中使用一次:

     // error: illegal reuse of template parameter name V
     template <class V, class V> V calc(const V&, const V&) ;
       e.當然,正如可以重用函數形參名字一樣,模板形參的名字也能在不同模板中重用. 

            f.同一模板的聲明和定義中,模板形參的名字不必相同。

     // all three uses of calc refer to the same function template
     // forward declarations of the template
     template <class T> T calc(const T&, const T&) ;
     template <class U> U calc(const U&, const U&) ;
     // actual definition of the template
     template <class Type>
     Type calc(const Type& a, const Type& b) { /* ... */ }
       g.每個模板類型形參前面必須帶上關鍵字 classtypename,每個非類型形參前面必須帶上類型名字,省略關鍵字或類型說明符是錯誤的
 
      5.typenameclass 的區別:
        在函數模板形參表中,關鍵字 typenameclass 具有相同含義,可以互換使用,兩個關鍵字都可以在同一模板形參表中使用
 
   6.在模板定義內部指定類型:在類型之前指定 typename 沒有害處,因此,即使 typename 是不必要的,也沒有關系。

如果希望編譯器將 size_type 當作類型,則必須顯式告訴編譯器這樣做:

     template <class Parm, class U>
     Parm fcn(Parm* array, U value)
     {
         typename Parm::size_type * p; // ok: declares p to be a pointer
     }

 

          7.非類型模板形參:

              a.在調用函數時非類型形參將用值代替,值的類型在模板形參表中指定。

              b.模板非類型形參是模板定義內部的常量值,在需要常量表達式的時候,可使用非類型形參(例如,像這里所做的一樣)指定數組的長度.

 

          8.編寫泛型程序:

              a.在函數模板內部完成的操作限制了可用於實例化該函數的類型。程序員的責任是,保證用作函數實參的類型實際上支持所用的任意操作,以及保證在模板使用哪些操作的環境中那些操作運行正常。

              b.編寫模板代碼時,對實參類型的要求盡可能少是很有益的。

              c.編寫泛型代碼的兩個重要原則:

  •                模板的形參是 const 引用。

  •                函數體中的測試只用 < 比較。

  •        9.警告:鏈接時的編譯時錯誤:

    重要的是,要認識到編譯模板定義的時候,對程序是否有效所知不多。類似地,甚至可能會在已經成功編譯了使用模板的每個文件之后出現編譯錯誤。只在實例化期間檢測錯誤的情況很少,錯誤檢測可能發生在鏈接時。

 

二 、實例化

       概述:模板是一個藍圖,它本身不是類或函數。編譯器用模板產生指定的類或函數的特定類型版本。產生模板的特定類型實例的過程稱為實例化,這個術語反映了創建模板類型或模板函數的新“實例”的概念。

       模板在使用時將進行實例化,類模板在引用實際模板類類型時實例化,函數模板在調用它或用它對函數指針進行初始化或賦值時實例化。

          1.類的實例化:

               a.類模板的每次實例化都會產生一個獨立的類類型。為 int 類型實例化的 Queue 與任意其他 Queue 類型沒有關系,對其他 Queue 類型成員也沒有特殊的訪問權。

                b.想要使用類模板,就必須顯式指定模板實參。

                c.類模板不定義類型,只有特定的實例才定義了類型。特定的實例化是通過提供模板實參與每個模板形參匹配而定義的。模板實參在用逗號分隔並用尖括號括住的列表中指定:

         Queue<int> qi;         // ok: defines Queue that holds ints
           d.用模板類定義的類型總是包含模板實參。例如,Queue 不是類型,而 Queue<int>Queue<string> 是類型。
 
      2.函數模板實例化:
         A.使用函數模板時,編譯器通常會為我們推斷模板實參:
          int main()
        {
           compare(1, 0);             // ok: binds template parameter to int
           compare(3.14, 2.7);        // ok: binds template parameter to double
           return 0;
         }
         B.模板實參推斷:要確定應該實例化哪個函數,編譯器會查看每個實參。如果相應形參聲明為類型形參的類型,則編譯器從實參的類型推斷形參的類型。
 
         C.類型形參的實參的受限轉換:一般而論,不會轉換實參以匹配已有的實例化,相反,會產生新的實例。除了產生新的實例化之外,編譯器只會執行兩種轉換:
 a.const 轉換:接受 const 引用或 const 指針的函數可以分別用非 const 對象的引用或指針來調用,無須產生新的實例化。如果函數接受非引用類型,形參類型實參都忽略 const,即,無論傳遞 const 或非 const 對象給接受非引用類型的函數,都使用相同的實例化。
              b.數組或函數到指針的轉換:如果模板形參不是引用類型,則對數組或函數類型的實參應用常規指針轉換。數組實參將當作指向其第一個元素的指針,函數實參當作指向函數類型的指針。
 
         D.類型轉換的限制只適用於類型為模板形參的那些實參。

       用普通類型定義的形參可以使用常規轉換,下面的函數模板 sum 有兩個形參:

     template <class Type> Type sum(const Type &op1, int op2)
     {
         return op1 + op2;
     }
         E.模板實參推斷與函數指針:可以使用函數模板對函數指針進行初始化或賦值,這樣做的時候,編譯器使用指針的類型實例化具有適當模板實參的模板版本。獲取函數模板實例化的地址的時候,上下文必須是這樣的:它允許為每個模板形參確定唯一的類型或值。
   // overloaded versions of func; each take a different function pointer type
     void func(int(*) (const string&, const string&));
     void func(int(*) (const int&, const int&));
     func(compare); // error: which instantiation of compare?
        F.在返回類型中使用類型形參:
          a.指定返回類型的一種方式是引入第三個模板形參,它必須由調用者顯式指定,也就是說返回類型不能推斷,必須顯示指定
     // T1 cannot be deduced: it doesn't appear in the function parameter list
     template <class T1, class T2, class T3>
     T1 sum(T2, T3);
          b.顯式模板實參從左至右對應模板形參相匹配,第一個模板實參與第一個模板形參匹配,第二個實參與第二個形參匹配,以此類推。假如可以從函數形參推斷,則結尾(最右邊)形參的顯式模板實參可以省略。
 
       G.顯式實參與函數模板的指針:通過使用顯式模板實參能夠消除二義性:
     template <typename T> int compare(const T&, const T&);
     // overloaded versions of func; each take a different function pointer type
     void func(int(*) (const string&, const string&));
     void func(int(*) (const int&, const int&));
     func(compare<int>); // ok: explicitly specify which version of compare


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM