• <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>

            聚星亭

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

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

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

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

            一、 函數(shù)模板

            1、 函數(shù)模板的定義和使用

            定義一個模板函數(shù)的格式并不復(fù)雜, 如下:

            template <模板參數(shù)列表>

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

            {

                // code ...

            }

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

            // 定義一個函數(shù)模板, 用來實(shí)現(xiàn)任意類型數(shù)據(jù)的相互交換。

            template <typename T> // 聲明一個 數(shù)據(jù)類型, 此類型隨 調(diào)用方 類型的變化而變化

            void swap(T& a, T& b)

            {

                T tmp = a; 

                a = b;

                b = tmp;

            }

              

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

            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函數(shù)模板實(shí)例化為 int類型的模板函數(shù)再調(diào)用

                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;

            }

            具體的執(zhí)行結(jié)果如下:

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

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

              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), 只是一個“函數(shù)模板”, 要使用它需要先將它實(shí)例化為一個“模板函數(shù)”(如:swap <int>)。編譯器在編譯此程序的時候,為每個調(diào)用此模板的代碼生成了一個函數(shù)。而且在后期的使用過程中,更加讓我認(rèn)識到:

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

            b、 在模板類型參數(shù)中, 實(shí)參與形參要求嚴(yán)格匹配,它不會進(jìn)行任何的類型轉(zhuǎn)換。

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

            2、  函數(shù)模板的重載

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

            template <typename T>

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

            {

                return a < b ? b : a;

            }

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

            template <typename T>

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

            {

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

            }

            B、 倘若我們再需要比較兩個字符串大小時,上面兩個模板都失效了。因?yàn)?font face="Times New Roman">char* 并沒有提供 operator < 運(yùn)行,我們只能通過調(diào)用strcmp庫函數(shù)自己實(shí)現(xiàn)一個Max模板的特例(見Exp02):

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

            {

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

            }

            說明:

            C++模板機(jī)制規(guī)定,如果一個調(diào)用,即匹配普通函數(shù),又能匹配模板函數(shù)的話,則優(yōu)先匹配普通函數(shù)。因此,當(dāng)我們模板特例化的時候,會先匹配特例化的函數(shù)。

            二、 類模板

            1、  基本概念

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

            struct Node

            {

               Nodeint nData = 0 )

               {

                  m_nData = nData;

                  m_pPrev = m_pNext = NULL;

               }

               int   m_nData; // 數(shù)據(jù)元素

               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 );

            };

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

            template <typename T>

            struct Node

            {

               Node( T Data )

               {

                  m_Data  = Data;

                  m_pPrev = m_pNext = NULL;

               }

               T m_Data;  /通用類型的數(shù)據(jù)元素

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

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

            };

            這樣,我們每個節(jié)點(diǎn)都可以使用通用的類型了,當(dāng)然,對節(jié)點(diǎn)的處理也一樣得處理。按照這個樣子將類型參數(shù)化(為節(jié)省篇幅,具體的實(shí)現(xiàn)部分請參考Exp04):

            template <typename T>

            class CDList

            {

            private:

            int     m_nMessage; // 消息號

            int     m_nCount; // 鏈表中 元素的數(shù)量

            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 );

            };

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

            2、  類模板的定義

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

            Template <typename T>

            Class 類名

            {

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

            }

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

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

            {

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

            //(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;

            }

            程序執(zhí)行結(jié)果:

            總結(jié):

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

            3、  類模板的特化

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

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

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

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

            template <>

            class CDList<CStudent>

            {

            private:

            int     m_nMessage; // 消息號

            int     m_nCount; // 鏈表中 元素的數(shù)量

            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>實(shí)現(xiàn)而拋棄編譯器為我們生成的CDList<CStudent>類。

            當(dāng)一個模板擁有多個模板參數(shù)時,如果我們只對其部分參數(shù)定做則稱為“局部定做”,這樣定做出來的“物件”仍然是一個模板,因?yàn)槲覀冎惶鼗艘徊糠帜0鍏?shù).

            說明:

            剛才,我們?yōu)?font face="Times New Roman">CStudent類定做的CDList模板類,其實(shí)我們沒有必要將整個CDList<CStudent>類都寫一遍,只需要重寫Add函數(shù)即可,例如:

            Template<>

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

            {

            ……

            }

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

            三、 模板程序的組織

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

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

            1、  包含方式

            本專題中,所有的實(shí)例代碼中的模板代碼都是以包含方式組織的。因?yàn)楹枚嗟木幾g器(如VC6cl)并不支持分離方式組織代碼,將代碼寫在頭文件也只是方便編譯器的預(yù)處理工作能方便的將.h文件中的代碼根據(jù)模板實(shí)參的類型生成相應(yīng)的模板類。

            當(dāng)然,將代碼都寫在頭文件中還有一點(diǎn)點(diǎn)小要求:

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

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

            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、  分離方式

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

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

            // .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 );

            };

            在實(shí)現(xiàn)文件(cpp文件)中。

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

            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;

            }

            .

            四、 學(xué)習(xí)小結(jié)

            經(jīng)過上面各個小節(jié)的學(xué)習(xí),我相信大家一定像我一樣,對模板有了一點(diǎn)認(rèn)識。大家也一定都知道,模板只是在編譯期間編寫,所有的代碼都只有效與編譯期。

            因此,模板的重載、特化等多態(tài)性也都是在編譯期間體現(xiàn)出來的,如果我們對編譯生成的可執(zhí)行文件進(jìn)行反匯編時,我們不會找到任何與模板有關(guān)的代碼,因?yàn)槟0逯皇蔷幾g期間的產(chǎn)物。

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

            因此本專題也只是帶領(lǐng)大家了解模板的基礎(chǔ)用法,關(guān)于模板的更多更深入知識,請參考 “模板元編程”相關(guān)內(nèi)容。

            Feedback

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

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

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

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

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

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

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

            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;
            }
            返回值應(yīng)該是T吧
            哈哈,小問題...

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

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

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

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

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

            2015-09-01 20:44 by 菜鳥
            Windows編程基礎(chǔ)是本實(shí)用的好書
            精品一久久香蕉国产线看播放| 91精品国产综合久久婷婷| 久久久久亚洲AV无码专区首JN| 99久久国产宗和精品1上映| 国产精品久久久久久影院| 久久久久99精品成人片三人毛片| 久久99精品国产麻豆宅宅| 国产精品久久久久久搜索| 伊人 久久 精品| 国产精品久久99| 久久青青草视频| 国产99精品久久| 国产精品久久久久蜜芽| 青青草国产精品久久久久| 久久亚洲AV成人无码软件| 亚洲国产天堂久久综合网站| 久久亚洲中文字幕精品一区| 色综合久久最新中文字幕| 99久久国产综合精品女同图片| 亚洲国产精品人久久| 无码国内精品久久人妻| 久久99精品国产麻豆不卡| 久久夜色精品国产噜噜噜亚洲AV| 久久九九久精品国产免费直播| 精品熟女少妇a∨免费久久| 日韩va亚洲va欧美va久久| 久久中文字幕一区二区| 久久久久久久女国产乱让韩| 久久成人国产精品一区二区| 精品久久久久久无码专区不卡| 亚洲精品国精品久久99热 | 久久经典免费视频| 99久久国产主播综合精品| 久久精品aⅴ无码中文字字幕不卡| 久久只有这里有精品4| 久久黄色视频| 国产精品美女久久久久av爽| 久久国产精品无码HDAV| 色成年激情久久综合| 久久亚洲国产成人精品性色| 久久精品国产99国产精品亚洲 |