C++模版分爲函數模版和類模版。其實模版就是一種對類型進行參數化的工具。
一、函數模板
1. 函數模板的聲明定義
Template<typename/class T1,typename/class T2,….>
返回類型 函數名(函數參數){
函數體
}
例如:
template<typename T>
T add(T t,T t1)
{
return t+t1;
}
2. 模板函數的兩種調用方式
(1)隱式調用
int i=1,j=2;
int k=add(i,j);
默認是add函數的兩個參數是同一類型,系統根據實際調用的時候的實參的類型來確定形參的具體類型。注意這裏的兩個參數類型必須相同。
不可以:
int i=1,j=2;
int k=add(1,2.2);//error
(2)顯示調用
int i=1,j=2;
int k=add<int>(i,j);
首先指定函數模板的具體類型,再去調用。這樣就可以:
int k=add<int>(1,2.3);//OK
這裏和隱式調用的區別:顯示調用就是先指定函數模板的具體類型,則函數就變成了具體類型的函數了,intadd(int t,int t1)則,再去調用add<int>(1,2.3);顯然就沒有問題了。只是系統進行了一下類型轉換而已。
二、類模板
1. 類模板的聲明定義
Template<typename/class T1, typename/class T2,….>
Class 類名{
類的成員
}
例如:
template<typename T>
class A
{
public:A(T b):a(b){}
T swap(T& i,T& j){
T temp;
temp=i;
i=j;
j=temp;
}
void foo(Tt);
private: T a;
};
template<typename T>
void A<T>::foo(T t)
{cout<<t<<endl;}
(1) 注意類模板的成員函數,如果在類外實現,必須在實現的前面加上template<…>。而且函數名前加”類名<T1[,T2,…]>”。
(2)當在類中,想要使用自身類的對象時(例如:copy構造、operator=)時,直接使用類名就可以了。例如:
class A
{
public:A(T b):a(b){}
A(const A&c){
a=c.a;
}
private: T a;
};
(3)當在類外,想要使用這個類名的時候,必須要“類名+<T,….>”。例如成員函數的類外實現的時候,函數名前加的東西。
class A
{
public:A(T b):a(b){}
void foo(Ac);
private: T a;
};
template<typename T>
void A<T>::foo(A<T>t)
{cout<<t<<endl;}
(4)有時在類中,想使用同一模板的其他類型的類的對象。例如:
class A
{
public:A(T b):a(b){}
T getm(){returna;}
template<typename Y>
voidfoo(A<Y>& c){
cout<<c.getm()<<endl;
}
private: T a;
};
int _tmain(int argc, _TCHAR* argv[])
{
A<int> a(2);
A<double> b(3);
a.foo(b);
system("pause");
return 0;
}
在函數的前面加template<typenameY>,注意這裏的Y不能和類模板時的T相同。
這種情況常常在重寫copy和operator=時,使用子類的對象給父類的對象copy構造、賦值時,會經常用到這個。具體可見auto_ptr的源碼。
2. 類模板的調用
A<int>a(2);
定義類對象時,先聲明類的具體類型。
(1) 和函數模板隱式調用不一樣的地方,可以:
template<typename T>
class A
{
public:A(T b):a(b){}
T add(T t1, T t2)
{returnt1+t2;}
private: T a;
};
int _tmain(int argc, _TCHAR* argv[])
{
A<int> a(2);
a.add(2,3.4);//OK
system("pause");
return 0;
}
這裏注意類模板的原理:由於類模板在定義的時候,已經確定了T的類型,所以在定義了A<int> a(2)之後,a中的T都變成了int,所以這裏add函數就變成了int add(int t1,int t2).這顯然就可以add(2,3.4)調用了,符合系統的默認類型轉換。
三、非類型形參
當template<typenameT,int In>。出現了int這樣的具體類型的時候,In就是非類型形參。例如:
template<typename T,int In>
class A
{
public:A(){a=new T[In];a[0]=3;}
void foo()
{cout<<a[0]<<endl;}
~A(){delete[]a;}
private: T* a;
};
int _tmain(int argc, _TCHAR* argv[])
{
A<int,4> a;
a.foo();
system("pause");
return 0;
}
1. 注意非類型形參In在內部是常量值,在上面代碼中也可以看出。
2. 非類型形參只能是整型(包括int,short,unsigned int),指針和引用。就是說不可以是float/double/string,但可以是double*/double&/對象的指針、引用也是可以的。
class B{};
template<typename T,doubleIn>class A{};//ERROR
template<typename T,floatIn>class A{};//ERROR
template<typename T,B In>classA{};//ERROR
template<typename T,double&In>class A{};//OK
template<typename T,double*In>class A{};//OK
template<typename T,B* In>classA{};//OK
3. 調用非類型形參的實參必須是一個常量表達式。編譯時能計算出結果。(注意sizeof的返回值也是常量,可以使用)
注意:何爲常量表達式?(值不會改變,且在編譯階段就能知道確切值的)
const int 、常數、枚舉。全局變量的引用/地址、全局對象的引用/地址。
不爲常量表達式:
局部變量,局部對象,局部對象的地址/引用,全局變量,全局對象都不是常量表達式。
總結:首先非類型的形參只能是整型,指針,引用。這也決定了實參也必須只能是這些,除此之外,實參還必須是一個常量表達式。所以這樣一彙總:實參只能是const int、常數、枚舉、全局變量的引用/地址、全局對象的引用/地址。
也可以:
template<int* T>
class A{};
int d[]={2};
int _tmain(int argc, _TCHAR* argv[])
{
A<d>a;//----------OK
system("pause");
return 0;
}
4. 有非類型形參的模板在類外實現成員函數時:
其實對於所有類型的類模板,在類外面定義類的成員時template後面的模板形參應與要定義的類的模板形參一致。
template<int* a>
class A{
public:void foo();
};
template<int* a> -----在類外不要隨便修改a的名稱。保持和定義時一致
void A<a>::foo(){}
四、可以爲類模板提供類型的默認值
template<typename T=int>
class A{};
int _tmain(int argc, _TCHAR* argv[])
{
A<>a;//OK
A<double> b;//OK
system("pause");
return 0;
}
1. 注意可以爲類模板提供類型默認值,不可以爲函數模板提供類型默認值。
template<typename T=int>//ERROR
void foo(){}
2. 注意當有多個類型形參,則從第一個提供默認值的形參後面的形參,都要提供默認值。
template<typename T=int,typename T1>//ERROR
class A{};
五、模板的特化
1. 模板的特化就是給一個已存在的模板進行特殊類型時,執行特別的操作。
template<typename T>
class A{
public:void foo(){cout<<"T"<<endl;}
};
template<>//模板的特化,當T爲int時,執行的是這個,當其他類型時,執行的是上面
class A<int>{
public:void foo(){cout<<"int"<<endl;}
};
int _tmain(int argc, _TCHAR* argv[])
{
A<int> a;//執行的是下面那個特化模板
a.foo();
A<double> b;//執行的是上面那個普通模板
b.foo();
system("pause");
return 0;
}
2. 模板的偏特化,也叫部分特化。當模板有多個形參時,對其中一部分進行特化。與上面的全部特化想對應。
//1. 標準模板類。
template<typename T1, typenameT2>
class MyClass {
... ...
};
//2. 兩個模板參數具有相同類型的部分特化類。
template<typename T>
class MyClass<T,T> {
... ...
};
//3. 第二個類型參數是int
template<typename T>
class MyClass<T,int> {
... ...
};
//4. 兩個模板參數都是指針。
template<typename T1,typenameT2>
classMyClass<T1*,T2*> {
... ...
};
//5. 兩個模板參數都是相同類型的指針。
template<typename T>
class MyClass<T*,T*>{
... ...
};
//6. 模板的全部特化,上面2~5爲部分特化
template<>
class MyClass<int,double>{
......
};
int _tmain(int argc, _TCHAR* argv[])
{
MyClass<int,float>c1; //調用MyClass<T1,T2>
MyClass<float,float> c2; //調用MyClass<T,T>
MyClass<float,int> c3; //調用MyClass<T,int>
MyClass<int*,float*>c4; //調用MyClass<T1*,T2*>
MyClass<int*,int*>c5; //調用MyClass<T*,T*>
MyClass<int,double>c6; //調用MyClass<int,double>
system("pause");
return 0;
}
全部特化一般都是template<>。中括號裏沒有東西,把所有的形參全部特化。
部分特化一般都是template<T,…>。中括號裏有東西,沒有把所有的形參都特化。
函數模板同理也可以特化。
六、函數模板的重載
類模板是不可以重載的,而函數模板可以重載。
例子:
template<typename T>
void foo(T)
{cout<<"template"<<endl;}
void foo(int)
{cout<<"foo"<<endl;}
int _tmain(int argc, _TCHAR* argv[])
{
int i=2;
double j=2.0;
foo(i);//調用的是foo(int)------(1)
foo(j);//調用的是template<>foo(T)----------(2)
system("pause");
return 0;
}
和特化類似,只是類模板不可以重載。(廢話,類能重載嗎)
重載選擇的順序:“越特殊越優先高”
顯然如果(1)這麼寫的話:”foo<int>(i);”就是顯式的調用函數模板,這樣就調用的tempalte<>foo(T);了。