今天機會難得就把一些學習心得po上來了
template用法非常多樣,多到可以再出一本書
但是不外乎幾個基本用法
Function Template (函數樣板)
只針對函式寫樣板
template <class T>
void swaps(T& a1,T& a2){
T tmp=a1;
a1=a2;
a2=tmp;
}
int main() {
int a=10;
int b=20;
cout<<'('<<a<<','<<b<<')'<<endl;
swaps(a,b);
cout<<'('<<a<<','<<b<<')'<<endl;
return 0;
}
//output:
(10,20)
(20,10)
也有人會把template跟函式名稱寫在一起
template <class T> void swaps(T& a1,T& a2){...}
這邊有一點要注意的就是把函式的宣告跟實作分開時,必須上下都加template
template <class T>
void swaps(T& a1,T& a2);
...
void swaps(T& a1,T& a2){
T tmp=a1;
a1=a2;
a2=tmp;
}
像上面一段程式碼會編譯不過
必須修改成
template <class T>
void swaps(T& a1,T& a2);
...
template <class T>
void swaps(T& a1,T& a2){
T tmp=a1;
a1=a2;
a2=tmp;
}
簡單來說只要有用到樣板的地方都該加上template <class T>
T是由自己命名,叫什麼都可以,只要能跟叫用的函式對起來即可
這邊補充一點,樣板有個功能叫特製化(Specialization)
Specialization
同樣名稱的樣板函式或類別可以重複出現,只要針對某個type進行特製化
這是為了有時候我們需要對某些Type作"特別"的處理
語法如下
template <> Return function<CType>{}
就像這樣template裏面保持為空,針對CType作特別處理
template <class T>
void swaps(T& a1,T& a2);
template <>
void swaps<int>(int& a1,int& a2){//特製化樣板 如果傳近來int就什麼都不做
}
int main() {
int a=10;
int b=20;
cout<<"特製化樣板"<<endl;
cout<<'('<<a<<','<<b<<')'<<endl;
swaps<int>(a,b);
cout<<'('<<a<<','<<b<<')'<<endl;
float c=10;
float d=20;
cout<<"一般樣板"<<endl;
cout<<'('<<c<<','<<d<<')'<<endl;
swaps<float>(c,d);
cout<<'('<<c<<','<<d<<')'<<endl;
return 0;
}
template <class T>
void swaps(T& a1,T& a2){
T tmp=a1;
a1=a2;
a2=tmp;
}
//output:
特製化樣板
(10,20)
(10,20)
一般樣板
(10,20)
(20,10)
然後還有一種就是可以帶參數的樣板
template<class T,int Size> //帶一個參數
void print(T t)
{
cout<<t<<":"<<Size<<endl;
}
int main() {
int data=100;
print<int,32767>(data);
return 0;
}
//output:
100:32767
Class Template (類別樣板)
替類別設置樣板,其有效範圍是整個"類別的宣告"
#include <iostream>
using namespace std;
template <class T>
//Printer.h
class Printer {
T _t;
public:
Printer(T t){_t=t;}
void print(){
cout<<_t<<endl;
}
};
...
//main.cpp
int main() {
Printer<int> p(100);
p.print();
return 0;
}
//output:
100
這邊有個小語病,其有效範圍是整個"類別的宣告"
是的,出了這個宣告仍然要加上template的宣告
//Printer.h
template <class T>
class Printer {
T _t;
public:
Printer(T t);
void print();
};
Printer::Printer(T t){
_t=t;
}
void Printer::print(){
cout<<_t<<endl;
}
像這樣直接把宣告跟實作拉開是不行的,會編譯不過
必須修改如下
//Printer.h
template <class T>
class Printer {
T _t;
public:
Printer(T t);
void print();
};
template <class T>
Printer<T>::Printer(T t){
_t=t;
}
template <class T>
void Printer<T>::print(){
cout<<_t<<endl;
}
如上,除了template要宣告,把實作分開時原本的Printer::的呼叫方式也行不通了
必須改成型別 Printer<T>::
初使化靜態變數也一樣
template <class T>
class Printer {
static int count;
};
template <class T>
int Printer<T>::count=0;//初使化靜態變數
這裡再提一個比較重要的地方
template是不能分開成.h和.cpp
如果要把實作分開也要全部再.h檔裏面完成
//Printer.h
template <class T>
class Printer {
static int count;
};
//Printer.cpp
template <class T>
int Printer<T>::count=0;//初使化靜態變數
像上面這樣一支程式是編譯不會通過的
不過有的編譯器或IDE很聰明,可以解決這問題
但是保守之道還是寫再.h檔比較保險
而樣板類別當然也能進行特製化
舉個『The C++ Programming Language, Special Edition』上的例子
template <class T>
class Vector{
T* v;
int sz;
public:
static int count;
Vector(int size);
~Vector();
T& elem(int i){return v[i];}
T& operator[](int i);
};
這是一個Vector類別,能接收任何型態
但是我覺得不夠,我希望能對指標做處理於是新增了一個
template<> class Vector<void*>{
void** p;
public:
Vector(int size){
p=new void*[size];
}
~Vector(){
}
void*& elem(int i){return p[i];}
void*& operator[](int i){return p[i];}
};
上面是一個萬用指標void*為基底的Vector 他並不會真正給user使用
這邊稍微提一下void*& 這種返回型態
因為我們希望可以把他當一般陣列一樣使用
Vector<int*> v;
*v[3]=100;
因此他必須回傳一個lvalue,要讓return的值變成lvalue的方式就是加上&
如果沒加上直接用
void* operator[](int i){return p[i];}
...
Vector<int*> v;
*v[3]=100;
就會出現如下的錯誤訊息
error: lvalue required as left operand of assignment
回到正題,如果想用指標型態還比須做一個動作
新增一個真正給user用的類別
再次宣告一個特製化類別<T*>,此時他的template不為空<>
因為他是為了T延伸的特製化T* 所以必須加上去
template<class T> class Vector<T*>:private Vector<void*>{
public:
typedef Vector<void*> Base;
explicit Vector(int size):Base(size){
}
~Vector(){
}
T*& operator[](int i){
static T* t=(T*)Base::elem(i);
return t;
}
};
這樣就沒問題了
上面有個點T*& operator[](int i),為何我用個靜態變數去傳呢
我照範例上面直接傳是不行,因為void*無法直接的轉成T*
如果我用
T*& operator[](int i){
return Base::elem(i);
}
會出現如下的錯誤訊息
invalid initialization of reference of type float*& from expression of type void*
而如果我照書上的寫法
T*& operator[](int i){
return static_cast<T*&>(Base::elem(i));
}
則又會出現如下的錯誤訊息
invalid static_cast from type void* to type int*&
而如果,我只試想轉型呢?
T*& operator[](int i){
return (T*)Base::elem(i);
}
結果還是不行,出現了錯誤訊息
invalid initialization of non-const reference of type char*& from a temporary of type char*
因為經過轉型後他會回傳一個(暫存變數)temporary,但是T*&這個型態因為&的關性他再等一個不會被消滅的變數
後來我就想了想,用個local變數如何
T*& operator[](int i){
T* t=(T*)Base::elem(i);
return t;
}
結果如何呢?當然還是不行,以lvalue當回傳值,不可能去接一個local變數,因為出了這個函式他就掛了,會出現如下的錯誤訊息
warning: reference to local variable t returned
最後絕招,加上static修飾,改變local的lifetime,讓他從stack移到.Data去
T*& operator[](int i){
static T* t=(T*)Base::elem(i);
return t;
}
這樣總算公德圓滿了
沒有留言:
張貼留言