C++模板解析

要使參數類型不同的函數實現相同的功能,我們可以選擇函數重載的方式,但函數重載大部分代碼是相同的,每多一個類型就得多實現一次函數重載,也就造成了編程效率的損失,我們可以通過泛型編程來解決這個問題。
泛型編程:編寫與類型無關的邏輯代碼,是代碼複用的一種手段。模板是泛型編程的基礎。
模板分爲函數模板和模板類。
函數模板:代表了一個函數家族,該函數與類型無關,在使用時被參數化,根據實參類型產生函數的特定類型版本。
模板類也是模板,必須以關鍵字template開頭,後接模板形參表。
函數模板的格式:

template<typename Param1, typename Param2,...,class Paramn>
返回值類型 函數名(參數列表)
{
函數體
}

其中typename和class是關鍵字,class可以用typename 關鍵字代替,在這裏typename 和class沒區別,<>括號中的參數叫模板形參,模板形參不能爲空。
模板函數也可以定義爲inline函數,但inline關鍵字必須放在模板形參表之後,返回值之前,不能放在template之前。

template<typename T>
inline T Add(const T _left, const T _right)
{
return (_left + _right);
}

編譯器用模板產生指定的類或者函數的特定類型版本,產生模板特定類型的過程稱爲函數模板實例化。
模板被編譯了兩次:
1.實例化之前,檢查模板代碼本身,查看是否出現語法錯誤,如:遺漏分號。
2.在實例化期間,檢查模板代碼,查看是否所有的調用都有效,如:實例化類型不支持某些函數調用。

template<typename T>
T Add(T left, T right)
{
    return left + right;
}
int main()
{
    cout << Add(10, 20) << endl;
    cout << Add(10.0, 20.0) << endl;
    cout << Add(10, (int)20.0) << endl;
    cout << Add<int>(10, 20.0) << endl;
    return 0;
}

實參推演:從函數實參確定模板形參類型和值的過程稱爲模板實參推斷多個類型形參的實參必須完全匹配。
在上面的四個輸出中,第1,3,4均調用的是int型函數,但理由不同,第1個輸出本身2個參數都是int型,匹配int型函數,第3個輸出2個參數本身一個爲int型,一個爲double型,但double型進行了強制類型轉換轉換爲int型,第4個是將參數類型全部強制轉換爲int型,而第2個因2個參數均爲double型,調用double型函數。第1,2種輸出調用的函數與本身參數類型完全匹配,稱爲隱式實例化,而第3,4種輸出調用的函數是本身參數類型經過強制轉換後的類型,稱爲顯式實例化。

int Add(int left, int right)
{
    return left + right;
}
double Add(double left, double right)
{
    return left + right;
}

編譯器只會執行兩種轉換:
1、const轉換:接收const引用或者const指針的函數可以分別用非const對象的引用或者指針來調用
2、數組或函數到指針的轉換:如果模板形參不是引用類型,則對數組或函數類型的實參應用常規指針轉換。數組實參將當做指向其第一個元素的指針,函數實參當做指向函數類型的指針。

函數模板有兩種類型參數:模板參數和調用參數
模板形參的名字在同一模板形參列表中只能使用一次

template<typename T, typename T>
//如這裏使用了兩次T就是錯誤的,正確用法爲只保留其中一個
T Add(T left, T right)
{
    return left + right;
}

所有模板形參前面必須加上class或者typename關鍵字修飾

template<typename T1T2>
//如這裏要定義兩種不同的類型T1T2,每個類型前面都必須加上class或者typename關鍵字修飾
T Add(T1 left, T2 right)
{
    return left + right;
}

注意:在函數模板的內部不能指定缺省的模板實參。

模板形參說明:
1、模板形參表使用<>括起來。
2、和函數參數表一樣,跟多個參數時必須用逗號隔開,類型可以相同也可以不相同。
3、定義模板函數時模板形參表不能爲空。
4、模板形參可以是類型形參,也可以是非類型新參,類型形參跟在class和typename後。
5、模板類型形參可作爲類型說明符用在模板中的任何地方,與內置類型或自定義類型。使用方法完全相同,可用於指定函數形參類型、返回值、局部變量和強制類型轉換。
6、模板形參表中,class和typename具有相同的含義,可以互換,使用typename更加直觀。但關鍵字typename是作爲C++標準加入到C++中的,舊的編譯器可能不支持。

//模板函數的重載:
int Max(const int& left, const int & right)
{
return left>right? left:right;
}
template<typename T>
T Max(const T& left, const T& right)
{
return left>right? left:right;
}
template<typename T>
T Max(const T& a, const T& b, const T& c)
{
return Max(Max(a, b), c);
};
int main()
{
Max(10, 20, 30);
Max<>(10, 20);
Max(10, 20);
Max(10, 20.12);
Max<int>(10.0, 20.0);
Max(10.0, 20.0);
return 0;
}

注意:函數的所有重載版本的聲明都應該位於該函數被調用位置之前。
說明:
1、一個非模板函數可以和一個同名的函數模板同時存在,而且該函數模板還可以被實例化爲這個非模板函數。
2、對於非模板函數和同名函數模板,如果其他條件都相同,在調動時會優先調動非模板函數而不會從該模板產生出一個實例。如果模板可以產生一個具有更好匹配的函數,那麼將選擇模板。
3、顯式指定一個空的模板實參列表,該語法告訴編譯器只有模板才能來匹配這個調用,而且所有的模板參數都應該根據實參演繹出來。
4、模板函數不允許自動類型轉換,但普通函數可以進行自動類型轉換。

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

模板函數特化形式如下:
template<>
返回值 函數名<Type>(參數列表)
{
 函數體
}
//如比較P1,P2指針所指向值的大小寫法爲
template<>
int Compare<const char*>(const char* const p1,const char* const p2)
{
  return strcpy(p1,p2);
}

注意:1.在模板特化版本的調用中,實參類型必須與特化版本函數的形參類型完全匹配,如果不匹配,編譯器將爲實參模板定義中實例化一個實例。
2.特化不能出現在模板實例的調用之後,應該在頭文件中包含模板特化的聲明,然後使用該特化版本的每個源文件包含該頭格式。

模板類格式:
template <class 形參名1, class 形參名2, ...class 形參名n>
class 類名
{ ... };
//用模板類定義順序表
template<typename T>
class SeqList
{
private :
T* _data ;
int _size ;
int _capacity ;
};
// 以模板類方式實現動態順序表
template<typename T>
class SeqList
{
public :
SeqList();
~ SeqList();
private :
int _size ;
int _capacity ;
T* _data ;
};
template <typename T>
SeqList <T>:: SeqList()
: _size(0)
, _capacity(10)
, _data(new T[ _capacity])
{}
template <typename T>
SeqList <T>::~ SeqList()
{
delete [] _data ;
}
void test1()
{
SeqList<int > sl1;
SeqList<double > sl2;
}
//模板類的實例化
//只要有一種不同的類型,編譯器就會實例化出一個對應的類。
//SeqList<int> sl1;
//SeqList<double> sl2;
//當定義上述兩種類型的順序表時,編譯器會使用int和double分別代替模/形參,重新編寫SeqList類,最後創建名爲SeqList<int>和SeqList<double>的類。
//模板類全特化:
template <typename T>
class SeqList
{
public :
SeqList();
~ SeqList();
private :
int _size ;
int _capacity ;
T* _data ;
};
//模板類全特化爲int型,特化後定義成員函數不再需要模板形參
SeqList <int>:: SeqList(int capacity)
: _size(0)
, _capacity(capacity )
, _data(new int[ _capacity])
{
cout<<"SeqList<int>" <<endl;
}

類模板全特化限定死類模板實現的具體類型,類模板偏特化就是如果這個類模板有多個類型,那麼只限定其中的一部分。

//模板類偏特化(局部特化):
template <typename T1, typename T2>
class Data
{
public :
Data();
private :
T1 _d1 ;
T2 _d2 ;
};
// 局部特化第二個參數爲int型
template <typename T1>
class Data <T1, int>
{
public :
Data();
private :
T1 _d1 ;
int _d2 ;
};
// 局部特化第一個參數爲引用
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public :
Data(const T1& d1, const T2& d2);
private :
const T1 &_d1;
T2 _d2;

模板類的全特化和偏特化都是在已定義的模板基礎之上,不能單獨存在。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章