C++之模板與泛型編程(一)——函數模板

假設我們希望編寫一個函數來比較兩個值,並指出第一個值是小於、等於還是大於第二個值。在實際中,我們可能就會想到定義多個重載函數:

//兩個值相等返回0,第一個值小於第二個值返回-1,第一個值大於第二個值則返回1.


這兩個函數幾乎是相同的,唯一的差異是參數的類型。

如果對每種希望比較的類型都不得不重複定義完全一樣的函數體,是非常繁瑣且容易出錯的。對於這樣的問題,我們可以定義一個通用的函數模板(function template),而不是爲每個類型都定義一個新函數。一個函數模板就是一個公式,可用來生成針對特定類型的函數版本:

template <typename T>
int compare(const T& a, const T& b)
{
	if(a < b)
		return -1;
	if(b < a)
		return 1;
	return 0;
}

泛型編程:編寫與類型無關的邏輯代碼,是代碼複用的一種手段。模板是泛型編程的基礎。


模板定義以關鍵字template開始,後跟一個模板參數列表列表,這是一個逗號分隔的一個或多個模板參數的列表,用<>包圍起來。類型參數前必須使用關鍵字typename或者class。在模板參數列表中,這兩個關鍵字的含義相同,可以互換使用。一個模板參數列表中可以同時使用這兩個關鍵字:

template <typename T, class U>

compare(const T&,  const U&);

模板參數表示在類或函數定義中用到的類型或值。當使用模板時,我們(隱式地或顯式地)指定模板實參,將其綁定到模板參數上。

模板函數也可以定義爲inline函數,inline關鍵字必須放在模板形參表後面,返回值之前,不能放在template之前。(模板是一個藍圖,它本身不是類或者函數)

1、實例化函數模板

當我們調用一個函數模板時,編譯器通常用函數實參來爲我們推斷模板實參。編譯器用推斷出的模板參數來爲我們實例化一個特定版本的函數。當編譯器實例化一個模板時,它使用實際的模板實參代替對應的模板參數來創建出模板的一個新實例。

以上編譯器就實例出了兩個不同版本的compare,對於第一個調用,編譯器會編寫並編譯一個T被替換成int的compare的版本;對於第二個調用,編譯器會生成另一個compare版本,其中T被替換成double。這些編譯器生成的版本通常被稱爲模板的實例。

模板直到實例化時纔會生成代碼,這一特性影響了我們何時纔會獲知模板內代碼的編譯錯誤。通常,編譯器會在三個階段報告錯誤:

第一個階段是在編譯模板本身時。在這個階段,編譯器通常不會發現很多的錯誤。編譯器可以檢查語法錯誤,例如忘記分量或者變量名拼錯等。

第二個階段是編譯器遇到模板使用時。對於函數模板調用,編譯器通常會檢查實參數目是否正確,它還能檢查參數類型是否匹配。

第三個階段是模板實例化時,只有這個階段才能發現類型相關的錯誤,依賴於編譯器如何管理實例化,這類錯誤可能在鏈接時才報告。

2、類型形參轉換

一般不會轉換實參以匹配已有的實例化,相反會產生新的實例。

編譯器只會執行兩種轉換:

1)const轉換:接收const引用或者const指針的函數可以分別用非const對象的引用或者指針來調用

2)數組或函數到指針的轉換:如果模板形參不是引用類型,則對數組或函數類型的實參應用常規指針轉換。數組實參將當做指向其第一個元素的指針,函數實參當做指向函數類型的指針。

3、實參推演

從函數實參確定模板形參類型和值的過程稱爲模板實參推演,多個類型形參的實參必須完全匹配。

4、模板形參說明

(1)模板形參表使用<>括起來

(2)和函數參數表一樣,跟多個參數時必須用逗號隔開,類型可以相同也可以不相同

(3)模板形參表不能爲空

(4)模板形參可以是類型形參,也可以是非類型新參,類型形參跟在class和typename後

(5)模板類型形參可作爲類型說明符用在模板中的任何地方,與內置類型或自定義類型使用方法完全相同,可用於           指定函數形參類型、返回值、局部變量和強制類型轉換

(6)模板形參表中,class和typename具有相同的含義,可以互換,使用typename更加直觀。typename是作爲             C++標準加入到C++中的,舊的編譯器可能不支持。

5、模板函數特化

有時候並不總是能夠寫出對所有可能被實例化的類型都最合適的模板,在某些情況下,同用模板定義對於某個類型可能是完全錯誤的,或者不能編譯,或者做一些錯誤的事情。


可以對模板進行特化:

(1)關鍵字template後面接一堆空的尖括號<>

(2)再接模板名和一對尖括號,尖括號中指定這個特化定義的模板形參

(3)函數形參表

(4)函數體    template<>

                       返回值 函數名<type>(參數列表)

                       {

                            // 函數體

                        }


特化的聲明必須與特定的模板相匹配。

注意:

非類型模板參數:除了定義類型參數,還可以在模板中定義非類型參數。一個非類型參數表示一個值而非一個類型。我們通過一個特定的類型名而非關鍵字class或者typename來指定非類型參數。

例如:我們可以編寫一個compare版本處理字符串字面值常量。這種字面值常量是const char的數組。由於不能拷貝一個數組,所以我們將自己的參數定義爲數組的引用。我們希望能比較不同長度的字符串字面值常量,因此爲模板定義了兩個非類型的參數。第一個模板參數表示第一個數組的長度,第二個參數表示第二個數組的長度:


編譯器就會實例化出這樣的版本:

int compare(const char (&p1)[3], const char (&p2)[4]);

一個非類型參數可以是一個整型,或者是一個指向對象或函數類型的指針或引用。綁定到非類型整型參數的實參必須是一個常量表達式,綁定到指針或引用非類型參數的實參必須靜態的生存期。我們不能用一個普通(非static)局部變量或動態對象作爲指針或引用非類型模板參數的實參。指針參數也可以用nullptr或者一個值爲0的常量表達式來實例化。

在模板定義內,模板非類型參數是一個常量值。在需要表達常量式的地方,可以使用非類型參數。例如,指定數組大小。


發佈了76 篇原創文章 · 獲贊 17 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章