溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C++中函數(shù)模板如何定義與使用

發(fā)布時間:2022-09-07 09:48:29 來源:億速云 閱讀:125 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹“C++中函數(shù)模板如何定義與使用”,在日常操作中,相信很多人在C++中函數(shù)模板如何定義與使用問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”C++中函數(shù)模板如何定義與使用”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

    1. 前言

    什么是函數(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ù)類型,由使用者決定。

    2. 初識函數(shù)模板

    2.1 語法

    在重構(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ī)則的前提下,可以起任意名稱。

    2.2 實例化

    函數(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

    說明:

    處先定義一個函數(shù)指針類型。

    處這行代碼,千萬不要理解是取函數(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);
    }

    ff1函數(shù)都需要使用 test<int>實例,于編譯器而,無法知道 ff1函數(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: 顯示聲明只對一個源文件有效。

    2.3 實參推導(dǎo)

    所謂實參推導(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)出來的。如下圖所示:

    C++中函數(shù)模板如何定義與使用

    使用如上函數(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ù)實參68推導(dǎo)出 T1是 int類型。

    了解什么是實參推導(dǎo)后,使用時,需要知道實參推導(dǎo)是不支持自動類型轉(zhuǎn)換的。如下代碼是錯誤的。

    int res=getMin(4,7.5); //錯誤

    編譯器認定實參 4int類型,實參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; 
    }

    3. 重載函數(shù)模板

    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)換。

    charint之間可以相互轉(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í)行后:

    C++中函數(shù)模板如何定義與使用

    再恢復(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>

    向AI問一下細節(jié)

    免責(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)容。

    c++
    AI