您好,登錄后才能下訂單哦!
這篇文章主要介紹“C++中函數(shù)模板如何定義與使用”,在日常操作中,相信很多人在C++中函數(shù)模板如何定義與使用問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”C++中函數(shù)模板如何定義與使用”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
什么是函數(shù)模板?
理解什么是函數(shù)模板,須先搞清楚為什么需要函數(shù)模板。
如果現(xiàn)在有一個需求,要求編寫一個求 2 個數(shù)字中最小數(shù)字的函數(shù),這 2 個數(shù)字可以是 int
類型,可以是 float
類型,可以是所有可以進行比較的數(shù)據(jù)類型
常規(guī)編寫方案:針對不同的數(shù)據(jù)類型編寫不同的函數(shù)。
#include <iostream> using namespace std; //針對 int 類型 int getMin(int num1,int num2) { return num1>num2?num2:num1; } //針對 float 類型 float getMin(float num1,float num2) { return num1>num2?num2:num1; } //針對 double 類型 double getMin(double num1,double num2) { return num1>num2?num2:num1; } int main() { //整型數(shù)據(jù)比較 int min=getMin(10,4); cout<<min<<endl; //float 類型數(shù)據(jù)比較 float minf=getMin(3.8f,2.9f); cout<<minf<<endl; //double 類型數(shù)據(jù)比較 double mind=getMin(1.8,2.1); cout<<mind<<endl; return 0; }
重載函數(shù)(當(dāng)然上述幾個函數(shù)名也可以不相同)可以解決這個問題。顯然,上述 3 個函數(shù)的算法邏輯是一模一樣的,僅是函數(shù)的參數(shù)類型不一樣。既然函數(shù)的形式參數(shù)可以接受值不同的同類型數(shù)據(jù),能否把函數(shù)形參的數(shù)據(jù)類型也參數(shù)化,用來接受不同的數(shù)據(jù)類型。
函數(shù)模板實質(zhì)就是參數(shù)化數(shù)據(jù)類型,稱這種編程模式為數(shù)據(jù)類型泛化編程。
Tips: 泛化的意思是一般化、抽象化,先不明確指定,需要時再指定。
如:我對班長說,我需要一名學(xué)生幫我搬課桌。這名學(xué)生到底是誰,我沒有明確,由班長具體化。換在函數(shù)模板中,表示函數(shù)模板需要一種數(shù)據(jù)類型的數(shù)據(jù),具體是什么數(shù)據(jù)類型,由使用者決定。
在重構(gòu)上述代碼時,先了解一下函數(shù)模板的語法結(jié)構(gòu):
template <模板形式參數(shù)列表> 返回類型 函數(shù)名(函數(shù)形式參數(shù)列表)
{
函數(shù)體
}
語法結(jié)構(gòu)說明:
1.template
關(guān)鍵字說明了此函數(shù)是一個函數(shù)模板。
2.template <>
的尖括號里是模板參數(shù)列表,也可稱此處的參數(shù)為數(shù)據(jù)類型參數(shù),用來對函數(shù)算法所針對的數(shù)據(jù)類型的泛化,表示可以接受不同的數(shù)據(jù)類型。
Tips:模板參數(shù)列表中的參數(shù)可以是一個或多個泛化數(shù)據(jù)類型參數(shù),也可以是一個或多個具體數(shù)據(jù)類型參數(shù)。
泛化類型參數(shù)前面要加上 typename
關(guān)鍵字。
3.后面便是函數(shù)的一般性說明,只是在函數(shù)中可以使用模板數(shù)據(jù)類型參數(shù)。
Tips: 函數(shù)模板中有 2 類參數(shù),模板參數(shù)和函數(shù)參數(shù)。
使用函數(shù)模板重構(gòu)上面求最小值的代碼:
template<typename T> T getMin(T num1,T num2){ return num1>num2?num2:num1; }
說明:
1.typename T
聲明了一個數(shù)據(jù)類型參數(shù),用于泛化任一種數(shù)據(jù)類型,或者說 T
可以表示任意一種數(shù)據(jù)類型。
Tips:typename 是 C++11 標(biāo)準(zhǔn),也可以使用 class
關(guān)鍵字,但建議不用,避免和類定義混淆。
2.T
數(shù)據(jù)類型可以作為函數(shù)的參數(shù)類型、返回值類型、以及作為算法實施過程中臨時變量的數(shù)據(jù)類型。
Tips: T
是一個變量標(biāo)識符,在遵循變量命名規(guī)則的前提下,可以起任意名稱。
函數(shù)模板如現(xiàn)實生活中制作陶瓷的模具一樣,只有往模具中注入原材料,才能生成可實用的陶瓷。函數(shù)模板不是函數(shù),僅是一個模板,不能直接調(diào)用,需要實例化后才能調(diào)用。
實例化:指編譯器根據(jù)開發(fā)者對函數(shù)模板注入的具體(實參)數(shù)據(jù)類型構(gòu)造出一個真正的函數(shù)實體(實例),這個過程由編譯器自動完成,且實例化的函數(shù)對于開發(fā)者不可見。
int res= getMin<int>(1,6); cout<<res<<endl; //輸出結(jié)果:1
如上,編譯器通過函數(shù)模板<>
內(nèi)的int
數(shù)據(jù)類型,實例化的函數(shù)可以對 int
類型的數(shù)據(jù)進行算法操作。同理,下面的代碼會讓編譯器實例化針對不同數(shù)據(jù)類型的數(shù)據(jù)進行算法操作的函數(shù)。
//實例化原型為 float getMin(float num1,float num2){函數(shù)體} 的函數(shù) float resf=getMin<float>(3.2f,8.2f); cout<<resf<<endl; //實例化原型為 double getMin(double num1,double num2){函數(shù)體} 的函數(shù) double resd=getMin<double>(1.2,0.2); cout<<resd<<endl; //實例化原型為 char getMin(char num1,char num2){函數(shù)體} 的函數(shù) char resc=getMin<char>('A','B'); cout<<resc<<endl; //輸出結(jié)果分別為 3.2f 0.2 A
使用函數(shù)模板的優(yōu)點不言而喻,聲明一次,便可以實現(xiàn)針對不同數(shù)據(jù)類型的數(shù)據(jù)的操作。當(dāng)然,中間會有匹配、實例化的代價。
Tips:高級業(yè)務(wù)層面的一勞永逸往往會以犧牲底層的性能為代價,但是,這是值得的。
除了通過顯示聲明數(shù)據(jù)類型提示編譯器實例化,也可以使用函數(shù)指針實例化。
typedef int(*PF)(int,int); // 1 PF pf=getMin; // 2 int res= pf(6,8); //3 cout<<res; //4
說明:
1 處先定義一個函數(shù)指針類型。
2 處這行代碼,千萬不要理解是取函數(shù)模板的地址,編譯器在底層做了相應(yīng)處理。
編譯器會根據(jù)函數(shù)指針類型說明先實例化一個函數(shù)。
再取實例化函數(shù)的內(nèi)存地址,并賦值給 pf
。
3 處以函數(shù)指針方式調(diào)用函數(shù)。
實例化時要注意的幾個問題:
1.實例化時,可能會有一個直觀問題:真的能指定任意一種數(shù)據(jù)類型實例化函數(shù)模板嗎?
答案是:任何高級層面的邏輯行為都不能脫離基礎(chǔ)知識的認知范疇,不同的數(shù)據(jù)類型有著語法系統(tǒng)賦予它的運算操作能力,當(dāng)指定一個不支持函數(shù)模板內(nèi)部算法操作的數(shù)據(jù)類型時,必然會出錯。
如聲明一個求 2 個數(shù)字相除的余數(shù)的函數(shù)模板。
template<typename T> T getYuShu(T num1,T num2) { return num1 % num2; }
如果指定 double
數(shù)據(jù)類型實例化 getYuShu
函數(shù)模板時,就會拋出錯誤,因為 double
數(shù)據(jù)類型不能使用 %
運算符。
double res=getYuShu<double>(6.2,2.4); //出錯
Tips: 編譯器在實例化函數(shù)模板時,會遵循語法標(biāo)準(zhǔn)檢查給定的數(shù)據(jù)類型是否支持函數(shù)模板中的運算操作。
2.編譯器實例化的時機。
常規(guī)而言,編譯器會在程序中第一次需要函數(shù)模板的某個實例時對其進行編譯。但是,同一份代碼中,可能會出現(xiàn)對同一個實例多次調(diào)用的需要,如下面的代碼:
template <typename T > test(T num) { return num; } int f() { int res= test<int>(12); return res; } double f1() { int res= test<int>(24); return double(res); }
f
和f1
函數(shù)都需要使用 test<int>
實例,于編譯器而,無法知道 f
和f1
函數(shù)誰先會被調(diào)用(也就無法確定第一次編譯的時間點),但為了保證編譯期間完成實例化工作,早期C++
編譯器采用對同一實例每一次出現(xiàn)的地方都編譯的策略,然后從多個編譯結(jié)果中選一個作為最終結(jié)果,顯然,編譯時間會大大延長。
C++
充許顯式實例化聲明,用來顯示指定某一個函數(shù)模板的實例化的時間點,從而解決同一個實例被多次編譯的問題。其語法如下:
template 返回值類型 模板名<模板參數(shù)列表>(函數(shù)形參列表);
針對上述函數(shù)模板可以編寫如下代碼,告之編譯器編譯時間點。
template <typename T > test(T num) { return num; } //顯示指定實例化 template int test<int>(int);
Tips: 顯示聲明只對一個源文件有效。
所謂實參推導(dǎo),在使用函數(shù)模板時省略<>
,不明確指定數(shù)據(jù)類型參數(shù),而是由編譯器根據(jù)函數(shù)的實參類型自動推導(dǎo)出類型參數(shù)的真正類型。如下代碼:
int res=getMin(4,7);
實參是int
類型, 編譯器由此推導(dǎo)出 T
是 int
類型,從而使用 int
類型實例化函數(shù)模板,類似于下面的顯示聲明代碼:
int res=getMin<int>(4,7);
實參推導(dǎo)可以像調(diào)用普通函數(shù)一樣使用函數(shù)模板。但是實參推導(dǎo)是有前提條件的:函數(shù)參數(shù)使用了類型參數(shù)的才能通過函數(shù)實參類型推導(dǎo)。如下的函數(shù)模板。
template <typename T1,typename T2> T2 myMax(T1 num1,T1 num2) { //函數(shù)體 }
因為 T2
是作為函數(shù)模板的返回類型,是無法通過實參類型推導(dǎo)出來的。如下圖所示:
使用如上函數(shù)模板,需要顯示指定具體的數(shù)據(jù)類型。
double res= myMax<int,double>(6,8); //正確
是否可以讓函數(shù)模板的類型參數(shù)一部分顯示指定,一部分由實參推導(dǎo)?
答案是可以,但是,要求在聲明函數(shù)模板時,把需要顯示指定的類型參數(shù)放在前面,可由實參推導(dǎo)的參數(shù)類型放在后面。把上面的函數(shù)模板的 T1、T2
參數(shù)說明交換位置。
template <typename T2,typename T1> T2 myMax(T1 num1,T1 num2) { //函數(shù)體 }
實例化時,只需要顯示指定 T2
的類型,T1
類型由編譯器根據(jù)實參推導(dǎo)。如下代碼可正確調(diào)用。
double res= myMax<double>(6,8); //正確
編譯器把 T2
指定為 double
類型,然后根據(jù)實參6
和8
推導(dǎo)出 T1
是 int
類型。
了解什么是實參推導(dǎo)后,使用時,需要知道實參推導(dǎo)是不支持自動類型轉(zhuǎn)換的。如下代碼是錯誤的。
int res=getMin(4,7.5); //錯誤
編譯器認定實參 4
是int
類型,實參7.5
是 double
類型,那么是到底是使用 int
類型還是使用 double
類型實例化 getMin
函數(shù)模板,會讓編譯器不知所措、左右為難。
Tips: 即使支持自動類型轉(zhuǎn)換,于編譯器而言也無法知道開發(fā)者是想使用 int
類型還是 double
類型。如此自動類型轉(zhuǎn)換沒有存在的意義。
對于上述問題可以采用如下幾種方案解決:
1.通過強制類型操作把實參轉(zhuǎn)換成統(tǒng)一數(shù)據(jù)類型。
int res=getMin(4,int(7.5)); //或者 int res=getMin(double(4),7.5);
2.顯示指定實例化時的數(shù)據(jù)類型。
int res=getMin<int>(4,7.5); //或者 int res=getMin<double>(4,7.5);
3.如果有必要傳遞 2
個不同類型的參數(shù),可需要修改函數(shù)模板,使其能接受 2
種類型參數(shù)。
template<typename T1,typename T2> T1 getMin(T1 num1,T2 num2){ return num1>num2?num2:num1; }
C++
中普通函數(shù)和函數(shù)模板可以一起重載,面對多個重載函數(shù),編譯器需要提供相應(yīng)的匹配策略。如下代碼:
//普通函數(shù) int getMax(int num1,int num2){ return num1>num2?num1:num2; } //函數(shù)模板 template<typename T> T getMax(T num1,T num2) { return num1>num2?num1:num2; }
如下調(diào)用時,編譯器是選擇普通函數(shù)還是函數(shù)模板?
int res= getMax(6,8);
函數(shù)實參是 int
類型,相比較函數(shù)模板,普通函數(shù)不需要實例化可直接使用,編譯器會優(yōu)先選擇普通函數(shù)。但是如下的調(diào)用,編譯器會選擇函數(shù)模板。
getMax(2.4,6.8); //調(diào)用 getMax<double>(實參推導(dǎo)) getMax('a','b'); //調(diào)用 getMax<char>(實參推導(dǎo)) getMax<>(7,3) //調(diào)用 getMax<int> (實參推導(dǎo)) getMax<double>(4,9) //顯示指定
編譯器選擇函數(shù)模板的原則:
如果函數(shù)模板能實例出一個完全與函數(shù)實參類型相匹配的函數(shù),那么就會選擇函數(shù)模板,如getMax(2.4,6.8);
調(diào)用。編譯器會根據(jù)函數(shù)模板實例化一個double getMax(double a,double b)
函數(shù)與需求完全相匹配的函數(shù)。
如果即想使用實參推導(dǎo),且想使用函數(shù)模板而非普通函數(shù),可以使用空 <>
尖括號語法。如上的 getMax<>(7,7);
調(diào)用。一旦指定<>
標(biāo)識符,顯示指定使用函數(shù)模板,無論其中是否有實參類型說明。
如下的函數(shù)調(diào)用,實參有 2
個,但 2
者之間可以發(fā)生自動類型轉(zhuǎn)換。
char
和int
之間可以相互轉(zhuǎn)換。
getMax('a',98);
編譯器會選擇誰?可以做一個實驗,把普通函數(shù)注釋,保留函數(shù)模板。
#include <iostream> #include <cstring> using namespace std; //函數(shù)模板 template<typename T> T getMax(T num1,T num2) { return num1>num2?num1:num2; } int main(int argc, char** argv) { int t= getMax('a',98) return 0; }
執(zhí)行后:
再恢復(fù)普通函數(shù)后執(zhí)行,代碼可以正常執(zhí)行。顯然,編譯器選擇的是普通函數(shù)。原因很簡單,在使用實參推導(dǎo)時,函數(shù)模板是不支持自動類型轉(zhuǎn)換,而普通函數(shù)表示沒有壓力。
到此,關(guān)于“C++中函數(shù)模板如何定義與使用”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。