模板使用&&模板爲什麼不能分離編譯

模板

爲什麼要模板,其實目的就是一個,
就是將本來應該我們做的重複的事情交給編譯器

模板的基本用法

模板函數

用法用例

template<typename T>
void swap(T& a,T& b)
{
    T tmp = a;
    a = b;
    b = tmp;
}

上面實現了一個兩個類型交互的函數,利用模板函數,我們可以傳入類型完成不同類型值的交換,例如

int a;
int b;
swap<int>(a,b);

float f1;
float f2;
swap<float>(f1,f2);

隱式實例化

編譯器根據傳入實參推演模板參數的實際類型

template<typename T>
void swap(T& a,T& b)
{
    T tmp = a;
    a = b;
    b = tmp;
}

int main()
{
 int a;
 int b;
swap(a,b);
}

顯式實例化

在函數名後的<>中指定模板參數的實際類型

template<typename T>
void swap(T& a,T& b)
{
    T tmp = a;
    a = b;
    b = tmp;
}

int main()
{
 int a;
 int b;
swap<int>(a,b);
}

模板參數的匹配原則

(1)一個非模板函數可以和一個同名的模板函數同時存在,而且該函數的模板還可以被實例化爲這個非模板函數

void swap(int& a,int& b)
{
    int tmp = a;
    a = b;
    b = tmp;
}

template<typename T>
void swap(T& a,T& b)
{
    T tmp = a;
    a = b;
    b = tmp;
}

int main()
{
    int a=10;
    int b=20;
    swap(a,b);//編譯器優先調用可以匹配的非模板參數
    swap<int>(a,b);//主動讓編譯器生成swap的實例
}

(2)對於非模板函數的同名函數模板,如果其他條件都相同,在調動時會優先調用非模板函數而不會從該模板產生出一個實例。如果模板可以產生一個具有更好的函數,那麼將選擇模板

void swap(int& a,int& b)
{
    int tmp = a;
    a = b;
    b = tmp;
}

template<typename T>
void swap(T& a,T& b)
{
    T tmp = a;
    a = b;
    b = tmp;
}

int main()
{
    int a=10;
    int b=20;
    float f1;
    float f2;
    swap(a,b);//編譯器優先調用可以匹配的非模板參數
    swap(f1,f2);//編譯器會生成一個更加匹配的版本,編譯器根據實參生成更加匹配的add函數
}

(3)模板函數不允許自動類型轉換,但普通函數可以進行自動類型轉換.

模板定義的格式

類模板

template<class T1,class T2,..,clas Tn>
class 模板類名
{
    //類內成員定義
}

類外的函數定義需要加上模板參數列表
template<class T>
class Vector
{
    ...
}
template <class T>
vector<T>::~vector()
{
 ...   
}

模板的實例化

我們定義的模板函數其實不是真正調用的函數,我們真正使用的函數是通過模板函數實例化出來的代碼

非類型模板參數

上面的模板是傳類型,而事實上,模板還可以傳非類型的參數,例如下面

示例代碼

#include <iostream>

template<class T,size_t N = 10>
class Array
{
  public:
  T _array[N]; 
};

int main()
{
  Array<int,10> arr1; 
  Array<int,20> arr2;
  return 0;
}

注意

(1)浮點數,類對象以及字符串是不允許作爲非類型模板參數的
(2)非類型的模板參數必須在編譯期就能確認結果。就和數組長度的定義一樣

模板的特化

模板可以實現與類型無關的代碼,但是也會有一些地方需要特殊處理,例如

template<class T>
bool IsEqual(T& left,T& right)
{
    return left==right;
}

void Test()
{
char* p1 = "hello";
char* p2 = "world";
if(IsEqual(p1, p2))
cout<<p1<<endl;
else
cout<<p2<<endl;
}

對於字符串的比較就不能直接像上面這樣比較,而需要特殊化處理

模板特化的步驟

(1)必須有一個基礎的模板

(2)關鍵template後面接一對空的尖括號<>

(3)函數名後跟一對尖括號,尖括號指定需要特化的類型

(4) 函數形參表必須和函數模板的基礎參數類型完全相同,如果不同就會報錯

template<>
bool IsEqual<char*>(char*& left, char*& right)
{
if(strcmp(left, right) > 0)
    return true;
return false;
}

注意

也可以直接實現該函數,直接匹配最佳的非模板參數,那樣就不需要編譯器再生成一個實例了

bool IsEqual(char* left, char* right)
{
if(strcmp(left, right) > 0)
return true;
return false;
}

利用非類型模板參數的特性解題

(1)求1+2+3+…n,要求不能使用乘法除法,for,while,if,else,switch,case等關鍵字及條件判斷語句(A?B:C)**

解題思路

利用非類型參數做一個遞歸的實例化,利用特化做一個遞歸的收斂

代碼

模板的偏特化

部分特化

template<class T1, class T2>
class Data
{
    public:
         Data() {cout<<"Data<T1, T2>" <<endl;}
    private:
        T1 _d1;
        T2 _d2;
};

// 將第二個參數特化爲int
template <class T1>
class Data<T1, int>
{
    public:
        Data() {cout<<"Data<T1, int>" <<endl;}
    private:
        T1 _d1;
        int _d2;
};

對參數進行限制

//兩個參數偏特化爲指針類型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
    public:
      Data() {cout<<"Data<T1*, T2*>" <<endl;}
    private:
        T1 _d1;
        T2 _d2;
};


//兩個參數偏特化爲引用類型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
    public:
        Data(const T1& d1, const T2& d2)
        : _d1(d1)
        , _d2(d2)
        {
        cout<<"Data<T1&, T2&>" <<endl;
        }
    private:
        const T1 & _d1;
        const T2 & _d2;
};

模板特化的應用

類型萃取

爲什麼要類型萃取呢?看下面的代碼

template<class T>
void Copy(T* dst, const T* src, size_t size)
{
    memcpy(dst, src, sizeof(T)*size);
}
int main()
{
    // 試試下面的代碼
    string strarr1[3] = {"11", "22", "33"};
    string strarr2[3];
    Copy(strarr2, strarr1, 3);
}

顯然對字符串的拷貝,上面的代碼並不通用 ,我們可以嘗試用一個方法區別自定義類型和內置類型,比如

template<class T>
void Copy(T* dst, const T* src, size_t size, bool IsPODType)
{
    if(IsPODType)
        memcpy(dst, src, sizeof(T)*size);
    else
    {
        for(size_t i = 0; i < size; ++i)
        dst[i] = src[i];
    }
}

上面增加了一個 bool 來區別自定義類型和內置類型,但是這樣就增加了使用上出錯的概率,能否用一種方法讓編譯器自動識別呢,如下

//
// POD: plain old data 平凡類型(無關痛癢的類型)--基本類型
// 指在C++ 中與 C兼容的類型,可以按照 C 的方式處理。
//
// 此處只是舉例,只列出個別類型
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(array)/sizeof(array[0]); ++i)
    {
        if(0 == strcmp(strType, arrType[i]))
        return true;
    }
        return false;
}
template<class T>
void Copy(T* dst, const T* src, size_t size)
{
    if(IsPODType(typeid(T).name()))
        memcpy(dst, src, sizeof(T)*size);
    else
    {
        for(size_t i = 0; i < size; ++i)
        dst[i] = src[i];
    }
}

上面利用tipeid來確認拷貝對象的實際類型,在內置類型的集合中尋找是否匹配內置類型,但是這樣就有缺陷了,每次都要比較字符串,效率很低

事實上我們可以藉助模板的特化進行類型萃取,而且是編譯的時候就已經完成了,所以這方法效率很高

/*
T爲int:TypeTraits<int>已經特化過,程序運行時就會使用已經特化過的TypeTraits<int>, 該類中的
IsPODType剛好爲類TrueType,而TrueType中Get函數返回true,內置類型使用memcpy方式拷貝
T爲string:TypeTraits<string>沒有特化過,程序運行時使用TypeTraits類模板, 該類模板中的IsPODType
剛
好爲類FalseType,而FalseType中Get函數返回true,自定義類型使用賦值方式拷貝
*/
template<class T>
void Copy(T* dst, const T* src, size_t size)
{
    if(TypeTraits<T>::IsPODType::Get())
      memcpy(dst, src, sizeof(T)*size);
    else
    {
        for(size_t i = 0; i < size; ++i)
        dst[i] = src[i];
    }
}
int main()
{
    int a1[] = {1,2,3,4,5,6,7,8,9,0};
    int a2[10];
    Copy(a2, a1, 10);
    string s1[] = {"1111", "2222", "3333", "4444"};
    string s2[4];
    Copy(s2, s1, 4);
    return 0;
}

關於爲什麼模板不能分離編譯?

1.爲什麼模板不能分離編譯了,首先我們要這樣想,模板是一個圖紙一樣的東西,假設我們的模板參數是一個int,那編譯器就要藉助這個模板生成一個爲int類型服務的代碼,也就是生成一個模板實例,而這個實例是一段代碼。

2.模板生成實例的時候是在鏈接前,生成實例的前提是當前文件中調用了模板

3.C/C++程序要運行,一般要經歷一下步驟

預編譯—》編譯—>彙編—> 鏈接

編譯:對程序按照語言特性進行詞法,語法,語義分析,錯誤檢查無誤後生成彙編代碼,注意頭文件不參與編譯,編譯器對工程中的多個源文件是分離開單獨編譯的。

鏈接:將多個obj文件和合併成一個,並處理沒有解決的地址問題。

下面代碼引用自 https://blog.csdn.net/pongba/article/details/19130

//-------------test.h----------------//

template<class T>

class A

{

public:

void f(); // 這裏只是個聲明

};




//---------------test.cpp-------------//

#include”test.h”

template<class T>

void A<T>::f()// 模板的實現

{

 …//do something

}




//---------------main.cpp---------------//

#include”test.h”

int main()

{

A<int> a;

f(); // #1

}

編譯器在編譯 #1的時候並不知道A::f的定義,因爲它不在test.h裏面,於是編譯器只好寄希望於連接器,希望它能夠在其他的.obj裏面找到A::f的實例,在本例中就是test.obj,然而,實際上後者根本就沒有A::f的二進制代碼

因爲C++標準明確表示,當一個模板不被用到的時候它就不該被實例化出來,test.cpp中用到了A::f了嗎?沒有!!所以實際上test.cpp編譯出來的test.obj文件中關於A::f一行的二進制代碼也沒有,於是連接器就傻了,根本就沒有要的二進制代碼嘛,於是就報出了鏈接錯誤

如果在test.cpp中寫一個函數,然後裏面調用了A::f 編譯器就會把它實例化出來,因爲test.cpp使用了A::f,並且test.cpp也有A::f的模板,所以就會實例化出來,在這種情況下,我們再鏈接,就發現可以鏈接上了。

下面是在test.cpp中寫一個函數,函數中調用A::f,這樣就會生成A::f的實例,test.obj的符號導出表中就有了A::f這個符號的地址,於是連接器就能完成任務。

如下

//---------test.h------//
template<class T>
int Add(const T& d1,const T& d2);

//---------test.cpp-----//
#include "test.h"

template<class T>
int Add(const T& d1,const T& d2)
{
  return d1+d2;
}


void fun()
{
  Add<int>(1,2);
}


//-------main.cpp---//
#include <iostream>
#include "test.h"

using namespace std;

int main()
{
    cout<<Add<int>(1,2)<<endl;
  return 0;
}

//------makefile---//
main:main.cpp  test.cpp
	g++ -o $@ $^


加上void fun()
{
Add(1,2);
}後就可以編譯成功了,同時要這樣編譯g++ -o main main.cpp test.cpp

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