假如我們需要取得兩個變量中較大的變量,或許,我們可以通過重載的方式實作,如下。
int max(int fir,int sec);
float max(float fir,float sec);
double max(double fir,double sec);
有一天,我們定義了一個新的type,School,取決于max的實作,我們不僅需要重載School::operator<(), 或者School::operator>()還要重載一個新的max
const School max(School& fir,School& sec);
使用C++的模闆,從此告别這些繁瑣而又略顯臃腫的代碼。
注:1. 上述的傳回值可以考慮使用const School&,但一般不建議,參見在傳回值拒絕reference
2.形參使用了School&,參見傳參時,使用引用替換變量
函數模闆##
顧名思義,模闆,也就是“模闆”,并不是實際存在的東西,而隻是為了讓我們更友善地生産某些東西的模具。C++的模闆分為了兩類,類模闆與函數模闆。分别用于讓我們友善地“生産各種各樣的函數與類”,它們都使用了template,class,typename幾個關鍵字。為什麼說是各種各樣,看完了部落格自然就明白了。下面介紹函數模闆。
示例####
template < class type>
type max(type fir,type sec)
{
return fir > sec ? fir : sec;
}
template告訴編譯器這是一個模闆,緊跟在後面的<>中聲明了模闆形參,這些形參在模闆中可以充當類型,聲明可以選用class或者typename,暫時認為兩者在C++中作用相同。普通函數的形參為一個變量,模闆形參為一種變量類型。也就是,我們可以通過指定模闆形參的類型。來個簡單的例子。
比如int,float來形成不同的重載函數
template < class type...>
type max(type fir,type sec...)
{
return fir > sec ? fir : sec;
}
void main()
{
int a(1),b(2);
float c(1),d(2);
max(a,b); //具現化int max(int fir,int sec);
max(c,d); //具現化int max(float fir,float sec);
}
第一個max使用了int類型的參數,相當于告訴函數模闆,type對應于int,在具現化的函數模闆中,type的作用相當于int。是以具現化的函數相當于int
max(int fir,int sec);相對應地,使用了float調用函數模闆,也就是制定了type為float,與前一個函數形成了重載。
注:雖然float能夠隐私轉換為int,但是還是會具現化新的函數。隻有目前的參數類型與已經具現化的函數模闆完全比對的時候,才會繼續使用已經具現化的函數。
拓展####
template < class type_1,class type_2...>
type_1 func(type_2 fir,type_1 sec,int thir)
{
//return...
}
相對于前一個模闆函數,這個模闆函數的模闆形參數量增加了,在普通的形參清單中,模闆形參的順序打亂了,還增加了int的形參。
- 在模闆形參中,我們可以随意地定義任意數量的模闆形參,但必須保證能夠全部初始化。
- 使用了不同的類型名type_1,type_2...意味着我們可以指定多種類型的模闆形參,其類型也可以不相同。
- 模闆形參沒有要求必須與普通函數形參一一對應,即在形參中的順序可以随意打亂,其類型由相應的普通形參的類型決定。如,type_1的類型由sec的類型決定。
- 在模闆函數中,除了模闆形參外,可以使用内置的或者自定義的類型。
還是來個簡單的例子
template < class type_1,class type_2>
void max(type_2 fir,type_1 sec,int)//最後的參數沒有使用,可以直接忽略形參名
{
std::cout<<fir<<"+"<<sec<<endl;
}
void main()
{
int a(1);
float b(1.0);
max(a,b,1); //1. 具現化void max(int fir,float sec,int);
}
第一個實參為int型,其對應的形參是type_2,是以type_2具現化後就是int。
第二個實參為float,其對應的形參type_1,是以type_1具現化後就是float。最後的具現化的函數就是int max(int fir,float sec,int);
指定參數類型####
還記得使用STL容器的方法嗎,比如定義一個vector類型的容器。STL也叫作标準模闆庫,也就是其内部也是通過模闆實作的,是以這種名稱後加類型名的方法對我們也同樣适用。
void Select(int a)
{
std::cout<<"是int型"<<endl;
}
void Select(float a)
{
std::cout<<"是float型"<<endl;
}
template < class type_1,class type_2>
void myPrint(type_1 fir,type_2 sec)
{
Select(fir);
}
void main()
{
myPrint(1.0,1); //輸出"是float型"
myPrint<int>(1.0,1); //輸出"是int型"
}
在上面的例子中,我們可以發現:
- 在調用模闆函數的時候,我們可以通過直接指定模闆形參的類型進而阻止普通函數形參對于模闆形參的影響。但是,指定的類型與普通函數形參必須能夠進行類型轉換。
比如,内置類型的int與double可以互相轉換,是以myPrint< double>(1)可用。但是string與int之間不可互相轉換myPrint< string>(1)就沒辦法通過編譯。假如我們定義了class My,其構造函數為public:My(int),那麼認為My與int可以互相轉換(本質上是隐式調用了My的構造函數),myPrint< My>(1)就可以通過編譯。
類模闆##
假如你對函數模闆還不會使用,請自行回顧,一些函數模闆講過的在下面不再贅述。
執行個體##
template < class type_1,class type_2>
class Student
{
public:
Student(){}
Student(type_1 fir,type_2 sec){}
Student(type_1 fir){}
private:
type_1 value_1;
type_2 value_2;
...
};
void main()
{
Student stu(1,1); //error
Student<int,float> stu(1,1); //OK
}
template,class的作用與函數模闆一緻。不同的是:
- 類模闆必須在使用的時候指定好模闆形參的類型,編譯器不會通過public接口,包括構造函數去作為模闆形參類型的辨識依據。記得vector vec吧,沒見過vector vec(1)吧。
- 使用類模闆的時候,使用到的成員函數在主調語句必須可見。比如,上述的Student(type_1 fir,type_2 sec)在main中調用,其函數定義在main所在檔案必須可見。再比如上述例子,假如其實作配置設定到如下幾個檔案,在連結的時候将出錯。讀者可以先記得,在“精通篇”會詳細闡述這一點。
- 類模闆中,慎用模闆形參重載函數。上述的例子中,假如再增加Student(type_2)就會編譯出錯。編譯器無法在Student(type_1)與Student(type_2)中做抉擇。
//1.h
template < class type_1,class type_2>
class Student
{
public:
...
Student() //有具體實作的構造函數
{
...
}
Student(type_1 fir,type_2 sec);
private:
...
};
//1.cpp
#include"1.h"
Student< class type_1,class type_2>::Student()
{}
//core.cpp
#include"1.h"
void main()
{
Student<int,int> stu(1,1); //構造函數定義在1.cpp中,不可見,出錯
Student<int,int> stu(); //預設構造函數随1.hinclude,可見,編譯通過
}
C++中模闆的基本使用方法如上。下一篇部落格将帶大家進入模闆特化以及深入解釋上述例子無法編譯的原因。