定義模板——函數模板和類模板


面向對象編程(OOP)和泛型編程都能處理在編寫程序時不知道類型的情況。不同之處在於:OOP能處理類型在程序運行之前都未知的情況;而在泛型編程中,在編譯時就能獲知類型了

前面介紹的容器、迭代器和算法都是泛型編程的例子。當我們編寫一個泛型程序時,是獨立與任何特定類型來編寫代碼的。當使用一個泛型程序時,我們提供類型或值,程序實例可在其上運行。

模板是泛型編程的基礎。一個模板就是一個創建類或函數的藍圖或者說公式。當使用一個vector這樣的泛型類型,或者find這樣的泛型函數時,我們提供足夠的信息,將藍圖轉換為特定的類型函數。這種轉換發生在編譯時。

定義模板

假定我們希望編寫一個函數來比較兩個值,並指出第一個值是小於、等於還是大於第二個值。在實際中,我們可能想要定義多個函數,每個函數比較一個給定類型的值。我們的初次嘗試可能定義多個重載函數:

int compare(const string &v1,const string &v2)
{
    if(v1<v2) return -1;
    if(v1>v2) return 1;
    return 0;
}

int compare(const double &v1,const string &v2)
{
    if(v1<v2) return -1;
    if(v1>v2) return 1;
    return 0;
}

這兩個函數幾乎相同,唯一的差別是參數的類型,函數體則完全一樣。

如果對每種希望比較的類型都不得不重復定義完全一樣的函數體,是非常煩瑣且容易出錯的。更麻煩的是,在編寫程序的時候,我們就要確定可能要compare的所有類型。

函數模板

我們可以定義一個通用的函數模板,而不是為每個類型都定義一個新函數。一個函數模板就是一個公式,可用來生成針對特定類型的函數版本。compare的模板版本可能像下面這樣:

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

模板定義以關鍵字template開始,后跟一個模板參數列表,這是一個逗號分隔的一個或多個模板參數的類表,用小於號(<)和大於號(>)包圍起來。

在模板定義中,模板參數列表不能為空

模板參數列表的作用很像函數參數列表。函數參數列表定義了若干特定類型的局部變量,但並未指出如何初始化它們。在運行時,調用者提供實參來初始化形參。

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

我們的compare函數聲明了一個名為T的類型參數。在compare中,我們用名字T表示一個類型。而T 表示的實際類型則在編譯時根據compare的使用情況來確定。

實例化函數模板

當我們調用一個函數模板時,編譯器(通常)用函數實參來為我們推斷模板實參。即,當我們調用compare時,編譯器使用實參的類型來確定綁定到模板參數T的類型。例如,在下面的調用中:

cout<<compare(1,0)<<endl;  //T為int

實參類型是int。編譯器會推斷出模板實參為int,並將它綁定到模板參數T。

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

例如,給定下面的調用:

//實例化出int compare(const int&,const int&)

cout<<compare(1,0)<<endl; //T為int

//實例化出 int compare(const vector<int>&,const vector<int>&)

vector<int> vec1(1,2,3),vec2(4,5,6);

cout<<compare(vec1,vec2)<<endl; //T為vector<int>

編譯器會實例化出兩個不同版本的compare。對於第一個調用,編譯器會編寫並編譯一個compare版本,其中T被替換為int:

int compare(const int &v1,const int &v2)
{
    if(v1<v2) return -1;
    if(v1>v2) return 1;
    return 0;
}

對於第二個調用,編譯器會生成另一個compare版本,其中T被替換為vector<int>,這些編譯器生成的版本通常被稱為模板的實例。

模板類型參數

我們的compare函數有一個模板類型參數,一般來說,我們可以將類型參數看作類型說明符,就像內置類型或類類型說明符一樣使用。特別是,類型參數可以用來指定返回類型或函數的參數類型,以及在函數體內用於變量聲明或類型轉換。

類型參數必須使用關鍵字class或typename:

//錯誤:U之前必須加上class或typename

template <typename T,U> T calc(const T&,const U&);

在模板參數列表中,這兩個關鍵字的含義相同,可以相互轉換使用。一個模板參數列表中可以同時使用這兩個關鍵字:

//正確:在模板參數列表中,typename和class沒有什么不同

template<typename T,class U> calc(const T&,const U&);

看起來用關鍵字typename來指定模板類型參數比用class更為直觀。畢竟,我們可以用內置(非類)類型作為模板類型實參。而且,typename更清楚地指出隨后的名字是一個類型名。

 

非類型模板參數

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

當一個模板被實例化時,非類型參數被一個用戶提供的或編譯器推斷出的值所代替。這些值必須是常量表達式,從而允許編譯器在編譯時實例化模板。

 


免責聲明!

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



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