C++之模板,模板
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{};//ERRORtemplate<typename T,floatIn>class A{};//ERRORtemplate<typename T,B In>classA{};//ERRORtemplate<typename T,double&In>class A{};//OKtemplate<typename T,double*In>class A{};//OKtemplate<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>//ERRORvoid foo(){}
2. 注意當有多個類型形參,則從第一個提供預設值的形參後面的形參,都要提供預設值。
template<typename T=int,typename T1>//ERRORclass 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. 第二個型別參數是inttemplate<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);了。
C語言寫一模板函數,任意兩個數之與,並在主函數(main)中進行顯式與隱式調用
代碼如下:
#include <iostream>
using namespace std;
// 加法的模板函數
template <typename T>
T Add(T a, T b)
{
return (a + b);
}
// 測試函數
int main()
{
// 顯式
cout<<"顯式調用:"<<endl;
int ia = 1, ib = 2, ic = 0;
ic = Add<int>(ia, ib);
cout<<ia<<" + "<<ib<<" = "<<ic<<endl;
// 隱式
cout<<"隱式調用:"<<endl;
float fa = 1.1f, fb = 2.2f, fc = 0.0f;
fc = Add(fa, fb);
cout<<fa<<" + "<<fb<<" = "<<fc<<endl;
cout<<endl;
return 0;
}
測試結果:
分層抽樣ABC三層,個體之比5:3:2,從總體抽取20個樣本,C層甲乙都抽到機率1/28則總體個數是哪個
題目給的數有點問題。
做法是這樣的,從c層抽4個,設c層共有n個。
答案不是整數。