1. 程式人生 > >函數模板(四十七)

函數模板(四十七)

函數模板 泛型編程 代碼復用 多參數定義 函數模板重載

我們到目前為止,學習了 C++ 這麽久。提個小問題:在 C++ 中有幾種交換變量的方法呢?通過定義宏代碼塊和定義函數。下來我們做個實驗

#include <iostream>
#include <string>

using namespace std;

#define SWAP(t, a, b)    do                       {                            t c = a;                 a = b;                   b = c;               }while(0)
    
void Swap(int& a, int& b)
{
    int c = a;
    a = b;
    b = c;
}

void Swap(double& a, double& b)
{
    double c = a;
    a = b;
    b = c;
}

void Swap(string& a, string& b)
{
    string c = a;
    a = b;
    b = c;
}

int main()
{
    int a = 0;
    int b = 1;
    
    SWAP(int, a, b);
    
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << endl;

    double m = 2;
    double n = 3;
    
    Swap(m, n);
    
    cout << "m = " << m << endl;
    cout << "n = " << n << endl;
    cout << endl;
    
    string c = "hello";
    string d = "world";
    
    Swap(c, d);
    
    cout << "c = " << c << endl;
    cout << "d = " << d << endl;
    cout << endl;
    
    Swap(a, b);
    
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    
    return 0;
}

我們來看看編譯結果

技術分享圖片

我們看到已經成功實現交換的功能了。宏定義代碼塊的優點是代碼可以復用,適合所有的類型,缺點是編譯器不知道宏的存在,並不會去進行類型檢查。定義函數的優點是編譯器會去對類型進行檢查,而缺點是根據類型需要重復定義函數,無法進行代碼復用。那麽在 C++ 中有沒有解決方案集合兩種方法的優點呢?就是泛型編程泛型編程是指不考慮具體數據類型的編程方式,對於上面 Sawp 函數可以考慮下面的泛型寫法

void Sawp(T& a, T& b)
{
    T t = a;
    a = b;
    b = t;
}

Swap 泛型寫法中的 T 不是一個具體的數據類型,而是泛指任意的數據類型。C++ 中泛型編程便是指函數模板了。一種特殊的函數可用不同類型進行調用,看起來和普通函數很相似,區別是類型可被參數化

。如下

template <typename T>
void Swap(T& a, T& b)
{
    T t = a;
    a = b;
    b = t;
}

函數模板的語法規則是:template 關鍵字用於聲明開始進行泛型編程,typename 關鍵字用於聲明泛指類型,如下

技術分享圖片

函數模板的使用:自動類型推導調用和具體類型顯示調用,如下

int a = 0;
int b = 1;

Swap(a, b)          // 自動推導

float c = 2;
float d = 3;

Swap<float>(c, d);  // 顯示調用

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

#include <iostream>
#include <string>

using namespace std;

template < typename T >
void Swap(T& a, T& b)
{
    T c = a;
    a = b;
    b = c;
}

template < typename T >
void Sort(T a[], int len)
{
    for(int i=0; i<len; i++)
    {
        for(int j=i; j<len; j++)
        {
            if( a[i] > a[j] )
                Swap(a[i], a[j]);
        }
    }
}

template < typename T >
void Println(T a[], int len)
{
    for(int i=0; i<len; i++)
    {
        cout << a[i] << ", ";
    }
    
    cout << endl;
}

int main()
{
    int a[5] = {5, 2, 4, 1, 3};
    
    Println(a, 5);
    Sort(a, 5);
    Println(a, 5);
    
    cout << endl;
    
    string s[5] = {"Java", "C++", "Pascal", "Ruby", "Basic"};
    
    Println(s, 5);
    Sort(s, 5);
    Println(s, 5);
    
    return 0;
}

我們看看能否實現數組和字符串的排序

技術分享圖片

這個函數模板已經實現了我們所想要的功能了。我們有必要再來深入的看下這個函數模板都做了什麽?編譯器從函數模板通過具體類型產生不同的函數,編譯器會對函數模板進行兩次編譯,第一次是對模板本身進行編譯,第二次是對參數替換後的代碼進行編譯。請註意哈,函數模板本身不允許隱式類型轉換,即自動推導類型時,必須進行嚴格匹配;顯示類型指定時,能夠進行隱式類型轉換。

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

#include <iostream>
#include <string>

using namespace std;

class Test
{
public:
    Test()
    {
    }
};

template < typename T >
void Swap(T& a, T& b)
{
    T c = a;
    a = b;
    b = c;
}

typedef void(FuncI)(int&, int&);
typedef void(FuncD)(double&, double&);
typedef void(FuncT)(Test&, Test&);

int main()
{
    FuncI* pi = Swap;   // 編譯器自動推導 T 為 int
    FuncD* pd = Swap;   // 編譯器自動推導 T 為 double
    FuncT* pt = Swap;   // 編譯器自動推導 T 為 Test
    
    cout << "pi = " << reinterpret_cast<void*>(pi) << endl;
    cout << "pd = " << reinterpret_cast<void*>(pd) << endl;
    cout << "pt = " << reinterpret_cast<void*>(pt) << endl;
    
    return 0;
}

我們看看是不是如我們在註釋中所寫的一樣,如果產生具體函數的話,那麽它打印的三個地址就會是不同的。我們來看看編譯結果

技術分享圖片

我們看到編譯器確實在編譯函數模板的時候產生具體的函數了,類的對象都可以作為參數。下來我們看看編譯器是不是會對函數模板進行二次編譯,我們在 Test 類中定義一個私有成員函數,拷貝構造函數。如果進行二次編譯的話,肯定會報錯,因為在 Swap 函數中會有臨時對象的產生,而這塊是私有的。我們來看看編譯結果技術分享圖片

我們看到確實是報錯了,確實是如我們所分析的那樣。函數模板還可以定義任意多個不同的類型參數。如下

技術分享圖片

那麽對於多參數函數模板,它無法自動推導返回值類型,可以從左向右部分指定類型參數。在工程中將返回值參數作為第一個類型參數!所以第一個參數必須指定,下來我們還是以代碼為例來進行分析

#include <iostream>
#include <string>

using namespace std;

template 
< typename T1, typename T2, typename T3 >
T1 Add(T2 a, T3 b)
{
    return static_cast<T1>(a + b);
}

int main()
{
    // T1 = int, T2 = double, T3 = double
    int r1 = Add<int>(0.5, 0.8);

    // T1 = double, T2 = float, T3 = double
    double r2 = Add<double, float>(0.5, 0.8);

    // T1 = float, T2 = float, T3 = float
    float r3 = Add<float, float, float>(0.5, 0.8);

    cout << "r1 = " << r1 << endl;     // r1 = 1
    cout << "r2 = " << r2 << endl;     // r2 = 1.3
    cout << "r3 = " << r3 << endl;     // r3 = 1.3
    
    return 0;
}

我們來看看編譯結果是不是這樣的

技術分享圖片

當函數重載遇見函數模板會發生什麽呢?函數模板可以像普通函數一樣被重載,C++ 編譯器優先考慮普通函數。如果函數模板擴產生一個更好的匹配,那麽選擇模板;可以通過空模板實參列表限定編譯器只匹配模板。如下

技術分享圖片

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

#include <iostream>
#include <string>

using namespace std;

template < typename T >
T Max(T a, T b)
{
    cout << "T Max(T a, T b)" << endl;
    
    return a > b ? a : b;
}

int Max(int a, int b)
{
    cout << "int Max(int a, int b)" << endl;
    
    return a > b ? a : b;
}

template < typename T >
T Max(T a, T b, T c)
{
    cout << "T Max(T a, T b, T c)" << endl;
    
    return Max(Max(a, b), c);
}

int main()
{
    int a = 1;
    int b = 2;
    
    cout << Max(a, b) << endl;            // 普通函數 Max(int, int)
    
    cout << Max<>(a, b) << endl;          // 函數模板 Max(int, int)
    
    cout << Max(3.0, 4.0) << endl;        // 函數模板 Max(double, double)
    
    cout << Max(5.0, 6.0, 7.0) << endl;   // 函數模板 Max(double, double, double)
    
    cout << Max('a', 100) << endl;        // 普通函數 Max(int, int)
    
    return 0;
}

我們看下這個程序,第一個顯然調用的是普通函數,因為編譯器會優先考慮普通函數;第二個顯然是通過空模板是來實現的,因此它調用的是函數模板;第三個是 double 類型的,因此它會調用函數模板;第四個有三個參數,因此我們肯定它調用的是函數模板;最後一個是兩個不同類型的參數,因為 char 會隱式轉換為 int 類型,所以它會調用普通函數。我們下來看看編譯結果

技術分享圖片

我們看到我們分析的完全是正確的。通過對函數模板的學習,總結如下:1、函數模板是泛型編程在 C++ 中的應用方式之一;2、函數模板能夠根據實參對參數類型進行推導,它支持顯示的指定參數類型;3、函數模板是 C++ 中重要的代碼復用方式;4、函數模板通過具體類型產生不同的參數,並且它可以定義任意多個不同的類型參數;5、函數模板中的返回值類型必須顯示指定;6、函數模板可以像普通函數一樣被重載。


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

函數模板(四十七)