c++中模板的知識梳理

模板作用

在C++中模板是泛型編程的一種體現,是採用無類型的邏輯代碼編寫。其作用就是達到代碼的複用從而減少開發人員寫重複的代碼。

模板的分類

模板分類,大的方面分爲模板函數和模板類。

模板函數

在時間開發過程中很有可能同一塊代碼,但是用了不同的類型,那麼我們就必須每個類型都要寫一份代碼,爲了解決這個問題,引入了模板函數。
具體是什麼樣子呢?

在模板泛型編程中引入一個新的關鍵字,template關鍵字。用法如下:

template<class T>
void function(){}

這就是一個模板函數。當然這只是一個簡單的例子,我們先來解釋一下,template我們已經說過是一個關鍵字,就是爲了聲明成一個模板函數,<>中的class是什麼,T又是什麼?class是聲明T類型的關鍵字,class也可以換成typename,而T就是下面function函數中的類型,這裏的 T 不是固定的,也可以定義成自己喜歡的,但是大多數情況下,都是定義成T帶type類型這個單詞。
模板函數的具體的格式:

template<class 形參名1, class 形參名2, ...> //也就是對應的類型
void 函數名(參數列表)
{...}

在使用的過程中,我們就可以像使用普通函數一樣,進行傳參數,但是必須與聲明的參數名相匹配。

函數模板的顯示實例化

怎麼理解呢?

template<class T>
void fun(T& a, T& b){}

這個時候就不能傳參是時候傳 fun(2 , 2.2); 這樣做的會導致與模板的參數不匹配,因爲我們在模板中只聲明瞭一個參數類型T,而我們傳參數的時候一個是int一個是float類型,從而編譯器不能推演出T的類型,所以我們這時就有一個顯示實例話。
就是在傳參的時候顯示的加上類型,如:

fun<int>(2, 2.2);

這裏就說明我們要傳的參數就是int型參數。在以後的我們最好使用顯示的實例化傳參數。

函數模板的重載

重載是在函數調用中,在同一作用域中兩個具有相同的函數名字,但是函數參數或者返回值不同的函數,
那麼在模板函數中也有重載,主要是模板函數的模板參數不同,怎麼理解?

template<class T1, class T2>
void fun(T1& a, T2& b){}

這個函數就和上面的fun在同一作用域中就形成了模板函數的重載。
這個時候顯示實例化就是

fun<int, double>(1,2.2);

非類型的模板函數參數

什麼是非類型的模板參數,非類型的模板參數就是模板參數的其中一個爲給定的參數,並且知道類型可以給一個默認的參數(即缺省參數),而且在這個值初始化後,就不然能修改。這個就有一個好處,當我們在用靜態順序表的時候,不知到要開多大的空間,所以我們把這個可以讓調用者來給定來定,所以非類型的模板函數參數,當給定初始化值後就不能被改變。
例如:

template<class T, int VALUE>
T fun(T a)
{
    return a+value;
}

這就是當我們調用的時候自己給定value的值。

fun<int, 10>(20); //調用

模板類

前面有了模板函數的鋪墊那麼我們看模板類就很相對來說比較容易。
模板類就是在實現一個類的時候實現泛型的類型,意思就是實現的的類與類型無關,是一個與類型無關的邏輯代碼。
作用也是爲了進行代碼的複用,可以在不同的類型下用到。
我們先舉個例子來看模板類

template<class T>
class Date
{
public:
    Date();
    ~Date();
private:
    T year;
    T month;
    T day;
};

這是就是一個很簡單的例子,我們實例化的時候就可以

// 這裏Date<int>是一個類型,所以必須要加上<int>編譯器纔會推演出類型
Date<int> d; 

在C++中類中的成員函數可以在類外實現,所以類模板也不例外,但是在類外是實現類中的成員,有一下要注意的幾點:
1)要在實現的成員函數的前面加上template<…>
2)在類外寫成員函數時,需要加域,注意這裏的域是類加類型。
例如:我們要實現上面Date的構造函數,就要寫成

template<class T>
Data<int>::Date(){}

模板參數

模板參數是什麼?模板參數就是把一個模板類的參數定義成了另一個模板類。
例如:我們在實現順序表的時候,用一個模板類,實現好了以後,又需要實現一個棧的數據結構,那麼我們可以利用順序表的數據結構,實現一個容器適配器,也就是把順序表當一個類型,然後爲棧模板類的參數。
如:

template<class T, class Container = SeqList<T> >
class Stack
{
public:
    void Push_back();
    void Pop_back();
    const T& Top();
private:
    Container con; // 將其定義爲這個類的成員
};

這樣我們要怎麼實例化呢?

Stack<int, SeqList<int> > s; //在類中T 現在給定的類型就是int

在上面的T 和SeqList< T >是一種類型。
但是在用的時候如果不注意把傳參數的兩個寫成不一樣的結果就顯然不正確了。

模板的模板參數

模板的模板參數就是爲了解決上面的問題,但是也帶來問題,導致模板類的可讀性降低。
我們就在上面的代碼上做出簡單的改進

// 與上面相比後面的沒有了T
template<class T, template<class>class Container = SeqList > 
class Stack
{
public:
    void Push_back();
    void Pop_back();
    const T& Top();
private:
    Container<T> con; // 在這裏加上了T
};

這個實例化就是

Stack<int, SeqList > s; // 只寫出容器適配的類名字,不寫T

非類型的類模板參數

在函數模板中有非類型的函數模板參數,現在是在類中,但是用法和原理大同小異,沒有什麼特別大的區別。

類模板的特化

模板的特化就是在類的模板的基礎上加上,給不同的類型進行一些特殊的處理代碼,怎麼理解?

就是我們產生模板類就是爲了能達到代碼的複用,但是在不同的類型中,我們爲了提高效率,有些類的類型用一種更高效的方法實現,這樣就可以提高效率,所以我們就用模板類的特化來實現。
那麼模板的特化怎麼實現的呢?就是在模板類爲原生模板的基礎上實現特化。

模板類中又分爲全特化和偏特化(局部特化)

全特化

模板類中全特化舉例子:

template<class T>
class Date
{
public:
    Date();
    ~Date();
private:
    T year;
    T month;
    T day;
};

全特化

template<>
class Date<int> // 在這裏加上特化後的類型
public:
    Date();
    ~Date();
private:
    int year; // 這裏也就直接用特化的類型
    int month;
    int day;
};

特化後在類外寫成員函數就不需要寫模板參數。

局部特化

我們來類比一下全特化,局部特化從表面意思上就可以知道,在多個模板參數的時候,我們可以特化其中一個參數,這就是局部特化。
我們就拿上面的例子來講

template<class T1, class T2>
class Date
{
public:
    Date();
    ~Date();
private:
    T2 year;
    T1 month;
    T1 day;
};

偏特化

template<class T1>
class Date<T1,int> // 在這裏加上特化後的類型
public:
    Date();
    ~Date();
private:
    int year; // 這裏也就直接用特化的類型
    T1 month;
    T1 day;
};

最後還有一點要注意就是在模板中,如果沒有進行調用,編譯器不會對其進行語法檢查,如果有調用,那麼編譯器纔會根據類型進行生成一份代碼。

還有就是要在編寫模板的時候,不能把定義與聲明放在兩個不同的文件中,這樣編譯器會在編譯完成後,在鏈接過程中會產生鏈接錯誤。

在模板類的特化中,如果在調用的類型是模板類特化的,那麼就會直接去特化的類,編譯器不會再生成一份代碼。順序是有全特化找全特化,沒有看局部特化,沒有才去生成一份模板類的代碼。

如有錯誤,還望指正,謝謝!

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