溫馨提示×

溫馨提示×

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

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

C++模板編程的示例分析

發(fā)布時間:2021-11-02 13:50:02 來源:億速云 閱讀:129 作者:小新 欄目:開發(fā)技術(shù)

這篇文章主要為大家展示了“C++模板編程的示例分析”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“C++模板編程的示例分析”這篇文章吧。

    模板初階

    泛型編程

    在計算機程序設(shè)計領(lǐng)域,為了避免因數(shù)據(jù)類型的不同,而被迫重復(fù)編寫大量相同業(yè)務(wù)邏輯的代碼,人們發(fā)展的泛型及泛型編程技術(shù)。什么是泛型呢?實質(zhì)上就是不使用具體數(shù)據(jù)類型(例如 int、double、float 等),而是使用一種通用類型來進行程序設(shè)計的方法,該方法可以大規(guī)模的減少程序代碼的編寫量,讓程序員可以集中精力用于業(yè)務(wù)邏輯的實現(xiàn)。泛型也是一種數(shù)據(jù)類型,只不過它是一種用來代替所有類型的“通用類型”

    我們通常如何實現(xiàn)一個通用的交換函數(shù)呢?

    void Swap(int& left, int& right)
    {
        int temp = left;
        left = right;
        right = temp;
    }
    void Swap(double& left, double& right)
    {
        double temp = left;
        left = right;
        right = temp;
    }
    void Swap(char& left, char& right)
    {
        char temp = left;
        left = right;
        right = temp;
    }
    ......

    Swap函數(shù)能實現(xiàn)各種類型的變量交換,但是只要類型不同就需要重新寫一個

    使用函數(shù)重載雖然可以實現(xiàn),但是有一下幾個不好的地方:

    • 重載的函數(shù)僅僅只是類型不同,代碼的復(fù)用率比較低,只要有新類型出現(xiàn)時,就需要增加對應(yīng)的函數(shù)

    • 代碼的可維護性比較低,一個出錯可能所有的重載均出錯,那能否告訴編譯器一個模版,讓編譯器根據(jù)不同的類型利用該模版來生成代碼呢?

    可以的,C++語法中有了模板:

    函數(shù)模板

    函數(shù)模板概念

    所謂函數(shù)模板,實際上是建立一個通用函數(shù),它所用到的數(shù)據(jù)的類型(包括返回值類型、形參類型、局部變量類型)可以不具體指定,而是用一個虛擬的類型來代替(實際上是用一個標識符來占位),等發(fā)生函數(shù)調(diào)用時再根據(jù)傳入的實參來逆推出真正的類型。 這個通用函數(shù)就稱為 函數(shù)模板(Function Template) 。函數(shù)模板代表了一個函數(shù)家族,該函數(shù)模板與類型無關(guān),在使用時被參數(shù)化,根據(jù)實參類型產(chǎn)生函數(shù)的特定類型版本。

    函數(shù)模板格式

    template<typename T1, typename T2,…,typename Tn>
    返回值類型 函數(shù)名(參數(shù)列表){}

    template<typename T>
    //或者 template<class T>
    void Swap(T& x1, T& x2)
    {
        T temp = left;
        left = right;
        right = temp;
    }

    T1,T2等等是什么類型現(xiàn)在也不確定,一會用的時候才能確定

    注意:

    typename是用來定義模板參數(shù)關(guān)鍵字,也可以使用class

    函數(shù)模板的原理

    函數(shù)模板本身并不是函數(shù),是編譯器根據(jù)調(diào)用的參數(shù)類型產(chǎn)生特定具體類型函數(shù)的模具,所以其實模板就是將本來應(yīng)該我們做的重復(fù)的事情交給了編譯器,我們看下面的例子:

    template<class T>
    void Swap(T& x, T& y)
    {
    	T temp = x;
    	x = y;
    	y = temp;
    }
    int main()
    {
    	int a = 1;
    	int b = 2;
    	Swap(a, b);
    	char A = 'a';
    	char B = 'b';
    	Swap(A,B);
    	return 0;
    }

    C++模板編程的示例分析

    在編譯器編譯階段,對于模板函數(shù)的使用,編譯器需要根據(jù)傳入的實參類型來推演生成對應(yīng)類型的函數(shù)以供調(diào)用。比如:當用int類型使用函數(shù)模板時,編譯器通過對實參類型的推演,將T確定為int類型,然
    后產(chǎn)生一份專門處理int類型的代碼,對于字符類型也是如此。

    然而當我們在寫了函數(shù)時,不會進入模板函數(shù)里,沒有寫具體的函數(shù)時,就會進入模板函數(shù)里,我們看下面的例子:

    void Swap(int& x, int& y)
    {
    	int temp = x;
    	x = y;
    	y = temp;
    }
    template<class T>
    void Swap(T& x, T& y)
    {
    	T temp = x;
    	x = y;
    	y = temp;
    }
    int main()
    {
    	int a = 1;
    	int b = 2;
    	Swap(a, b);
    	char A = 'a';
    	char B = 'b';
    	Swap(A,B);
    	return 0;
    }

    我們進行調(diào)式:

    C++模板編程的示例分析

    我們可以看到int類型的交換函數(shù)我們寫了,調(diào)用時調(diào)用的是我們寫的,而char類型的我們沒寫,就用了模板。

    那么這里調(diào)用的是模板函數(shù)嗎?

    不是的,實際上這里會有兩個過程

    1、模板推演,推演T的具體類型是什么

    2、推演出T的具體類型后實例化生成具體的函數(shù)

    上面的代碼實例化生成了下面的函數(shù):

    void Swap(char& x, char& y)
    {
    	char temp = x;
    	x = y;
    	y = temp;
    }

    真正調(diào)用的還是兩個函數(shù),但是其中的一個函數(shù)不是我們自己寫的,而是我們給了編譯器一個模板,然后編譯器進行推演在編譯之前實例化生成三個對應(yīng)的函數(shù),模板是給編譯器用的,編譯器充當了寫函數(shù)的工具:

    C++模板編程的示例分析

    可以看到這里是調(diào)用了Swap<char>函數(shù)

    在C++當中,其實內(nèi)置類型也可以像自定義類型那樣這樣初始化:

    int a(1);
    int(2);//匿名

    C++模板編程的示例分析

    void Swap(T& x1, T& x2)
    {
        T temp(x1);
        x1 = x2;
        x2 = x1;
    }

    所以模板還可以這樣寫,可以使內(nèi)置類型和自定義類型兼容:

    void Swap(T& x1, T& x2)
    {
        T temp(x1);
        x1 = x2;
        x2 = x1;
    }

    我們來具體看一看函數(shù)模板的實例化:

    函數(shù)模板的實例化

    用不同類型的參數(shù)使用函數(shù)模板時,稱為函數(shù)模板的實例化。模板參數(shù)實例化分為:隱式實例化和顯式實例化。

    隱式實例化:讓編譯器根據(jù)實參推演模板參數(shù)的實際類型

    template<class T>
    T Add(const T& left, const T& right)
    {
    	return left + right;
    }
    int main()
    {
        int a1 = 10, a2 = 20;
        double d1 = 10.0, d2 = 20.0;
        Add(a1, a2);
        Add(d1, d2);
    // 此時有兩種處理方式:1. 用戶自己來強制轉(zhuǎn)化 2. 使用顯式實例化
        Add(a1, d2);
        return 0;
    }

    該語句是不能夠通過編譯的,因為在編譯期間,當編譯器看到該實例化時,用a1去推T是int,而用d2去推是double,但是模板參數(shù)列表里只有一個T,編譯器不能明確該T是int還是double,T是不明確的,所以編譯器會報錯

    那么怎么處理呢?

    解決方式:

    1、調(diào)用者自己強制轉(zhuǎn)換

    //實參去推演形參的類型
    Add(a1, (int)d2);
    Add((double)a1,d2);

    這里可以將d2先強制類型轉(zhuǎn)換,然后再進行推演;或者將a1先強制類型轉(zhuǎn)換再進行推演

    2、使用顯式實例化

    //實參不需要去推演形參的類型,顯式實例化指定T的類型
    Add<int>(a1, d2);
    Add<double>(a1,d2);

    這種方式是顯式實例化指定T的類型

    顯式實例化在哪種場景可用呢?看下面的這種場景:

    class A
    {
        A(int a=0):_a(a)
        {}
    private:
        int _a;
    };
    template<class T>
    T func(int x)
    {
        T a(x);
        return a;
    }
    int main()
    {
        func<A>(1);
        func<int>(2);
        return 0;
    }

    有些函數(shù)模板里面參數(shù)中沒用模板參數(shù),函數(shù)體內(nèi)才有用到模板參數(shù),此時就無法通過參數(shù)去推演T的類型,這時只能顯示實例化

    上面我們提了一點模板參數(shù)的匹配原則,下面我們具體看看模板參數(shù)的匹配原則:

    模板參數(shù)的匹配原則

     一個非模板函數(shù)可以和一個同名的函數(shù)模板同時存在,此時如果調(diào)用地方參數(shù)與非模板函數(shù)完全匹配,則會調(diào)用非模板函數(shù)

    int Add(int left, int right)
    {
    	return left + right;
    }
    // 通用加法函數(shù)
    template<class T>
    T Add(T left, T right)
    {
    	return left + right;
    }
    int main()
    {
        Add(1,2);//調(diào)用自己的函數(shù)
        return 0;
    }

    Add(1,2)參數(shù)是int類型,而我們有現(xiàn)成的int參數(shù)的Add函數(shù),所以有現(xiàn)成的就用現(xiàn)成的,編譯器也會偷懶

    那么如果我們想讓這里調(diào)用必須用模板呢?顯式實例化:

    Add<int>(1,2);

    這樣編譯器就強制會用模板去實例化函數(shù)

    一個非模板函數(shù)可以和一個同名的函數(shù)模板同時存在,此時如果調(diào)用地方參數(shù)與非模板函數(shù)不完全匹配,則會優(yōu)先使用模板實例化函數(shù)

    int Add(int left, int right)
    {
    	return left + right;
    }
    // 通用加法函數(shù)
    template<class T>
    T Add(T left, T right)
    {
    	return left + right;
    }
    int main()
    {
        Add(1.1,2.2);//使用模板實例化函數(shù)
        return 0;
    }

    模板匹配原則總結(jié):

    有現(xiàn)成完全匹配的,那就直接調(diào)用,沒有現(xiàn)成調(diào)用的,實例化模板生成,如果有需要轉(zhuǎn)換類型才能匹配的函數(shù)(也就是不完全匹配),那么它會優(yōu)先選擇去實例化模板生成。

    優(yōu)先級:

    完全匹配>模板>轉(zhuǎn)換類型匹配

    類模板

    類模板的定義格式

    template<class T1, class T2, ..., class Tn>
    class 類模板名
    {
    	//類內(nèi)成員定義
    };

    我們來看一個類模板的使用場景:

    typedef int STDateType;
    class Stack
    {
    private:
        STDateType* _a;
        int _top;
        int _capacity;
    };
    int main()
    {
        Stack st1;
        Stack st2;
        return 0;
    }

    這是我們定義的棧數(shù)據(jù)結(jié)構(gòu),我們創(chuàng)建了兩個棧對象,但是現(xiàn)在st1和st2的存儲數(shù)據(jù)的類型都是int,要是想轉(zhuǎn)換數(shù)據(jù)類型呢?

    typedef double STDateType;

    我們這樣就轉(zhuǎn)換了,但是我們要是想st1為int,st2為double呢:

    Stack st1;//int
    Stack st2;//double

    此時需要寫多個類,名字還得不一樣,如下:

    typedef int STDateType1;
    typedef double STDateType2;
    class IntStack
    {
    private:
        STDateType1* _a;
        int _top;
        int _capacity;
    };
    class DoubleStack
    {
    private:
        STDateType2* _a;
        int _top;
        int _capacity;
    };

    這樣太麻煩了,那么什么辦法可以解決呢?類模板可以解決:

    //類模板
    template<class T>
    class Stack
    {
    private:
        T* _a;
        int _top;
        int _capaticy;
    };
    int main()
    {
        //類模板的使用都是顯式實例化
        Stack<double> st1;
        Stack<int> st2;
        return 0;
    }

    注意:Stack不是具體的類,是編譯器根據(jù)被實例化的類型生成具體類的模具

    類模板的實例化

    //類模板
    template<class T>
    class Stack
    {
    public:
        Stack(int capacity = 4)
            :_a(new T(capacity))
             ,_top(0)
             ,_capacity(capacity)
            {}
        ~Stack()
        {
            delete[] _a;
            _a = nullptr;
            _top = _capacity = 0;
        }
        void Push(const T& x)
        {
            //...
        }
    private:
        T* _a;
        int _top;
        int _capaticy;
    };
    int main()
    {
        //類模板的使用都是顯式實例化
        Stack<double> st1;
        Stack<int> st2;
        return 0;
    }

    注意:類模板的使用都是顯式實例化

    假設(shè)我們想類里面聲明和類外面定義成員函數(shù)呢?

    //類模板
    template<class T>
    class Stack
    {
    public:
        Stack(int capacity = 4)
            :_a(new T(capacity))
             ,_top(0)
             ,_capacity(capacity)
            {}
        ~Stack()
        {
            delete[] _a;
            _a = nullptr;
            _top = _capacity = 0;
        }
        //假設(shè)我們想類里面聲明和定義分離呢?
        void Push(const T& x);
    private:
        T* _a;
        int _top;
        int _capaticy;
    };
    //在類外面定義
    template<class T>
    void Stack<T>::Push(const T& x);
    {
        //...
    }
    int main()
    {
        //類模板的使用都是顯式實例化
        Stack<TreeNode*> st1;
        Stack<int> st2;
        return 0;
    }
    //在類外面定義
    template<class T>
    void Stack<T>::Push(const T& x);
    {
        //...
    }

    在類外面定義我們必須要加模板的關(guān)鍵字,以及需要在實現(xiàn)的函數(shù)前面表明域Stack<T>。普通類,類名就是類型,對于類模板,類名不是類型,類型是Stack<T>,需要寫指定

    注意:

    模板不支持把聲明寫到.h,定義寫到.cpp,這種聲明和定義分開實現(xiàn)的方式,會出現(xiàn)鏈接錯誤

    以上是“C++模板編程的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

    向AI問一下細節(jié)

    免責聲明:本站發(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