• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            聚星亭

            吾笨笨且懶散兮 急須改之而奮進
            posts - 74, comments - 166, trackbacks - 0, articles - 0
              C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

            當我們越來越多的使用C++的特性將越來越多的問題和事物抽象成對象時, 我們不難發現:很多對象都具有共性。 比如 數值可以增加、減少;字符串也可以增加減少。 它們的動作是相似的, 只是對象的類型不同而已。

            C++ 提供了“模板”這一特性, 可以將“類型” 參數化, 使得編寫的代碼更具有通用性。 因此大家都稱模板編程為 “通用編程”或 “泛型編程”。

            一般而言, 模板分為 函數模板 和 類模板,下面就讓我們分別來了解一下它們。

            一、 函數模板

            1、 函數模板的定義和使用

            定義一個模板函數的格式并不復雜, 如下:

            template <模板參數列表>

            返回類型 函數名(函數參數列表)

            {

                // code ...

            }

            下面, 讓我們來舉一個例子來說明 模板函數的作用和用法(具體代碼見 Exp01)。

            // 定義一個函數模板, 用來實現任意類型數據的相互交換。

            template <typename T> // 聲明一個 數據類型, 此類型隨 調用方 類型的變化而變化

            void swap(T& a, T& b)

            {

                T tmp = a; 

                a = b;

                b = tmp;

            }

              

            上面的代碼, 說明了模板函數的用法。下面再給出調用的代碼, 我們看看如何使用這個函數模板:

            int main(int argc, char* argv[])

            {

                int nNum1 = 50;

                int nNum2 = 30;

                double dfNum1 = 2.1;

                double dfNum2 = 3.0;

                char *pszFirst = "Hello ";

                char *pszSec = "world!";

                swap <int> (nNum1, nNum2); // swap函數模板實例化為 int類型的模板函數再調用

                printf("nNum1 = %d, nNum2 = %d\r\n", nNum1, nNum2);

                swap<double> (dfNum1, dfNum2);

                printf("dfNum1 = %f, pszSec = %f\r\n", dfNum1, dfNum2);

                swap<char *> (pszFirst, pszSec);

                printf("pszFirst = %s, pszSec = %s\r\n", pszFirst, pszSec);

            return 0;

            }

            具體的執行結果如下:

            我相信,如果你是第一次見到模板的代碼,那你一定也會像我一樣好奇,這個功能是怎么實現的,它是怎么做到讓一段代碼來兼容各種類型的呢?

            當我要反匯編該EXE得時候,無意間查看了下編程生成的map文件,讓我看到了如下的內容:

              Address                           

            Publics by Value

            Rva+Base

            Lib:Object

            0001:00000140

            ?swap@@YAXAAH0@Z

            00401140 f i

            Exp01.obj

            0001:00000190

            ?swap@@YAXAAN0@Z

            00401190 f i

            Exp01.obj

            0001:000001f0

            ?swap@@YAXAAPAD0@Z

            004011f0 f i

            Exp01.obj

            由此可見, 我們編寫的void swap(T& a, T& b), 只是一個“函數模板”, 要使用它需要先將它實例化為一個“模板函數”(如:swap <int>)。編譯器在編譯此程序的時候,為每個調用此模板的代碼生成了一個函數。而且在后期的使用過程中,更加讓我認識到:

            a、 函數模板 并不是函數,它僅在編譯時根據調用此模板函數時傳遞的實參類型來生成具體的函數體!若 函數模板沒有被調用責不生成任何代碼也不對模板代碼作任何語法檢查。

            b、 在模板類型參數中, 實參與形參要求嚴格匹配,它不會進行任何的類型轉換。

            c、 對于函數模板,我們在調用時不一定必須先進行將它實例化為一個函數也是可以的,編譯器會自動的識別模板的類型。(換句話說:Exp01中的代碼可以直接使用swap, 而不需要<>

            2、  函數模板的重載

            當編寫的一個模板無法滿足所有需要的情況時,就需要對模板進行重載(或叫 特例化),例如:我們編寫了一個較大值的模板Max

            template <typename T>

            T constMax(T const& a, T const& b)

            {

                return a < b ? b : a;

            }

            A、 當我們需要傳入兩個指針類型的實參時,該模板就失效了,需要重載該模板:

            template <typename T>

            T constMax(T* const& a, T* const& b)

            {

                return *a < *b ? *b : *a;

            }

            B、 倘若我們再需要比較兩個字符串大小時,上面兩個模板都失效了。因為char* 并沒有提供 operator < 運行,我們只能通過調用strcmp庫函數自己實現一個Max模板的特例(見Exp02):

            const charMax(const char*& a, const char*& b)

            {

                return strcmp(a, b) < 0 ? b : a;

            }

            說明:

            C++模板機制規定,如果一個調用,即匹配普通函數,又能匹配模板函數的話,則優先匹配普通函數。因此,當我們模板特例化的時候,會先匹配特例化的函數。

            二、 類模板

            1、  基本概念

            類模板一般應用于容器類中,使得容器能夠處理各種類型的對象,如(詳見Exp03):

            struct Node

            {

               Nodeint nData = 0 )

               {

                  m_nData = nData;

                  m_pPrev = m_pNext = NULL;

               }

               int   m_nData; // 數據元素

               Node* m_pPrev; // 指向上一個元素的指針

               Node* m_pNext; // 指向下一個元素的指針

            };

            class CDList

            {

            private:

              int  m_nCount;

              Node* m_pHead;

              Node* m_pTail;

              int    m_nMessage;

            public:

            CDList();

            virtual ~CDList();

            public:

            int GetLen() const

            {

            m_nCount;

            }

            NodeGetHead() const

            {

            return m_pHead;

            }

            NodeGetTail() const

            {

            return m_pTail;

            }

            public:

            bool Change(int nIndex1,int nIndex2);

            void Release();

            //增加

            NodeAddTailint nData );

            NodeAddHeadint nData );

            Nodeoperator[](int nIndex);

            //刪除

            bool DeleteNodeint nIndex );

            void PrintAll();

            //查找

            NodeFindNodeint nIndex );

            };

            對于這樣的鏈表,其節點的元素只能存放整型數據。如果要想讓此雙向鏈表能夠存放任何一種類型的元素,那此時我們需要的問題與函數模板就一樣了,將此類修改成類模板,現在先不管類模板的寫法,讓我們按照函數模板的方法將類修改一下:

            template <typename T>

            struct Node

            {

               Node( T Data )

               {

                  m_Data  = Data;

                  m_pPrev = m_pNext = NULL;

               }

               T m_Data;  /通用類型的數據元素

               Node<T>* m_pPrev; // 指向上一個元素的指針

               Node<T>* m_pNext; // 指向下一個元素的指針

            };

            這樣,我們每個節點都可以使用通用的類型了,當然,對節點的處理也一樣得處理。按照這個樣子將類型參數化(為節省篇幅,具體的實現部分請參考Exp04):

            template <typename T>

            class CDList

            {

            private:

            int     m_nMessage; // 消息號

            int     m_nCount; // 鏈表中 元素的數量

            Node<T>* m_pHead; // 鏈表頭指針

            Node<T>* m_pTail; // 鏈表尾指針

            public:

            CDList();

            virtual ~CDList();

            public:

            int GetLen() const

            {

            m_nCount;

            }

            Node<T>* GetHead() const

            {

            return m_pHead;

            }

            Node<T>* GetTail() const

            {

            return m_pTail;

            }

            public:

            bool Change(int nIndex1,int nIndex2);

            void Release();

            //增加

            Node<T>* AddTail( T Data );

            Node<T>* AddHead( T Data );

            Node<T>* operator[](int nIndex);

            //刪除

            bool DeleteNode( int nIndex );

            void PrintAll();

            //查找

            Node<T>* FindNode( int nIndex );

            };

            這樣就修改好了,很簡單吧,貌似類模板沒有什么太多的新語法規范。完整的模板代碼,大家可以參考Exp04,下面我們總結一下類模板的一些語法小細節。

            2、  類模板的定義

            通過上面的一番修改,我相信你一定對類模板有了一定的了解,下面我們大致的總結一下類模板的定義格式:

            Template <typename T>

            Class 類名

            {

            // code,可以使用模板參數T來指定通用的數據類型。

            }

            正如上面的Exp04中一樣,我們的模板寫好了,但是它不能直接使用,就像函數模板一樣,我們需要先將模板實例化成一個模板類才可以使用。在函數模板中,編譯器會針對我們傳遞的實參類型等信息自動的給我們實例化函數模板為模板函數,但是類模板就沒有這么智能了,必須手工實例化:

            int main(int argc, char* argv[])

            {

            CDList<int> MyList; // CDList實例化為一個int類型,也就是說鏈表中數據元素為整型

            //(20) (80) 100 200 50 60

            MyList.AddTail(20);

            MyList.AddTail(80);

            MyList.AddTail(100);

            MyList.AddTail(200);

            MyList.AddTail(50);

            MyList.AddTail(60);

            MyList.PrintAll();

            MyList.Change(0,1);

            MyList.PrintAll();

            return 0;

            }

            程序執行結果:

            總結:

            a、 類模板 同樣也不是類,它僅在編譯時根據實例化本模板時傳遞的實參來生成具體的類代碼!若 類模板沒有被實例化也沒有被調用,那編譯器不會為本模板生成任何代碼也不對模板代碼作任何語法檢查。

            3、  類模板的特化

            類模板的特化又被叫做類模板的定做,首先讓我們來了解下什么叫作定做。

            通過上面幾個小節的學習,我相信,大家都知道模板不能直接被使用:必須先給模板傳遞一個實參,將它實例化為一個模板類,然后才可以用它來定義具體的對象。這樣就隱含了一個問題:

            我們通過給模板傳遞一個實參來實例化的模板類中的代碼都是在模板中定義好的,如果我們不能用與定義好的模板代碼來生成模板類,這時就需要使用模板的特化,也就是“定做”。

            比如,我們剛才寫好的雙向鏈表模板中,對于某一個類(比如CStudent)來說,不允許添加重復的節點,但是對于像普通的intdouble等數據類型以及其它一些類時,又不需要有這類的限制。這時我們就需要將此雙向鏈表模板針對這個不允許有重復節點的類(如:CStudent)進行特化,大致代碼如下:

            template <>

            class CDList<CStudent>

            {

            private:

            int     m_nMessage; // 消息號

            int     m_nCount; // 鏈表中 元素的數量

            Node<CStudent>* m_pHead; // 鏈表頭指針

            Node<CStudent>* m_pTail; // 鏈表尾指針

            public:

            bool Change(int nIndex1,int nIndex2);

            void Release();

            //增加

            Node<CStudent>* AddTailCStudent Data );

            Node<CStudent>* AddHeadCStudent Data );

            Node<CStudent>* operator[](int nIndex);

            //刪除

            bool DeleteNodeint nIndex );

            void PrintAll();

            //查找

            Node<CStudent>FindNodeint nIndex );

            };

            Node<CStudent>CDList<CStudent>::AddTail( CStudent Data )

            {

            Node<CStudent>* pNewNode = new Node<CStudent>(Data);

            if ( m_pTail )

            m_pTail->m_pNext = pNewNode;

            pNewNode->m_pPrev = m_pTail;

            if ( m_pTail == NULL )

            m_pHead = pNewNode;

            m_pTail = pNewNode;

            m_nCount++;

            return pNewNode;

            }

            由此可知,為CStudent類定做的CDList模板類,就是以CDList<CStudent>為類名重寫一份CDList<CStudent>實現而拋棄編譯器為我們生成的CDList<CStudent>類。

            當一個模板擁有多個模板參數時,如果我們只對其部分參數定做則稱為“局部定做”,這樣定做出來的“物件”仍然是一個模板,因為我們只特化了一部分模板參數.

            說明:

            剛才,我們為CStudent類定做的CDList模板類,其實我們沒有必要將整個CDList<CStudent>類都寫一遍,只需要重寫Add函數即可,例如:

            Template<>

            CDList<CStudent>::Add(CStudent& n)

            {

            ……

            }

            當然,這樣的代碼,需要寫在Cpp文件中,并在.h文件中聲明。

            三、 模板程序的組織

            當然,如果你足夠細心,你一定會好奇,為什么我給的例子中,模板的代碼都寫在頭文件中(.h文件),這里我們就討論模板代碼的組織方式。C++支持兩種模板代碼的組織方式,分別是包含方式(我們使用的就是包含方式)和分離方式。這兩種組織方式沒有太根本的區別,就是一個將代碼全寫在頭文件中,分離方式是像寫類一樣聲明和定義分別寫在頭文件(.h文件)和實現文件(cpp文件)中。

            下面我們分別討論下這兩種代碼組織方式。

            1、  包含方式

            本專題中,所有的實例代碼中的模板代碼都是以包含方式組織的。因為好多的編譯器(如VC6cl)并不支持分離方式組織代碼,將代碼寫在頭文件也只是方便編譯器的預處理工作能方便的將.h文件中的代碼根據模板實參的類型生成相應的模板類。

            當然,將代碼都寫在頭文件中還有一點點小要求:

            A、 如果模板的成員函數寫在類外,則需要寫成如下樣式(見Exp04):

            template <typename T> // 每個類外成員函數前都要有這句

            Node<T>CDList<T>::AddTail( T Data )

            {

            Node<T>* pNewNode = new Node<T>(Data);

            if ( m_pTail )

            m_pTail->m_pNext = pNewNode;

            pNewNode->m_pPrev = m_pTail;

            if ( m_pTail == NULL )

            m_pHead = pNewNode;

            m_pTail = pNewNode;

            m_nCount++;

            return pNewNode;

            }

            B、 對于特化的代碼則需要在.h文件中聲明并在.cpp文件中定義,如果都寫在.h文件中編譯會報重定義錯誤。

            2、  分離方式

            上面已經提到過,所謂的分離方式組織代碼,就是將模板的聲明和定義分別寫在頭文件(.h文件)和實現文件(cpp文件)中,需要注意的是,并不是所有的編譯器都支持這種寫法,目前我只知道GCC支持這種寫法。

            當然,分離方式組織代碼也有個小要求,就是在模板的聲明和定義的template關鍵字前都加上export關鍵字。比如:

            // .h 頭文件中

            export template <typename T>

            class CDList

            {

            public:

            CDList();

            virtual ~CDList();

            public:

            bool Change(int nIndex1,int nIndex2);

            void Release();

            //增加

            Node<T>* AddTail( T Data );

            Node<T>* AddHead( T Data );

            Node<T>* operator[](int nIndex);

            //刪除

            bool DeleteNode( int nIndex );

            void PrintAll();

            //查找

            Node<T>* FindNode( int nIndex );

            };

            在實現文件(cpp文件)中。

            export template <typename T> // 每個類外成員函數前都要有這句

            Node<T>CDList<T>::AddTail( T Data )

            {

            Node<T>* pNewNode = new Node<T>(Data);

            if ( m_pTail )

            m_pTail->m_pNext = pNewNode;

            pNewNode->m_pPrev = m_pTail;

            if ( m_pTail == NULL )

            m_pHead = pNewNode;

            m_pTail = pNewNode;

            m_nCount++;

            return pNewNode;

            }

            .

            四、 學習小結

            經過上面各個小節的學習,我相信大家一定像我一樣,對模板有了一點認識。大家也一定都知道,模板只是在編譯期間編寫,所有的代碼都只有效與編譯期。

            因此,模板的重載、特化等多態性也都是在編譯期間體現出來的,如果我們對編譯生成的可執行文件進行反匯編時,我們不會找到任何與模板有關的代碼,因為模板只是編譯期間的產物。

            關于模板的作用,我相信大家也一定都體會到了,它可以大大的減輕我們的編碼負擔,提高編程效率。關于模板的用法和技巧還有很多,單單模板特性足可以出一本書的篇幅來描述其特性及用法。

            因此本專題也只是帶領大家了解模板的基礎用法,關于模板的更多更深入知識,請參考 “模板元編程”相關內容。

            Feedback

            # re: 笨鳥先飛學編程系列之九-C++的模板編程  回復  更多評論   

            2010-07-22 16:09 by 陳梓瀚(vczh)
            學模板元編程之前,就算你沒學過haskell,你也應該先學haskell再學模板元編程,加起來的時間比你直接學模板元編程要短。

            # re: 笨鳥先飛學編程系列之九-C++的模板編程[未登錄]  回復  更多評論   

            2010-07-22 17:43 by besterChen
            @陳梓瀚(vczh)
            haskell?
            這個我還真不知道,我這就去看看,(*^__^*) 嘻嘻……
            謝謝vczh的指教~

            # re: 笨鳥先飛學編程系列之九-C++的模板編程  回復  更多評論   

            2010-07-22 22:38 by 空明流轉
            @陳梓瀚(vczh)
            其實沒必要。只要能理解“遞歸”和不動點就可以了。
            模板元編程最好不要看匯編,不是一個抽象層面的東西。

            # re: 笨鳥先飛學編程系列之九-C++的模板編程  回復  更多評論   

            2010-07-23 16:14 by yuegui2
            1.2例子的返回值搞錯了

            template <typename T>
            T* const& Max(T* const& a, T* const& b)
            {
            return *a < *b ? *b : *a;
            }
            返回值應該是T吧
            哈哈,小問題...

            # re: 笨鳥先飛學編程系列之九-C++的模板編程  回復  更多評論   

            2010-07-23 18:32 by 陳梓瀚(vczh)
            @空明流轉
            因此用haskell理解這些東西比看著模板要容易得多,因為haskell比模板元編程更加賞心悅目。

            # re: 笨鳥先飛學編程系列之九-C++的模板編程[未登錄]  回復  更多評論   

            2010-07-23 19:02 by besterChen
            @yuegui2
            恩,謝謝提醒,我改過來~

            # re: 笨鳥先飛學編程系列之九-C++的模板編程[未登錄]  回復  更多評論   

            2015-09-01 20:44 by 菜鳥
            Windows編程基礎是本實用的好書
            久久无码国产| 久久久久av无码免费网| 99久久精品国产一区二区 | 久久精品www| 久久亚洲AV成人无码国产| 麻豆av久久av盛宴av| 久久婷婷五月综合色奶水99啪| 亚洲精品NV久久久久久久久久| 久久久WWW成人免费精品| 国产亚州精品女人久久久久久 | 国产成年无码久久久久毛片| 99精品久久精品一区二区| 一本久久综合亚洲鲁鲁五月天亚洲欧美一区二区 | 久久精品人人做人人妻人人玩| 亚洲va中文字幕无码久久| 日韩精品久久无码中文字幕| 久久综合给久久狠狠97色| 久久夜色精品国产噜噜麻豆| 久久久无码精品亚洲日韩按摩| 久久久久久亚洲精品成人| 成人妇女免费播放久久久| 亚洲天堂久久精品| 精品欧美一区二区三区久久久| 久久久久久一区国产精品| 日韩久久久久中文字幕人妻| 久久这里有精品| A级毛片无码久久精品免费| 久久精品国产亚洲av影院| 精品久久久久久亚洲| 久久精品18| 18岁日韩内射颜射午夜久久成人| 日产精品99久久久久久| 亚洲一区中文字幕久久| 亚洲国产成人久久综合碰| 奇米影视7777久久精品| 日本三级久久网| 国产精品久久久久蜜芽| 久久久久久久人妻无码中文字幕爆| 久久er国产精品免费观看2| 色综合久久久久综合99| 久久精品九九亚洲精品|