青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

聚星亭

吾笨笨且懶散兮 急須改之而奮進
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編程基礎是本實用的好書
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            国产精品专区h在线观看| 亚洲国产精品99久久久久久久久| 一本久道久久综合婷婷鲸鱼| 欧美jizzhd精品欧美喷水| 欧美一区二区三区的| 国产精品视频免费观看| 午夜精品在线看| 小辣椒精品导航| 黑人巨大精品欧美一区二区| 麻豆国产精品777777在线| 久久综合图片| 99成人在线| 一区二区三区四区五区精品视频 | 亚洲日韩第九十九页| 美日韩精品视频免费看| 日韩视频欧美视频| 99re热这里只有精品免费视频| 欧美性猛交xxxx乱大交退制版| 欧美在线999| 久久中文精品| 亚洲视频专区在线| 欧美在线短视频| 亚洲黄色片网站| 一区二区三区视频在线看| 国产精品一级| 欧美大片第1页| 国产精品久久久久久久久婷婷 | 久久久噜噜噜久久久| 亚洲第一网站免费视频| 日韩视频免费观看| 国产亚洲一二三区| 亚洲激情一区二区三区| 久久综合色综合88| 91久久视频| 一区二区三区欧美| 在线观看亚洲视频| 夜夜嗨av色一区二区不卡| 国产乱码精品一区二区三区不卡| 一本一道久久综合狠狠老精东影业 | 国产精品区一区| 久久男女视频| 欧美理论电影网| 久久久精品国产99久久精品芒果| 另类人畜视频在线| 亚洲天堂成人| 久久久一区二区三区| 日韩手机在线导航| 性欧美8khd高清极品| 在线观看国产欧美| 日韩亚洲不卡在线| 狠狠色综合一区二区| 99国产精品99久久久久久| 国产精品丝袜久久久久久app| 欧美韩日亚洲| 国产精品美女www爽爽爽视频| 久久另类ts人妖一区二区| 欧美精品成人一区二区在线观看 | 国产有码在线一区二区视频| 狼人天天伊人久久| 国产精品热久久久久夜色精品三区| 欧美一区二区在线免费播放| 老司机免费视频久久| 99视频日韩| 久久精品人人做人人综合| 中文亚洲免费| 欧美日韩国产a| 亚洲福利小视频| 激情久久久久久| 久久精品三级| 久久亚洲综合| 激情文学综合丁香| 欧美在线视频一区| 久久超碰97人人做人人爱| 国产精品日韩| 亚洲一区二区免费看| 在线欧美视频| 久久偷看各类wc女厕嘘嘘偷窃| 久久精品视频免费播放| 国产欧美日韩视频| 亚洲资源在线观看| 亚洲精品欧洲精品| 久久精品视频亚洲| 蜜桃av噜噜一区| 国内精品久久久久国产盗摄免费观看完整版| 亚洲国产精品ⅴa在线观看| 欧美成人a∨高清免费观看| 久久婷婷亚洲| 亚洲第一精品在线| 久久久成人精品| 另类春色校园亚洲| 国语精品中文字幕| 久久国产精品99国产| 欧美在线一区二区三区| 国产一区白浆| 久久精品中文字幕一区| 狂野欧美激情性xxxx| 亚洲第一二三四五区| 麻豆精品一区二区综合av| 亚洲第一区中文99精品| 99精品热6080yy久久| 国产精品欧美日韩一区二区| 亚洲激情另类| 9i看片成人免费高清| 欧美精品在线视频观看| 久久综合伊人77777| 亚洲激情在线观看视频免费| 欧美激情视频一区二区三区免费| 亚洲欧洲日韩在线| 亚洲免费伊人电影在线观看av| 国产精品亚洲不卡a| 久久蜜桃资源一区二区老牛 | 欧美日韩美女在线| 亚洲综合三区| 欧美二区乱c少妇| 亚洲欧美久久久| 在线观看一区视频| 欧美午夜精品电影| 久久九九国产精品怡红院| 亚洲激情成人在线| 久久成人羞羞网站| 99精品国产一区二区青青牛奶| 国产精品高清一区二区三区| 久久精品日产第一区二区| 亚洲欧洲一区二区三区| 久久精品免费观看| 一本色道久久精品| 激情六月婷婷久久| 国产精品毛片| 欧美国产精品一区| 久久aⅴ国产欧美74aaa| 亚洲国产视频直播| 久久激情视频| 亚洲一区二区黄| 亚洲精品自在久久| 狠狠色丁香久久婷婷综合丁香 | 国产精品成人aaaaa网站| 老司机午夜精品视频| 亚洲欧美影院| 99热在线精品观看| 亚洲国产欧美久久| 欧美成人国产| 久久久噜噜噜久久| 欧美一区二区三区在线观看视频| 亚洲少妇最新在线视频| 亚洲国产老妈| 怡红院精品视频| 国产一区在线观看视频| 国产精品免费视频xxxx| 欧美日韩视频| 欧美日韩成人一区二区三区| 久久亚洲捆绑美女| 欧美在线视频免费观看| 亚洲中午字幕| 这里只有精品视频| 日韩午夜在线观看视频| 亚洲人成人一区二区在线观看| 免费成人小视频| 毛片一区二区三区| 久久综合九色综合欧美狠狠| 久久久水蜜桃| 老鸭窝毛片一区二区三区| 久久精品人人| 久久一区二区三区超碰国产精品| 久久不射网站| 久久午夜精品一区二区| 另类综合日韩欧美亚洲| 久久亚洲私人国产精品va媚药| 麻豆精品91| 欧美国产欧美亚州国产日韩mv天天看完整 | 亚洲国产人成综合网站| 亚洲第一偷拍| 亚洲美女网站| 亚洲综合国产激情另类一区| 亚洲欧美日韩国产成人精品影院| 亚洲欧美乱综合| 久久av红桃一区二区小说| 久久久美女艺术照精彩视频福利播放 | 亚洲剧情一区二区| 99在线精品视频在线观看| 99在线精品免费视频九九视| 在线亚洲成人| 久久成人18免费网站| 久久躁狠狠躁夜夜爽| 嫩草国产精品入口| 欧美日韩精品三区| 国产精品一二| 伊大人香蕉综合8在线视| 亚洲人成人77777线观看| 亚洲午夜视频在线观看| 久久精品国产清高在天天线| 欧美成人精品不卡视频在线观看| 亚洲国产欧美日韩| 亚洲在线不卡| 久久天天狠狠| 欧美日本在线看| 黄色日韩精品| 亚洲午夜国产一区99re久久| 久久久.com| 亚洲精品国产精品久久清纯直播 | 日韩午夜电影在线观看|