1. 程式人生 > >類模板(四十八)

類模板(四十八)

類模板 多個類型參數 函數模板完全特化 類模板部分特化 類模板完全特化

我們上節博客介紹了泛型編程思想,那麽在 C++ 中是否可以將泛型的思想應用於類呢?答案肯定是顯而易見的,在 C++ 中的標準庫中,就是一些通用的類模板。我們先來看看類模板有哪些特性,它主要是用於存儲和組織數據元素,類中數據組織的方式和數據元素的具體類型無關,如:數組類、鏈表類、Stack 類等。C++ 中將模板的思想應用於類,使得類的實現不關註數據元素的具體類型,而只關註類所需要實現的功能。

在 C++ 中的類模板是以相同的方式處理不同的類型,並且在類聲明前使用 template 進行標識< typename T > 用於說明類中使用的泛指類型 T。類模板只能顯示指定具體類型,無法自動推導;使用具體類型(Type)定義對象

。聲明的泛指類型 T 可以出現在類模板的任意地方;編譯器對類模板的處理方式和函數模板相同:即 a> 從類模板通過具體類型產生不同的類;b> 在聲明的地方對類模板代碼本身進行編譯;c> 在使用的地方對參數替換後的代碼進行編譯。

下來我們還是以代碼為例來進行分析

#include <iostream>
#include <string>

using namespace std;

template < typename T >
class Operator
{
public:
    T add(T a, T b)
    {
        return a + b;
    }
    
    T minus(T a, T b)
    {
        return a - b;
    }
    T multiply(T a, T b)
    {
        return a * b;
    }
    T divide(T a, T b)
    {
        return a / b;
    }
};

int main()
{
    Operator<int> op1;
    
    cout << "op1.add(1, 2) = " << op1.add(1, 2) << endl;
    
    Operator<string> op2;
    
    cout << op2.add("D.T.", "Software") << endl;
    //cout << op2.minus("D.T.", "Software") << endl;
    
    return 0;
}

我們來試試看看能不能實現整數和字符串的相加操作呢?

技術分享圖片

我們看到已經實現了,那麽我們再來試試字符串的減法呢?

技術分享圖片

我們看到字符串是不支持減法的,因為編譯器不知道該怎麽進行操作。下來我們寫一個減法操作符的重載函數,看看還會出錯嘛,如下

string operator- (string& l, string& r)
{
    return "Minus";
}

編譯結果如下

技術分享圖片

我們看到編譯沒報錯,也就是說,編譯器在編譯到字符串的減法操作時,它發現不支持,但是為了顯示它的強大沒先報錯,先去查找有沒有實現好的重載函數。結果找到了,便去調用這個函數了。

類模板在工程中的應用遵從以下幾條規則:1、類模板必須在頭文件中定義;2、類模板不能分開實現在不同的文件中;3、類模板外部定義的成員函數需要加上模板<>聲明。下來我們就以工程中的標準再來實現下上面的類模板


Operator.h 源碼

#ifndef _OPERATOR_H_
#define _OPERATOR_H_

template < typename T >
class Operator
{
public:
    T add(T a, T b);
    T minus(T a, T b);
    T multiply(T a, T b);
    T divide(T a, T b);
};

template < typename T >
T Operator<T>::add(T a, T b)
{
    return a + b;
}

template < typename T >
T Operator<T>::minus(T a, T b)
{
    return a - b;
}

template < typename T >
T Operator<T>::multiply(T a, T b)
{
    return a * b;
}

template < typename T >
T Operator<T>::divide(T a, T b)
{
    return a / b;
}

#endif


test.cpp 源碼

#include <iostream>
#include <string>
#include "Operator.h"

using namespace std;

int main()
{
    Operator<int> op1;
    
    cout << op1.add(1, 2) << endl;
    cout << op1.multiply(4, 5) << endl;
    cout << op1.minus(5, 6) << endl;
    cout << op1.divide(10, 5) << endl;
    
    return 0;
}

我們編譯後結果如下

技術分享圖片

我們看到運行已經得到正確的結果了。那麽我們接下來想想類模板能否支持泛型編程中的那種定義任意多個不同類型的參數嗎?顯然是肯定支持了。如下

技術分享圖片

類模板可以是可以被特化的:a> 指定類模板的特定實現;b> 部分類型參數必須顯示指定;c> 根據類型參數分開實現類模板。如下所示

技術分享圖片

類模板的特化類型分為兩種:部分特化和完全特化。部分特化是指用特定規則來約束類型參數,而完全特化則是指完全顯示指定類型參數。如下

技術分享圖片

下來我們還是以代碼為例來進行分析說明

#include <iostream>
#include <string>

using namespace std;

template
< typename T1, typename T2 >
class Test
{
public:
    void add(T1 a, T2 b)
    {
        cout << "void add(T1 a, T2 b)" << endl;
        cout << a + b << endl;
    }
};

template
< typename T1, typename T2 >
class Test < T1*, T2* >
{
public:
    void add(T1* a, T2* b)
    {
        cout << "void add(T1*, T2*)" << endl;
        cout << *a + *b << endl;
    }
};

template
< typename T >
class Test < T, T >
{
public:
    void add(T a, T b)
    {
        cout << "void add(T a, T b)" << endl;
        cout << a + b << endl;
    }
    void print()
    {
        cout << "class Test < T, T >" << endl;
    }
};

template
<  >
class Test < void*, void* >
{
public:
    void add(void* a, void* b)
    {
        cout << "void add(void* a, void* b)" << endl;
        cout << "Error to add void* param..." << endl;
    }
};

int main()
{
    Test<int, float> t1;
    Test<long, long> t2;
    Test<void*, void*> t3;
    
    t1.add(1, 2.5);
    
    cout << endl;
    
    t2.add(5, 5);
    t2.print();
    
    cout << endl;
    
    t3.add(NULL, NULL);
    
    cout << endl;
    
    Test<int*, double*> t4;
    int a = 1;
    double b = 0.1;
    
    t4.add(&a, &b);
    
    return 0;
}

我們先是定義了一個模板類,接著定義了它的部分特化(參數類型為 void*以及參數類型是相同的),最後一個是完全特化。我們來看看編譯結果

技術分享圖片

我們看到編譯是通過的,說明編譯器是支持這樣的寫法的,也正常運行結束了。那麽類模板特化還要註意幾個事項:1、特化只是模板的分開實現,其本質上還是用一個類模板;2、特化類模板的使用方式是統一的,必須顯示指定每一個類型參數。那麽類模板特化與重定義有區別嗎?函數模板可以特化嗎?重定義和特化是不同的,進行重定義時是:一個類模板和一個新類(或者兩個類模板),使用的時候需要考慮如何選擇的問題函數模板是可以特化的,以統一的方式使用類模板和特化類,編譯器自動優先選擇特化類。函數模板只支持類型參數完全特化,如下

技術分享圖片

下來我們還是以代碼為例來進行分析

#include <iostream>
#include <string>

using namespace std;

template
< typename T1, typename T2 >
class Test
{
public:
    void add(T1 a, T2 b)
    {
        cout << "void add(T1 a, T2 b)" << endl;
        cout << a + b << endl;
    }
};

class Test_void
{
public:
    void add(void* a, void* b)
    {
        cout << "void add(void* a, void* b)" << endl;
        cout << "Error to add void* param..." << endl;
    }
};

template
< typename T >
bool Equal(T a, T b)
{
    cout << "bool Equal(T a, T b)" << endl;
    
    return a == b;
}

template
< >
bool Equal<double>(double a, double b)
{
    const double delta = 0.00000000000001;
    double r = a - b;
    
    cout << "bool Equal<double>(double a, double b)" << endl;
    
    return (-delta < r) && (r < delta);
}
/*
bool Equal(double a, double b)
{
    const double delta = 0.0000000000001;
    double r = a - b;
    
    cout << "bool Equal(double a, double b)" << endl;
    
    return (-delta < r) && (r < delta);
}*/

int main()
{
    cout << Equal( 1, 1 ) << endl;
    cout << Equal( 0.001, 0.001 ) << endl;
    
    return 0;
}

我們看到重定義則是類 Test_void 那樣的,我們在底下定義了 Equal 函數的特化模板,下來編譯看看結果

技術分享圖片

我們看到編譯器確實支持函數特化的寫法,並且完美運行。下來我們將全局函數 Equal 的註釋去掉,再來看看編譯結果

技術分享圖片

我們看到調用的是全局函數,如果我們非要在這調用我們的函數特化模板該怎樣做呢?我們只需在 main 函數中的最後一個 Equal 函數加上 <>,改為 cout << Equal<>( 0.001, 0.001 ) << endl;再來看看編譯結果

技術分享圖片

我們看到已經已經成功的調用函數模板了。在工程中的建議是:當需要重載函數模板時,優先考慮使用模板特化;當模板特化無法滿足需求時,再來使用函數重載!通過對類模板的學習,總結如下:1、泛型編程的思想是可以應用於類的;2、類模板以相同的方式處理不同類型的數據;3、類模板非常適用於編寫數據結構相關的代碼,它在使用時只能顯示指定類型;4、類模板可以定義任意多個不同的類型參數;5、類模板可以被部分特化和完全特化,其特化的本質是模板的分開實現;6、函數模板只支持完全特化,工程中使用模板特化代替類(函數)重定義。


歡迎大家一起來學習 C++ 語言,可以加我QQ:243343083

類模板(四十八)