1. 程式人生 > >C++解析(26):函數模板與類模板

C++解析(26):函數模板與類模板

null 並且 http 外部 asi 分類 工程 顯示調用 完全

0.目錄

1.函數模板

  • 1.1 函數模板與泛型編程
  • 1.2 多參數函數模板
  • 1.3 函數重載遇上函數模板

2.類模板

  • 2.1 類模板
  • 2.2 多參數類模板與特化
  • 2.3 特化的深度分析

3.小結

1.函數模板

1.1 函數模板與泛型編程

C++中有幾種交換變量的方法?
交換變量的方法——定義宏代碼塊 vs 定義函數

  • 定義宏代碼塊
    1. 優點:代碼復用,適合所有的類型
    2. 缺點:編譯器不知道宏的存在,缺少類型檢查
  • 定義函數
    1. 優點:真正的函數調用,編譯器對類型進行檢查
    2. 缺點:根據類型重復定義函數,無法代碼復用

C++中有沒有解決方案集合兩種方法的優點

泛型編程的概念——不考慮具體數據類型的編程方式


技術分享圖片

函數模板:

  • 一種特殊的函數可用不同類型進行調用
  • 看起來和普通函數很相似,區別是類型可被參數化

技術分享圖片

函數模板的語法規則:

  • template關鍵字用於聲明開始進行泛型編程
  • typename關鍵字用於聲明泛型類型

技術分享圖片

函數模板的使用:

  • 自動類型推導調用
  • 具體類型顯示調用

技術分享圖片

示例——使用函數模板:

#include <iostream>

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, 3, 2, 4, 1};
    
    Println(a, 5);
    Sort(a, 5);
    Println(a, 5);
    
    string s[5] = {"Java", "C++", "Pascal", "Ruby", "Basic"};
    
    Println(s, 5);
    Sort(s, 5);
    Println(s, 5);
    
    return 0;
}

運行結果為:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out 
5, 3, 2, 4, 1, 
1, 2, 3, 4, 5, 
Java, C++, Pascal, Ruby, Basic, 
Basic, C++, Java, Pascal, Ruby,

函數模板深入理解:

  • 編譯器從函數模板通過具體類型產生不同的函數
  • 編譯器會對函數模板進行兩次編譯
    1. 對模板代碼本身進行編譯
    2. 對參數替換後的代碼進行編譯

註意事項:

  • 函數模板本身不允許隱式類型轉換
    1. 自動推導類型時,必須嚴格匹配
    2. 顯示類型指定時,能夠進行隱式類型轉換

示例——編譯器從函數模板通過具體類型產生不同的函數:

#include <iostream>

using namespace std;

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&);

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

運行結果為:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out 
pi = 0x40091e
pd = 0x40094a

可以看到,編譯器通過函數模板產生了兩個地址不同的實實在在的函數!

1.2 多參數函數模板

多參數函數模板——函數模板可以定義任意多個不同的類型參數
技術分享圖片

對於多參數函數模板:

  • 無法自動推導返回值類型
  • 可以從左向右部分指定類型參數

技術分享圖片

示例——多參數函數模板:

#include <iostream>

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;
}

運行結果為:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out 
r1 = 1
r2 = 1.3
r3 = 1.3

1.3 函數重載遇上函數模板

函數重載遇上函數模板會發生什麽?

函數模板可以像普通函數一樣被重載:

  • C++編譯器優先考慮普通函數
  • 如果函數模板可以產生一個更好的匹配,那麽選擇模板
  • 可以通過空模板實參列表限定編譯器只匹配模板

技術分享圖片

示例——重載函數模板:

#include <iostream>

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, int)
    cout << endl;
    
    cout << Max(3.0, 4.0) << endl;      // 函數模板 Max<double>(double, double)
    cout << Max(5.0, 6.0, 7.0) << endl; // 函數模板 Max<double>(double, double, double)
    cout << endl;
    
    cout << Max(‘a‘, 100) << endl;      // 普通函數 Max(int, int)
    
    return 0;
}

運行結果為:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out 
int Max(int a, int b)
2
T Max(T a, T b)
2

T Max(T a, T b)
4
T Max(T a, T b, T c)
T Max(T a, T b)
T Max(T a, T b)
7

int Max(int a, int b)
100

2.類模板

2.1 類模板

在C++中是否能夠將泛型的思想應用於類

類模板:

  • 一些類主要用於存儲和組織數據元素
  • 類中數據組織的方式和數據元素的具體類型無關
  • 如:數組類,鏈表類,Stack類,Queue類,等

C++中將模板的思想應用於類,使得類的實現不關註數據元素的具體類型,而只關註類所需要實現的功能。

C++中的類模板:

  • 以相同的方式處理不同的類型
  • 在類聲明前使用template進行標識
  • <typename T>用於說明類中使用的泛指類型 T

技術分享圖片

類模板的應用:

  • 只能顯示指定具體類型,無法自動推導
  • 使用具體類型<Type>定義對象

技術分享圖片

  • 聲明的泛指類型 T 可以出現在類模板的任意地方
  • 編譯器對類模板的處理方式和函數模板相同
    1. 從類模板通過具體類型產生不同的類
    2. 在聲明的地方對類模板代碼本身進行編譯
    3. 在使用的地方對參數替換後的代碼進行編譯

示例——類模板:

#include <iostream>

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; }
};

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

int main()
{
    Operator<int> op1;
    
    cout << op1.add(1, 2) << endl;
    
    Operator<string> op2;
    
    cout << op2.add("Hello", "World") << endl;
    cout << op2.minus("Hello", "World") << endl;
    
    return 0;
}

運行結果為:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out 
3
HelloWorld
Minus

類模板的工程應用:

  • 類模板必須在頭文件中定義
  • 類模板不能分開實現在不同的文件中
  • 類模板外部定義的成員函數需要加上模板<>聲明

示例——模板類的工程應用:

// 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 "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;
}

運行結果為:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out 
3
20
-1
2

2.2 多參數類模板與特化

類模板可以定義任意多個不同的類型參數
技術分享圖片

類模板可以被特化:

  • 指定類模板的特定實現
  • 部分類型參數必須顯示指定
  • 根據類型參數分開實現類模板

技術分享圖片

類模板的特化類型:

  • 部分特化——用特定規則約束類型參數
  • 完全特化——完全顯示指定類型參數

技術分享圖片

示例——類模板的特化:

#include <iostream>

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* a, T2* b)" << endl;
        cout << *a + *b << endl;
    }
};

template
< typename T >
class Test < T, T > // 當 Test 類模板的兩個類型參數完全相同時,使用這個實現
{
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* > // 當 T1 == void* 並且 T2 == 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;
}

運行結果為:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out 
void add(T1 a, T2 b)
3.5

void add(T a, T b)
10
class Test < T, T >

void add(void* a, void* b)
Error to add void* param...

void add(T1* a, T2* b)
1.1

類模板特化註意事項:

  • 特化只是模板的分開實現
    1. 本質上是同一個類模板
  • 特化類模板的使用方式是統一的
    1. 必須顯示指定每一個類型參數

2.3 特化的深度分析

類模板特化與重定義有區別嗎?函數模板可以被特化嗎?

重定義和特化的不同:

  • 重定義
    1. 一個類模板和一個新類(或者兩個類模板)
    2. 使用的時候需要考慮如何選擇的問題
  • 特化
    1. 以統一的方式使用內模板和特化類
    2. 編譯器自動優先選擇特化類

函數模板只支持類型參數完全特化:
技術分享圖片

示例——函數模板完全特化與函數重載:

#include <iostream>

using namespace std;

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.00000000000001;
    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;
    cout << Equal<>( 0.001, 0.001 ) << endl;
    
    return 0;
}

運行結果為:

[root@bogon Desktop]# g++ test.cpp
[root@bogon Desktop]# ./a.out 
bool Equal(T a, T b)
1
bool Equal(double a, double b)
1
bool Equal<double>(double a, double b)
1

工程中的建議:
當需要重載函數模板時,優先考慮使用模板特化當模板特化無法滿足需求,再使用函數重載!

3.小結

  • 函數模板是泛型編程在C++中的應用方式之一
  • 函數模板能夠根據實參對參數類型進行推導
  • 函數模板支持顯示的指定參數類型
  • 函數模板是C++中重要的代碼復用方式
  • 函數模板通過具體類型產生不同的函數
  • 函數模板可以定義任意多個不同的類型參數
  • 函數模板中的返回值類型必須顯示指定
  • 函數模板可以像普通函數一樣被重載
  • 泛型編程的思想可以應用於類
  • 類模板以相同的方式處理不同類型的數據
  • 類模板非常適用於編寫數據結構相關的代碼
  • 類模板在使用時只能顯示指定類型
  • 類模板可以定義任意多個不同的類型參數
  • 類模板可以被部分特化完全特化
  • 特化的本質是模板的分開實現
  • 函數模板只支持完全特化
  • 工程中使用模板特化代替類(函數)重定義

C++解析(26):函數模板與類模板