目錄
一、前言
二、非類型模板參數
三、模板的特化
3.1 類模板特化
3.11 全特化
3.12 偏特化
3.2 函數模板特化
3.3 注意
四、模板的分離編譯
一、前言
前面的文章梳理了模板初階的一些用法,在后面梳理了STL的一些容器的用法后,下面將用到含有STL的模板部分知識進行梳理。
那么首先我們先看下面的代碼:
template<class Container>
void Print(const Container& con)
{Container::const_iterator it=con.begin();while(it != con.end()){cout << *it << " ";it++;}cout << endl;
}int main()
{vector<int> v;v.push_back(1);v.push_back(2);Print(v);
}
這里我們首先實例化了一個vector<int>的對象v,然后插入兩個數據,最后寫了一個打印函數來打印有迭代器的容器的數據,但這里我們運行后出現了上圖的錯誤,這個錯誤出現的原因是第四行我們使用模板導致的,因為在我們使用模板進行定義時,在沒有對它進行實例化前編譯器無法識別,在沒有實例化時,Container::const_iterator這一個定義,編譯器無法識別,因為這一句話既可以當作類型,也可以當作變量/對象,雖然作為變量時加上后面的it是不對的,但編譯器無法在沒有其他任何操作前正確識別,所以報錯了。那么我們想要解決這個問題只要提前告訴編譯器這是類型就可以了,這里用到了定義模板參數時的另一個與class同等效果的typename,typename在模板中與class唯一的不同就在于將typename提前可以告訴編譯器這句話是代表類型,這樣編譯器就會在識別時確定這是類型,就不會報錯了。
template<class Container>
void Print(const Container& con)
{typename Container::const_iterator it=con.begin();while(it != con.end()){cout << *it << " ";it++;}cout << endl;
}int main()
{vector<int> v;v.push_back(1);v.push_back(2);Print(v);
}
類似上面這樣的只要傳過來的容器含有模板參數(vector<T>這樣的也是)都要在前面加typename
注意:無論定義模板參數時是class還是typename,遇到上面這種模板參數來定義時都要在定義前加typename
二、非類型模板參數
首先我們知道模板的使用格式是
template<class (模板參數),? ? ...>
那么非類型模板參數就是在模板參數的定義中用常量來代表模板參數。
下面我們寫了一個靜態棧的代碼:
#define N 10
template<class T>
class Stack
{
private:T _a[N];int _top;
};
int main()
{Stack<int> st1;Stack<int> st2;
}
如果此時我們需要st1的大小為10,st2的大小為100,如何做?此時我們發現一個靜態棧已經無法實現了,那么此時只要我們用到非類型模板參數加一個非類型模板參數即可:
template<class T,size_t N>
class Stack
{
private:T _a[N];int _top;
};
int main()
{Stack<int,10> st1;Stack<int,100> st2;
}
上述代碼所示:我們在模板參數中加了一個非類型模板參數,這樣我們在實例化時只要填我們需要的常量時就可以了。
注意:1. 浮點數、類對象以及字符串是不允許作為非類型模板參數的。2. 非類型的模板參數必須在編譯期就能確認結果。
三、模板的特化
在實際應用中會遇到一些普通模板無法實現的情況,需要特殊處理,那么此時就可以使用模板的特化來特殊處理。(這里以梳理用法為主)
模板的特化分為函數模板特化和類模板特化
3.1 類模板特化
3.11 全特化
全特化就是將所有模板參數特殊化,格式如下:
template<>
class A<類型,類型...>
{};
全特化將<>中的所有模板參數去掉,將我們需要特化的類型放到類名后面的<>中
我們看下面的代碼:
//沒有特化
template<class T1,class T2>
class Data
{
public:Data(){cout << "Data<T1, T2>" << endl;}
private:T1 d1;T2 d2;
};
//全特化
template<>
class Data<int, char>
{
public:Data(){cout << "Data<int,char>" << endl;}
private:int d1;char d2;
};
int main()
{Data<int ,int> d1;Data<int, char> d2;
}
這串代碼中上面是沒有特化的,下面是全特化,全特化這里我們需要int和char型,所以將類型寫到Data后即可,那么此時我們只有在調用時以Data<int, char> d2 ;這樣的格式才可以使用全特化,<int,int>在這里只能調用沒有特化類
3.12 偏特化
偏特化就是特化部分模板參數或進一步限制模板參數。
template<class T1,class T2>
class Data
{
public:Data(){cout << "Data<T1, T2>" << endl;}
private:T1 d1;T2 d2;
};
//偏特化1:特化部分模板參數
template<class T1>
class Data<T1, int>
{
public:Data(){cout << "Data<T1, int>" << endl;}
private:T1 d1;int d2;
};
//偏特化2:進一步限制模板參數
template<class T1, class T2>
class Data<T1*,T2*>
{
public:Data(){cout << "Data<T1*, T2*>" << endl;}
private:T1* d1;T2* d2;
};
偏特化有兩種,偏特化1:特化部分模板參數和偏特化2:進一步限制模板參數,它們代表著兩種偏特化形式:
偏特化1:將部分模板參數特化,上述代碼中我們可以看到保留了一個模板參數T1,特化了一個模板參數成int。
偏特化2:這里保留了所有的模板參數,但將所有的模板參數進一步進行限制.。例如上述代碼中將所有的模板參數限制為指針,也就是說只要參數都是指針類型的都會去調用這個模板類。
3.2 函數模板特化
函數模板只有全特化
#include<stdbool.h>
//沒有特化
template<class T>
bool Less(T a, T b)
{return a < b;
}
//全特化
template<>
bool Less<Date*>(Date* a, Date* b)
{return *a < *b;
}int main()
{cout << Less(1, 2) << endl;Date d1(2022, 7, 7);Date d2(2022, 7, 8);Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl;
}
上述代碼中先寫了一個Less沒有特化的比較,當遇到像Date* p1=&d1這樣的變量時再使用普適模板就無法比較了,所以需要寫一個Less模板的特化將Date*單寫出來
bool Less(Date* a, Date* b)
{return *a < *b;
}
上述代碼中沒有使用模板但依然可以正常比較,大部分的函數模板都可以被不適用模板的函數代替,所以此時再使用模板就會降低運行效率,因此函數模板的特化在此時沒有必要使用。
3.3 注意
無論是類模板特化還是函數模板特化,在使用特化時必須要有普通模板的存在,不然會報錯。
四、模板的分離編譯
我們有時會為了方便將函數的聲明和定義分離,將聲明放到頭文件中,定義放到源文件中,但如果使用了模板后那么這個函數就不可以與普通函數一樣放到兩個文件中。
我們看下面代碼:
//Stack.h
namespace lbs
{template<class T, class container = vector<int>>class stack{public:void push(const T& val);void pop();T& top(){return _con.back();}size_t size(){return _con.size();}private:container _con;};
//-------------Stack.cpp---------------------------
namespace lbs
{template<class T, class container>void stack<T, container>::push(const T& val){_con.push_back(val);}template<class T, class container>void stack<T, container>::pop(){_con.pop_back();}//template class stack<int>;
}
//-----------------test.cpp-------------------------
int main()
{lbs::stack<int> st1;st1.push(1);st1.pop();
}
上述代碼中,我們在Stack.h文件中我們寫了一個stack類,其中push和pop進行了聲明與定義分離,size和top沒有進行分離,此時我們運行后在定義的push和pop處會報錯。
這里出現錯誤的原因就是在鏈接過程中編譯器無法正確識別類模板的實例化,因為這里出現了模板參數,而模板參數在改函數沒有被調用時不會實例化,所以鏈接階段沒有進行實例化,那么此時在Stack,cpp文件中的定義就無法正常進行。
想要正確分離有兩種方法:
1、較為麻煩(不推薦)
在Stack.cpp文件中加上template class stack<類型>;這一句話
在定義所在文件中加上這一句話就代表提前告訴編譯器要對此類型進行實例化,但麻煩的地方在于每此用新的類型時都要加上這一句話并填上對應的類型
2、較為簡單(推薦)
namespace lbs
{template<class T, class container = vector<int>>class stack{public:void push(const T& val);void pop();T& top(){return _con.back();}size_t size(){return _con.size();}private:container _con;};template<class T, class container>void stack<T, container>::push(const T& val){_con.push_back(val);}template<class T, class container>void stack<T, container>::pop(){_con.pop_back();}
}
將定義放到聲明的下方,此時聲明與定義分離