模板

在我們編寫代碼時,我們會遇見這種情況:
比如交換函數(shù),當(dāng)我們要交換的類型是int(傳的參數(shù)為int型)時,我們要編寫的swap函數(shù)的形參就應(yīng)該是int,但當(dāng)我們要交換的是double型時,我們還要再寫一個swap函數(shù)來滿足要求。每換一種類型就要再重載一個swap函數(shù)來滿足條件。
雖然通過這方法重載實(shí)現(xiàn)所有類型的交換函數(shù),但是這種方法有幾個不好的地方,一是重載函數(shù)僅僅類型不同,導(dǎo)致代碼的復(fù)用率很低,只要有新類型出現(xiàn),就要增加對應(yīng)的函數(shù);再者代碼的可維護(hù)性比較低,一個出錯可能所有的重載都出錯,要一個一個改。

興寧ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為創(chuàng)新互聯(lián)的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:18980820575(備注:SSL證書合作)期待與您的合作!

通過上面的例子,我們想能不能告訴編譯器一個模子,編譯器可以通過不同的類型利用這樣的模子自動生成適合各種類型的函數(shù)。答案是當(dāng)然可以
即泛型編程:編寫與類型無關(guān)的通用代碼,而模板是泛型編程的基礎(chǔ)。

下面我們來鄭重的引入模板

模板分為函數(shù)模板和類模板

函數(shù)模板:

  • 什么是函數(shù)模板?
    函數(shù)模板是一個與類型無關(guān),并且對所有類型都適用的函數(shù),在使用時函數(shù)可被參數(shù)化,根據(jù)實(shí)參類型結(jié)合模板產(chǎn)生函數(shù)的特定類型版本實(shí)現(xiàn)函數(shù)功能。
  • 如何使用?
    template <typename T1,typename T2...>
    返回值 函數(shù)名(參數(shù)列表){ }
    typename是用來定義模板參數(shù)關(guān)鍵字的,也可以用class
    例如:

    template<typename T>   
    void Swap(T &x, T &y)      
    //之前不同的類型,對應(yīng)不同的形參列表,所以要寫多個重載函數(shù),但現(xiàn)在只寫一個模板函數(shù),編譯器就可以根據(jù)這個模板結(jié)合傳入的參數(shù)就可完成所有類型的生成對應(yīng)類型的函數(shù)以供調(diào)用
    
    T tmp = x;
    x = y;
    y = tmp;
    }
    //在這個函數(shù)中,T只能被替換為一樣的類型,若傳入?yún)?shù)不同,則編譯器其則生成不了匹配的函數(shù),而編譯器又不會進(jìn)行類型轉(zhuǎn)換,因而會報錯
    //若想要不同類型,可定義兩個T
    template<class T1, class T2>       //typename可用class代替
    void Swap(T1 &x, T2 &y)     
    {
    T1 tmp = x;
    x = y;
    y = tmp;
    }
    int main()
    {
    int x1=0, y1=1;
    double x2 = 1.0, y2 = 2.0;
    Swap(x1, y1);
    Swap(x2, y2);
    Swap(x1, y2);
    }
  • 函數(shù)模板原理
    其實(shí)模板本身并不是函數(shù),而是編譯器用使用方式產(chǎn)生特定具體類型函數(shù)的模具。所以其實(shí)模板就是將本來應(yīng)該我們做的重復(fù)的事情交給了編譯器。在編譯器編譯階段,編譯器會根據(jù)傳入的實(shí)參類型來推演生成對應(yīng)類型的函數(shù)以供調(diào)用。比如:當(dāng)用double類型使用函數(shù)模板時,編譯器通過對實(shí)參類型的推演,將T確定為double類型,然后產(chǎn)生一份專門處理double類型的代碼,對于字符類型也是如此。
  • 函數(shù)模板實(shí)例化
    編譯器根據(jù)不同的參數(shù)用模板推演不同的函數(shù)稱為函數(shù)模板的實(shí)例化,模板參數(shù)實(shí)例化分為:隱式實(shí)例化和顯示實(shí)例化。
    通過下面例子介紹隱式實(shí)例化和顯示實(shí)例化

    template<typename T>
    T Add(const T &x, const T &y)
    {
    return x + y;
    }
    int main()
    {
    int x1=0, y1=1;
    double x2 = 1.0, y2 = 2.0;
    //隱式實(shí)例化,讓編譯器根據(jù)實(shí)參推演模板參數(shù)的實(shí)際類型
    Add(x1, y1);     //隱式實(shí)例化
    
    //當(dāng)無法確定T是什么,由該推演成什么的時候,會報錯,因而要不然進(jìn)行強(qiáng)轉(zhuǎn)使傳入?yún)?shù)類型相同,要不然進(jìn)行顯式實(shí)例化
    Add(x1, (int)y2); //強(qiáng)轉(zhuǎn)
    
    //顯式實(shí)例化,直接告訴編譯器,由模板該推演成什么,在函數(shù)名后的<>中指定模板參數(shù)的實(shí)際類型
    Add<int>(x1, (int)y2);   
    }
  • 模板參數(shù)匹配原則

1.一個非模板函數(shù)可以和一個同名的函數(shù)模板同時存在,而且該函數(shù)模板還可以被實(shí)例化為這個非模板函數(shù)。
2.模板函數(shù)不允許自動類型轉(zhuǎn)換,但普通函數(shù)可以進(jìn)行自動類型轉(zhuǎn)換。
3.對于非模板函數(shù)和同名函數(shù)模板,如果其他條件都相同,在調(diào)動時會優(yōu)先調(diào)用非模板函數(shù)而不會從該模板產(chǎn)生出一個實(shí)例。如果模板可以產(chǎn)生一個具有更好匹配的函數(shù),那么將選擇模板。

int Add(int x, int y)
{
    return x + y;
}
template<typename T>
T Add(const T &x, const T &y)
{
    return x + y;
}
template<typename T1, typename T2>
T1 Add(const T1 &x, const T2 &y)
{
    return x + y;
}

int main()
{
//1
    Add(1, 2);    //調(diào)用非模板函數(shù),無需模板實(shí)例化
    Add<int>(1, 2);//調(diào)用編譯器特化的模板函數(shù)版本   --如果指定類型就必須用模板來生成相應(yīng)類型
    //2.
    Add(1, 2.0);      //此處會調(diào)用非模板函數(shù),發(fā)生隱式類型轉(zhuǎn)換   (說明:此處還未加Add(const T1 &x, const T2 &y)函數(shù)模板)
    //3.
    Add(1, 2.0);        //選擇函數(shù)模板若選擇非模板函數(shù),則會發(fā)生隱式類型轉(zhuǎn)化,
                        //不如調(diào)用Add(const T1 &x, const T2 &y)這個實(shí)例化的函數(shù)形參列表更匹配

}

類模板

  • 使用格式:
    template <class T1,class T2....>
    class A( /A為類模板名,A不是具體的類,是編譯器根據(jù)被實(shí)例化的類型生成具體類的模具)
    {
    ...
    };

  • 類模板實(shí)例化

類模板實(shí)例化需要在類模板名字后跟<>,然后將實(shí)例化的類型放在<>中即可,類模板名字不是真正的類,而實(shí)例化的結(jié)果才是真正的類。
例如容器vector的實(shí)現(xiàn):

template <class T>
class vector{
    typedef T* iterator;
    public:
        //構(gòu)造析構(gòu)
        vector()
            : _start(nullptr)
            , _finish(nullptr)
            , _endOfStorage(nullptr)
        {
        }
        vector(int n, const T &value = 0)
            :_start(nullptr)
            , _finish(nullptr)
            , _endOfStorage(nullptr)
        {
            _start = new T[n+1];
            int i = n;
            while (i--)
            {
                _start[i] = value;
            }
            _start[n] = '\0';
            _finish = _start + n;
            _endOfStorage = _finish;
        }
        template <class InputIterator>
        vector(InputIterator first, InputIterator last)    
            :_start(nullptr)
            , _finish(nullptr)
            , _endOfStorage(nullptr)
        {
            int n=0;
            auto tmp = first;
            while (tmp != last)                
            {
                n++;
                tmp++;
            }
            _start = new T[n + 1];
            _finish = _start + n;
            _endOfStorage = _finish;

            last--;
            while (n--)
            {
                _start[n] = *last;
                last--;
            }
        }
        vector(const vector<T>& v)
            :_start(nullptr)
            , _finish (nullptr)
            ,_endOfStorage(nullptr)
        {
            reserve(v.capacity());
            //memcpy(_start, v._start,v.size());
            for (int i = 0; i < v.size(); i++)
            {
                _start[i] = v._start[i];
            }
            _finish = _start + v.size();
            _endOfStorage = _start + v.capacity();
        }
        ~vector()
        {
            delete[] _start;
            _start = nullptr;
            _finish = nullptr;
            _endOfStorage = nullptr;
        }
        iterator begin()
        {
            return _start;
        }
        iterator end()
        {
            return _finish;
        }

        int size()const
        {   
            return _finish - _start;
        }
        int capacity()const
        {
            return  _endOfStorage - _start;
        }

friend iterator find(iterator begin, iterator end, const T &value);
    private:
        T *_start;
        T *_finish;
        T *_endOfStorage;

    };
// 注意:類模板中函數(shù)放在類外進(jìn)行定義時,需要加模板參數(shù)列表
    template <class T>
    T* find(T* begin, T* end, const T &value)
    {
        auto it = begin;
        while (it != end)
        {
            if (*it == value)
            {
                return it;
            }
            it++;
        }
        return nullptr;
    }
int main(){
vector<int> v1;   //實(shí)例化
}

模板的其他知識說明:

模板參數(shù)

模板參數(shù)分為類型形參與非類型形參
類型形參:出現(xiàn)在模板參數(shù)列表中,跟在class或者typename之類的參數(shù)類型名稱。
非類型形參,就是用一個常量作為類(函數(shù))模板的一個參數(shù),在類(函數(shù))模板中可將該參數(shù)當(dāng)成常量來使用。

例如:
namespace Eg
{
    template <class T,size_t N=10>     //N在下面類中,作為常數(shù)使用
    class array{
    public:
        size_t size()const
        {
            //N = 20;      //會報錯:錯誤 1   error C2106: “=”: 左操作數(shù)必須為左值,可證明N是常數(shù)
            return _size;
        }
    private:
        T _array[N];
        size_t _size;
    };
}
void Test1(){
    Eg::array<int> x;     
    x.size();
}
  • 注:
    1.浮點(diǎn)數(shù)、類對象以及字符串是不允許作為非類型模板參數(shù)
    Eg::array<int, 2.0> x3; //這樣創(chuàng)建對象會報錯
    2.非類型模板參數(shù)要在編譯期就能確認(rèn)結(jié)果,比如1+2可以在編譯時確認(rèn),而變量a+變量b不可以確認(rèn)因而會報錯
    Eg::array<int, 2+1> x2; //√
    //int a = 1, b = 2;
    //Eg::array<int, a+b> x3; //×

模板特化

對于一些特殊的類型(比如指針類型),使用已寫的模板可能達(dá)不到我們想要的結(jié)果,得出錯誤的結(jié)果。
例如:

template <class T>
T& MAX_T(T& left, T& right)
{
    return left>right?left:right;
}
void Test(){
    int x = 2,y=3;
    cout << MAX_T(x, y) << endl;  
    char *p1 = "wello";
    char *p2 = "hello";
    cout << MAX_T(p1, p2) << endl;   //應(yīng)該輸出wello  但因為比較的是地址卻輸出hello不符合邏輯,因此我們要為char*來特化一個模板提供給這種類型
}

通過上述列子我們可以通過對模板進(jìn)行特化,在原來模板函數(shù)(或類)的基礎(chǔ)上,針對特殊類型進(jìn)行特殊化的實(shí)現(xiàn)。比如上述例子中為char*特化一個函數(shù),按照我們的想法針對這種類型進(jìn)行特殊化處理,得到正確的結(jié)果。

模板特化又分為函數(shù)模板特化與類模板特化

函數(shù)模板特化

函數(shù)模板的特化方式:
1.要先有一個基礎(chǔ)函數(shù)模板
2.關(guān)鍵字template后面接一堆空的尖括號<>
3.函數(shù)名后跟一對尖括號里面放需要特化的類型
4.函數(shù)形參表必須要和模板函數(shù)的基礎(chǔ)參數(shù)類型完全相同,不同的話編譯器可能會報一些錯誤

//比如針對上述char*類型特化:
template <class T>
T& MAX_T(T& left, T& right)
{
    return left>right?left:right;
}
template <>
char*& MAX_T<char*>(char*& left, char*& right)
{
    if (strcmp(left, right) == 1)
    {
        return left;
    }
    else{
        return right;
    }
}
void Test(){
    char *p1 = "wello";      
    char *p2 = "hello";
    cout << MAX_T(p1, p2) << endl;   
    //會調(diào)用char*& MAX_T<char*>(char*& left, char*& right)特化模板函數(shù)
}

但是!!
對于特例化的函數(shù)模板,一般都是將該函數(shù)直接給出,一是實(shí)現(xiàn)簡單,二是因為函數(shù)模板可能會遇到不能處理或者處理有誤的類型

類模板特化

類模板特化又分為全特化和偏特化

  1. 全特化:即將模板參數(shù)列表中所有的參數(shù)都確定了
template<class T1, class T2>
class Data1
{
public:
    Data1() { cout << "Data<T1, T2>" << endl; }
private:
    T1 _d1;
    T2 _d2;
};

template<>
class Data1<int, int>
{
public:
    Data1()
    {
        cout << "Data1<int, int>" << endl;
    }
private:
    int _d1;
    int _d2;
};

void Test(){
    Data1<int, int> d1;      //用特化模板參數(shù)類
    Data1<int, double> d2;
}

2.偏特化:有兩種表現(xiàn):部分特化和參數(shù)更進(jìn)一步的限制

template<class T1, class T2>
class Data2
{
public:
    Data2() { cout << "Data2<T1, T2>" << endl; }
private:
    T1 _d1;
    T2 _d2;
};
  • 部分特化:將模板參數(shù)列表中部分參數(shù)類型化
template<class T1>
class Data2<T1, int>    //特化一半
{
public:
    Data2()
    {
        cout << "Data2<T1, int>" << endl;
    }

private:
    T1 _d1;
    int _d2;
};
void Test(){                     
    Data2<int, int> d1;      //用部分特化模板參數(shù)類
    Data2<double, int> d2;   //用部分特化模板參數(shù)類     因為后面的都是int,都符合這個部分特化模板
    Data2<int, double> d3;
    Data2<double, double> d4;
    }
  • 參數(shù)更進(jìn)一步的限制:讓模板參數(shù)列表中的類型限制更加嚴(yán)格
template<class T1, class T2>
class Data2<T1*, T2*>
{
public:
    Data2()
    {
        cout << "Data2<T1*, T2*>" << endl;
    }
private:
    T1* _d1;
    T2* _d2;
};
void Test(){                     
    Data2<int*, int> d5;    //使用class Data2<T1, int>
    Data2<int, int*> d6;    //使用template<class T1, class T2> class Data2{}
    Data2<int*, int*> d7;  //使用class Data2<T1*, T2*>
    Data2<int*, double*> d8; //使用class Data2<T1*, T2*>
}
類型萃取

通過以下題目來感受類型萃取

// 寫一個通用的拷貝函數(shù),要求:效率盡可能高

/*Way1:
//String:用該函數(shù)會報錯拷貝的是地址,析構(gòu)會對同一塊空間釋放兩次,會報錯
template<class T>
void Copy(T* dst, T* src, size_t size)
{
memcpy(dst, src, sizeof(T)*size);
}
*/

//因而要區(qū)分自定義和內(nèi)置類型,來調(diào)用使用不同的方法進(jìn)行拷貝
/*
//Way2:增加函數(shù)判定 區(qū)分自定義和內(nèi)置類型
bool IsPodType(const char* strType){
    const char* arrType[] = { "char", "short", "int", "long", "long long", "float","double", "long double" };
    for (size_t i = 0; i < sizeof(arrType) / sizeof(arrType[0]); ++i)      //每次都要遍歷,效率太低!
    {
        if (0 == strcmp(strType, arrType[i]))
            return true;
    }
    return false;
}

template<class T>
void Copy(T* dst, T* src, size_t size)
{
    if (IsPodType(typeid(T).name()))
    {
        memcpy(dst, src, sizeof(T)*size);
    }
    else{
        for (int i = 0; i < size; i++)
        {
            dst[i] = src[i];
        }
    }
}
*/

//Way3:萃取類型
//代表內(nèi)置類型
struct TrueType{
    static bool Get(){     //只有靜態(tài)才能用 ::  訪問
        return true;
    }
};
//代表自定義類型
struct FlaseType{
    static bool Get(){
        return false;
    }
};

template<class T>
struct TypeTraits{
    typedef FlaseType IsPodeType;
};

//對上述模板進(jìn)行實(shí)例化,將內(nèi)置類型都特化
template<>
struct TypeTraits<int>{
    typedef TrueType IsPodeType;
};
template<>
struct TypeTraits<double>{
    typedef TrueType IsPodeType;
};
/*
T為int:TypeTraits<int>已經(jīng)特化過,程序運(yùn)行時就會使用已經(jīng)特化過的TypeTraits<int>, 該類中的IsPODType剛好為類TrueType,而TrueType中Get函數(shù)返回true,內(nèi)置類型使用memcpy方式拷貝
T為string:TypeTraits<string>沒有特化過,程序運(yùn)行時使用TypeTraits類模板, 該類模板中的IsPODType剛好為類FalseType,而FalseType中Get函數(shù)返回true,自定義類型使用賦值方式拷貝
*/

template<class T>
void Copy(T* dst, T* src, size_t size)
{
    if (TypeTraits<T>::IsPodeType::Get())
    {
        memcpy(dst, src, sizeof(T)*size);
    }
    else{
        for (int i = 0; i < size; i++)
        {
            dst[i] = src[i];
        }
    }
}

void TestCopy()
{
    int array1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
    int array2[10];
    Copy(array2, array1, 10);

    String s1[3] = { "1111", "2222", "3333" };
    String s2[3];            
    Copy(s2, s1, 3);
}

模板分離編譯

要說模板分離編譯我們就要先來談?wù)勈裁词欠蛛x編譯

分離編譯

一個工程中有很多文件,但他們分為兩大類:頭文件和源文件
程序的運(yùn)行有以下五個步驟:
預(yù)處理-->編譯-->匯編-->鏈接
頭文件會在預(yù)處理階段展開,在預(yù)處理期間程序會將頭文件中的內(nèi)容復(fù)制一份到源文件。參與編譯的只有源文件,而且每個源文件都單獨(dú)進(jìn)行編譯生成目標(biāo)文件,最后將所有目標(biāo)文件鏈接形成單一的可執(zhí)行文件。如下圖:
模板

目標(biāo)文件鏈接的時候,是通過找函數(shù)地址(入口)進(jìn)行調(diào)用的。

注意:強(qiáng)調(diào):每個源文件都單獨(dú)進(jìn)行編譯

模板的分離編譯

對于模板:
實(shí)例化之前編譯器只會做一些簡單的語法測驗,不會生成處理具體類型的代碼
實(shí)例化期間編譯器用過推演形參類型來確保模板參數(shù),通過列表中T的實(shí)際類型在生成處理具體類型的代碼

//頭文件 CompilingTest.h
template <class T>
T Add(T left,T right);

//源文件 CompilingTest.c
#include "CompilingTest.h"
template <class T>
T Add(T left, T right)
{
    return left + right;
}

//源文件 main.c
#include "CompilingTest.h"
int main()
{
    Add(1.0, 1.0);   //會發(fā)生報錯,因為沒有實(shí)例化Add(int,int),找不到匹配的函數(shù)入口地址,因而在鏈接時會報錯
    }
//頭文件 CompilingTest.h
template <class T>
T Add(T left,T right);

//源文件 CompilingTest.c
#include "CompilingTest.h"
template <class T>
T Add(T left, T right)
{
    return left + right;
}
void tmp(){
    Add(1, 2);         //在此編譯器推演出了T->int的函數(shù),在鏈接時找到了適合Add(1, 1);該函數(shù)的入口地址,因而才不會報錯
}

//源文件 main.c
#include "CompilingTest.h"
int main()
{
    Add(1, 1);   //這樣就不會發(fā)生錯誤了
    //Add(1.0, 1.0);  //無匹配的Add(double,,double)
}

通過上述例子可得模板不支持分離編譯
模板

解決方法:

  1. 將聲明和定義放到一個文件 "xxx.hpp" 里面或者xxx.h其實(shí)也是可以的。推薦。
  2. 模板定義的位置顯式實(shí)例化。這種方法不實(shí)用,不推薦使用。

分離編譯詳細(xì)講解: http://blog.csdn.net/pongba/article/details/19130

模板優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

  1. 模板復(fù)用了代碼,節(jié)省資源,更快的迭代開發(fā),C++的標(biāo)準(zhǔn)模板庫(STL)因此而產(chǎn)生
  2. 增強(qiáng)了代碼的靈活性
    缺點(diǎn)
  3. 模板會導(dǎo)致代碼膨脹問題,也會導(dǎo)致編譯時間變長
  4. 出現(xiàn)模板編譯錯誤時,錯誤信息非常凌亂,不易定位錯誤

本文題目:模板
文章分享:http://muchs.cn/article36/ijdpsg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供小程序開發(fā)、網(wǎng)站導(dǎo)航響應(yīng)式網(wǎng)站、虛擬主機(jī)手機(jī)網(wǎng)站建設(shè)、網(wǎng)站改版

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站建設(shè)網(wǎng)站維護(hù)公司