自己制作網(wǎng)站app一手app推廣接單平臺(tái)
C++最重要的特性之一就是代碼重用,為了實(shí)現(xiàn)代碼重用,代碼必須具有通用性。通用代碼應(yīng)不受數(shù)據(jù)類型的影響,并且可以自動(dòng)適應(yīng)數(shù)據(jù)類型的變化。這種程序設(shè)計(jì)類型稱為參數(shù)化程序設(shè)計(jì)。模板是C++支持參數(shù)化程序設(shè)計(jì)的工具,通過(guò)它可以實(shí)現(xiàn)參數(shù)化多態(tài)性。所謂參數(shù)化多態(tài)性,就是將程序所處理的對(duì)象的類型參數(shù)化,使得一段程序可以用于處理多種不同類型的對(duì)象。
1.函數(shù)模板
通過(guò)函數(shù)重載,可以看出重載函數(shù)通常是對(duì)于不同的數(shù)據(jù)類型完成類似的操作。很多情況下,一個(gè)算法是可以處理多種數(shù)據(jù)類型的。但是用函數(shù)實(shí)現(xiàn)算法時(shí),即使設(shè)計(jì)為重載函數(shù)也只是使用相同的函數(shù)名,函數(shù)體仍然要分別定義。
下面是兩個(gè)求絕對(duì)值的函數(shù):
int abs(int x)
{return x < 0 ? -x : x;
}double abs(double x)
{return x < 0 ? -x : x;
}
這兩個(gè)函數(shù)只有參數(shù)類型和返回類型不同,功能完全一樣。類似這樣的情況,我們需要寫(xiě)一段通用的代碼是用于多種不同的數(shù)據(jù)類型,這樣會(huì)使代碼的可重用性大大提高,從而提高軟件的開(kāi)發(fā)效率。使用函數(shù)模板就是為了達(dá)到這一目的。程序員只對(duì)函數(shù)模板編寫(xiě)一次,然后基于調(diào)用函數(shù)時(shí)提供的參數(shù)類型,C++編譯器將自動(dòng)產(chǎn)生相應(yīng)的函數(shù)來(lái)正確地處理該類型的數(shù)據(jù)。
(1)函數(shù)模板的定義形式為
template <模板參數(shù)表>
類型名 函數(shù)名(參數(shù)表)
{函數(shù)體定義
}
所有函數(shù)模板的定義都是用關(guān)鍵字template開(kāi)始的,該關(guān)鍵字之后是用尖括號(hào)<>括起來(lái)的“模板參數(shù)表”。模板參數(shù)表由用逗號(hào)隔開(kāi)的模板參數(shù)構(gòu)成,可以包括以下內(nèi)容:
①class(或typedef)標(biāo)識(shí)符,指明可以接收一個(gè)類型參數(shù)。這些類型參數(shù)代表的是類型,可以是內(nèi)部類型或者自定義類型。
②“類型說(shuō)明符”標(biāo)識(shí)符,指明可以接收一個(gè)由“類型說(shuō)明符”所規(guī)定類型的常量作為參數(shù)。
③template<參數(shù)表>class標(biāo)識(shí)符,指明可以接收一個(gè)類模板名作為參數(shù)。
類型參數(shù)可以用來(lái)指定函數(shù)模板本身的形參類型、返回值類型,以及聲明函數(shù)中的局部變量。函數(shù)模板中函數(shù)體的定義方式與定義普通函數(shù)類似。
【例1】求絕對(duì)值的函數(shù)模板
template<class T>
T abs(T x)
{return x < 0 ? -x : x;
}
int main()
{int n = -5;cout << abs(n) << endl;double m = -6.8;cout << abs(m) << endl;return 0;
}
運(yùn)行結(jié)果:
分析:
①在上述主函數(shù)中調(diào)用abs()時(shí),編譯器從實(shí)參的類型推導(dǎo)出函數(shù)模板的類型參數(shù)。
②當(dāng)類型參數(shù)的含義確定后,編譯器將以函數(shù)模板為樣板,生成一個(gè)函數(shù),這一過(guò)程稱為函數(shù)模板的實(shí)例化。
例如,對(duì)于調(diào)用表達(dá)式abs(n),由于實(shí)參n是int類型,所以推導(dǎo)出函數(shù)模板中類型參數(shù)T為int,接著,編譯器以函數(shù)模板為樣板,生成如下函數(shù),該函數(shù)為函數(shù)模板abs的一個(gè)實(shí)例:
int abs(int x)
{return x < 0 ? -x : x;
}
同樣,對(duì)于調(diào)用表達(dá)式abs(m),由于實(shí)參m是double型,所以推導(dǎo)出函數(shù)模板中類型參數(shù)T為double,接著,編譯器以函數(shù)模板為樣板,生成如下函數(shù):
double abs(double x)
{return x < 0 ? -x : x;
}
③因此,當(dāng)主函數(shù)第一次調(diào)用abs時(shí),執(zhí)行的實(shí)際上是由函數(shù)模板生成的函數(shù)int abs(int x);
,主函數(shù)第二次調(diào)用abs時(shí),執(zhí)行的實(shí)際上是由函數(shù)模板生成的函數(shù)double abs(double x);
。
【例2】函數(shù)模板示例
template<class T>
void outputA(const T* arr, int n)
{for (int i = 0;i < n; i++){cout << arr[i] << " ";}cout << endl;
}int main()
{const int A_n = 5;const int B_n = 6;const int C_n = 7;int arr[A_n] = { 1,2,3,4,5 };cout << "輸出數(shù)組arr的內(nèi)容:" << " ";outputA(arr, A_n);double brr[B_n] = { 1.1,2.2,3.3,4.4,5.5,6.6 };cout << "輸出數(shù)組brr的內(nèi)容:" << " ";outputA(brr, B_n);char crr[C_n] = "Hi yyn";cout << "輸出數(shù)組crr的內(nèi)容:" << " ";outputA(crr, C_n);return 0;
}
運(yùn)行結(jié)果:
分析:
函數(shù)模板中聲明了類型參數(shù)T,表示一種抽象的類型。當(dāng)編譯器檢測(cè)到程序中調(diào)用函數(shù)模板outputA時(shí),便用outputA的第一個(gè)實(shí)參的類型替換掉整個(gè)模板定義中的T,并建立用來(lái)輸出指定類型數(shù)組的一個(gè)完整的函數(shù),然后再編譯這個(gè)新建的函數(shù)。
主函數(shù)中聲明了3中不同類型的數(shù)組,int型數(shù)組arr,double型數(shù)組brr和char型數(shù)組crr,長(zhǎng)度分別為5,6,7。然后調(diào)用函數(shù)模板生成相應(yīng)的函數(shù),最后在屏幕上輸出每個(gè)數(shù)組。編譯過(guò)程中針對(duì)3種數(shù)據(jù)類型生成的函數(shù)如下:
outputA(a,A_n);//適用于int類型的outputA模板函數(shù)
outputA(b,B_n);//適用于double類型的outputA模板函數(shù)
outputA(c,C_n);//適用于char類型的outputA模板函數(shù)
由上例可以看出,模板函數(shù)與重載密切相關(guān)。從函數(shù)模板產(chǎn)生的相關(guān)函數(shù)都是同名的,編譯器用重載的方法調(diào)用相應(yīng)的函數(shù)。另外函數(shù)模板本身也可以用多種方法重載。
(2)模板函數(shù)的使用形式和函數(shù)的本質(zhì)區(qū)別
①函數(shù)模板本身在編譯時(shí)不會(huì)生成任何目標(biāo)代碼,只有由模板生成的實(shí)例會(huì)生成目標(biāo)代碼。
②被多個(gè)源文件引用的函數(shù)模板,應(yīng)當(dāng)連同函數(shù)體一同放在頭文件中,而不能像普通函數(shù)那樣只將聲明放在頭文件中。
③函數(shù)指針也只能指向函數(shù)模板的實(shí)例,而不能指向函數(shù)模板本身。
2.類模板
使用類模板使用戶可以為類定義一種模式,使得類中的某些數(shù)據(jù)成員、某些成員函數(shù)的參數(shù)、返回值或局部變量能取任意類型(包括系統(tǒng)預(yù)定義的和用戶自定義的)。
類是對(duì)一組對(duì)象的公共性質(zhì)的抽象,而類模板則是對(duì)不同類的公共性質(zhì)的抽象,因此,類模板是屬于更高層次的抽象。由于類模板需要一種或多種類型參數(shù),所以類模板也常常稱為參數(shù)化類。
(1)類模板聲明的語(yǔ)法形式
template<模板參數(shù)表>
class 類名
{類成員聲明;
};
其中類成員的聲明方法和普通類的定義幾乎相同,只是它的各個(gè)成員(數(shù)據(jù)成員和函數(shù)成員)中通常要用到模板的類型參數(shù)T。其中“模板參數(shù)表”的形式與函數(shù)模板中的“模板參數(shù)表”相同。
如果需要在類模板以外定義其成員函數(shù),則要采用以下的形式:
template<模板參數(shù)表>
類型名 類名<模板參數(shù)標(biāo)識(shí)符列表>::函數(shù)名(參數(shù)表)
一個(gè)類模板聲明,其自身并不是一個(gè)類,它說(shuō)明了類的一個(gè)家族,只有被其他代碼引用時(shí),類模板才根據(jù)引用的需要生成具體的類。類模板的實(shí)例化過(guò)程在程序中時(shí)隱藏的。
使用一個(gè)類模板建立對(duì)象時(shí),應(yīng)以如下形式聲明:
模板名<模板參數(shù)表>對(duì)象名1,...,對(duì)象名n;
【例】類模板應(yīng)用舉例
在本例中,聲明一個(gè)實(shí)現(xiàn)任意類型數(shù)據(jù)存取的類模板S,然后通過(guò)具體數(shù)據(jù)類型參數(shù)對(duì)類模板進(jìn)行實(shí)例化,生成類,然后類在被實(shí)例化生成對(duì)象s1,s2,s3和d。
struct student//結(jié)構(gòu)體student
{int id;//學(xué)號(hào)float avg;//平均分
};template<class T>//類模板:實(shí)現(xiàn)對(duì)任意類型數(shù)據(jù)進(jìn)行存取
class S
{
private:T item;//用于存放任意類型的數(shù)據(jù)bool Isvalue;//標(biāo)記item是否被存入
public:S();//默認(rèn)構(gòu)造函數(shù)T& getE();//提取數(shù)據(jù)函數(shù)void putE(const T& x);//存入數(shù)據(jù)函數(shù)
};template<class T>//默認(rèn)構(gòu)造函數(shù)的實(shí)現(xiàn)
S<T>::S():Isvalue(false){}template<class T>//提取數(shù)據(jù)函數(shù)的實(shí)現(xiàn)
T&S<T>::getE()
{ if (!Isvalue)//如果提取的是沒(méi)有初始化的數(shù)據(jù),則程序終止{cout << "數(shù)據(jù)不存在" << endl;exit(1);//使程序完全退出,返回到操作系統(tǒng)//參數(shù)可用來(lái)表示程序終止的原因,可以被操作系統(tǒng)接收}elsereturn item;//返回item中存放的數(shù)據(jù)
}template<class T>//存入函數(shù)的實(shí)現(xiàn)
void S<T>::putE(const T& x)
{Isvalue = true;//將Isvalue設(shè)置為true,表示item中已存入數(shù)值item = x;//將x的值存入item
}int main()
{S<int>s1, s2;//定義兩個(gè)S<int>類對(duì)象s1和s2,其中數(shù)據(jù)成員item為int型s1.putE(3);//向?qū)ο髎1中存入數(shù)據(jù)(初始化對(duì)象s1為3)s2.putE(-7);//向?qū)ο髎2中存入數(shù)據(jù)(初始化對(duì)象s1為-7)cout << s1.getE() << " " << s2.getE() << endl;//輸出對(duì)象s1和s2的數(shù)據(jù)成員student g = { 1000,23 };//定義student類型結(jié)構(gòu)體變量的同時(shí)賦予初值S<student>s3;//定義S<student>類對(duì)象s3,其中數(shù)據(jù)成員item為student類型s3.putE(g);//向?qū)ο髎3中存入數(shù)據(jù)(初始化對(duì)象s3)cout << "這個(gè)學(xué)生的id是" << s3.getE().id << endl;//輸出對(duì)象s3的數(shù)據(jù)成員S<double>d;//定義S<double>類對(duì)象d,其中數(shù)據(jù)成員item為double類型cout << "檢索對(duì)象d";cout << d.getE() << endl;//輸出對(duì)象d的數(shù)據(jù)成員//由于對(duì)象d未經(jīng)初始化,在執(zhí)行函數(shù)d.getE()過(guò)程中導(dǎo)致程序終止return 0;
}
運(yùn)行結(jié)果: