模板是C++中泛型編程的基礎(chǔ),一個模板就是一個創(chuàng)建類或函數(shù)的公式或者說是代碼生成器,當(dāng)使用模板類型時础爬,編譯器會生成特定的類或函數(shù)阐枣,這個過程發(fā)生在編譯時马靠。C++的模板屬于類型膨脹,是真泛型蔼两,而Java里的泛型屬于類型擦除甩鳄,是偽泛型。
模板定義
模板定義以關(guān)鍵字template開始额划,后跟以個模板參數(shù)列表妙啃,這是一個逗號分隔的一個或多個模板參數(shù)的列表,用尖括號括起來俊戳。
template<typename T,typename M>
模板參數(shù)表示在類或函數(shù)定義中用到的類型或值揖赴,在使用模板時,我們顯式或隱式指定模板實(shí)參抑胎,將其綁定到模板參數(shù)上燥滑。
模板編譯
為了生成一個實(shí)例化版本,編譯器需要掌握函數(shù)或類模板成員函數(shù)的定義圆恤,所以模板的聲明和定義應(yīng)該都放在頭文件里突倍,如果是只用于源文件內(nèi)部則可以不放在頭文件腔稀。
函數(shù)模板
當(dāng)我們調(diào)用一個函數(shù)模板時,編譯器用函數(shù)實(shí)參來為我們推斷模板實(shí)參羽历,然后為我們實(shí)例化一個特定版本的函數(shù)焊虏。下面的代碼會實(shí)例化出兩個不同版本的compare。
template<typename T>
bool compare(const T& value1, const T& value2)
{
return value1 == value2;
}
int main()
{
cout << compare(1, 10) << endl;
cout << compare("11", "11") << endl;
system("pause");
}
類模板
與函數(shù)模板不同秕磷,編譯器不能為類模板推斷模板參數(shù)類型诵闭,為了使用類模板,我們必須在模板名后的尖括號中提供額外的信息澎嚣,例如vector<int>疏尿。
template<typename T>
class Printer
{
public:
Printer<T>(T value):value_(value) {};
void execute()
{
cout << value_ << endl;
}
private:
T value_;
};
int main()
{
auto printer1 = Printer<int>(100);
auto printer2 = Printer<double>(123.4);
auto printer3 = Printer<string>("hello world!");
printer1.execute();
printer2.execute();
printer3.execute();
system("pause");
}
定義在類模板之外的成員函數(shù)必須以關(guān)鍵字template開始,后接類模板參數(shù)列表易桃。
template<typename T>
void Printer<T>::execute2()
{
std::cout << value_ << std::endl;
}
成員函數(shù)只有在被用到才會實(shí)例化褥琐,這一特性使得即使類型不能完全符合類模板要求,我們?nèi)匀荒苡迷擃愋蛯?shí)例化類晤郑。下面代碼中的printer1雖然不能調(diào)用execute()敌呈,但是仍然可以實(shí)例化并調(diào)用其它成員函數(shù)。
template<typename T>
class Printer
{
public:
Printer<T>(T value):value_(value) {};
void execute()
{
cout << value_ << endl;
}
void printAddress()
{
cout << &value_ << endl;
}
private:
T value_;
};
int main()
{
auto printer1 = Printer<vector<int>>({1,2,3});
//printer1.execute();
printer1.printAddress();
system("pause");
}
在類模板自己的作用域中造寝,我們可以直接使用模板名而省略尖括號磕洪。
template<typename T>
class Printer
{
public:
Printer<T>(T value):value_(value) {};
void execute()
{
cout << value_ << endl;
}
Printer& getSelf() { return *this; };
Printer<T>& getSelf2() { return *this; };
Printer& getSelf3();
private:
T value_;
};
//類模板外使用類模板名
template<typename T>
Printer<T>& Printer<T>::getSelf3()
{
Printer& printer = *this;
return printer;
}
int main()
{
auto printer1 = Printer<int>(100);
printer1.getSelf().execute();
printer1.getSelf2().execute();
printer1.getSelf3().execute();
system("pause");
}
類模板的每個實(shí)例都有一個獨(dú)立的靜態(tài)成員,類似其它成員函數(shù)诫龙,一個靜態(tài)成員函數(shù)只有在使用時才會實(shí)例化析显。
template<typename T>
class Printer
{
public:
static int testValue_;
};
template<typename T>
int Printer<T>::testValue_ = 0;
int main()
{
Printer<double>::testValue_ = 100;
Printer<int>::testValue_ = 101;
cout << Printer<double>::testValue_ << endl;
cout << Printer<int>::testValue_ << endl;
system("pause");
}
非類型模板參數(shù)
除了定義類型參數(shù),還可以在模板中定義非類型參數(shù)签赃,一個非類型參數(shù)表示一個值而非一個類型谷异,我們通過特定的類型名而不是typename來指定非類型模板參數(shù)。
非類型模板參數(shù)可以是一個整型姊舵,或者是一個指向?qū)ο蠡蚝瘮?shù)類型的指針或引用晰绎,非類型模板參數(shù)的模板實(shí)參必須是常量表達(dá)式,從而允許編譯器在編譯時實(shí)例化模板括丁。
template<int N,int M>
int compare(const char (&value1)[N], const char(&value2)[M])
{
return strcmp(value1,value2);
}
int main()
{
cout << compare("11", "112") << endl;
system("pause");
}
默認(rèn)模板實(shí)參
就像我們能為函數(shù)參數(shù)提供默認(rèn)參數(shù)意義,我們也可以為函數(shù)和類模板提供默認(rèn)實(shí)參:
template<typename T,typename F=less<int>>
bool compare(const T & value1, const T & value2, F func = F())
{
return func(value1, value2);
}
template<typename T=int>
class MyClass {
public:
T value = {};
};
int main()
{
//使用默認(rèn)模板實(shí)參
compare(111, 2222);
compare(string("111"), string("2222"), less<string>());
//使用默認(rèn)模板實(shí)參
MyClass<> value1;
MyClass<double> value2;
value2.value = 3.1416;
cout << value1.value << endl;
cout << value2.value << endl;
system("pause");
}
限制模板參數(shù)
C++不像其它語言能顯式限制模板參數(shù)伶选,例如Java里的extends史飞,Swift里的T:ClassB,好處是我們可以把要使用的成員先寫好仰税,模板實(shí)例化的時候編譯器會判斷該類型是否符合條件构资,不符合則編譯失敗。
template<typename T>
size_t getSize(const T & value)
{
return value.size();
}
class MyClass {
public:
size_t size() const { return 3; }
};
int main()
{
vector<int> value1 = { 1,2,3,4,5 };
MyClass value2;
int value3;
cout <<getSize(value1)<< endl;
cout << getSize(value2) << endl;
cout << getSize(value3) << endl;//錯誤陨簇,int沒有size方法
system("pause");
}
控制模板實(shí)例化
當(dāng)多個源文件使用了相同的模板吐绵,并提供相同的模板參數(shù)時,每個文件都會有該模板的一個實(shí)例,為了避免這種額外開銷己单,我們可以使用顯式實(shí)例化唉窃,當(dāng)編譯器遇到extern template時不會在本文件中生成實(shí)例化代碼,而是從定義處實(shí)例化代碼纹笼,可能有多個extern聲明但是只能有一處定義纹份。和普通實(shí)例化不同,實(shí)例化定義會初始化所有成員廷痘,即使我們沒有使用該成員蔓涧。
Example.h
template<typename T>
class MyClass {
public:
//int getSize() { return value.size(); };
T value = {};
};
Example.cpp
#include "Example.h"
//定義MyClass<int>
template class MyClass<int>;
main.cpp
//聲明MyClass<int>
extern template class MyClass<int>;
int main()
{
MyClass<int> value;
cout << value.value << endl;
system("pause");
}
指定顯式模板實(shí)參
函數(shù)模板一般會推斷模板參數(shù)類型,但有時候也需要指定顯式模板實(shí)參笋额,可以只指定前面的一部分實(shí)參元暴,后面的可以讓編譯器推斷。
template <typename T1,typename T2,typename T3>
T1 getResult(T2 value1, T3 value2)
{
return value1+value2;
}
int main()
{
cout <<getResult<int>(1,2)<< endl;
cout << getResult<int>(1.0, 2.2) << endl;
cout << getResult<string>(string("hello "), string("world!")) << endl;
system("pause");
}
重載與模板
當(dāng)有多個重載模板對一個調(diào)用提供同樣好的匹配時兄猩,編譯器會選擇最特例化的版本茉盏,越通用的模板的優(yōu)先級越低。
對于一個調(diào)用厦滤,如果一個非函數(shù)模板與一個函數(shù)模板提供同樣好的匹配援岩,則選擇非模板版本。