1. 程式人生 > >模板、函式模板、類模板

模板、函式模板、類模板

一、模板  

  泛型(Generic Programming)即是指具有在多種資料型別上皆可操作的含意。泛型程式設計的代表作品 STL 是一種高效、泛型、可互動操作的軟體元件
  泛型程式設計最初誕生於 C++中,目的是為了實現 C++的 STL(標準模板庫)。其語言支援機制就是模板(Templates)。模板的精神其實很簡單:引數化型別。換句話說, 把一個原本特定於某個型別的演算法或類當中的型別資訊抽掉,抽出來做成模板引數 T。

  所謂函式模板,實際上是建立一個通用函式,其函式型別和形參型別不具體指定,用一個虛擬的型別來代表。這個通用函式就稱為函式模板。

 

 

二、函式模板

1.語法格式

  template 是語義是模板的意思,尖括號中先寫關鍵字 typename 或是class,後面跟一個型別 T,此類即是虛擬的型別。至於為什麼用 T,用的人多了,也就是 T 了。

2.函式模板的例項

  呼叫過程是這樣的,先將函式模板實在化為函式,然後再發生函式呼叫。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>

using namespace std;

template<class T>
void MySwap(T& a, T& b)
{
    T temp 
= a; a = b; b = temp; } int main(void) { int a = 10; int b = 20; //1.自動型別推導 cout << "a:" << a << ",b:" << b << endl;//a:10,b:20 MySwap(a, b);//編譯器根據你傳的值,進行型別自動推導 cout << "a:" << a << ",b:" << b << endl;//a:20,b:10 //2.顯式的指定型別
MySwap<int>(a, b); cout << "a:" << a << ",b:" << b << endl;//a:10, b:20 double da = 10.01; double db = 20.02; cout << "da:" << da << ",db:" << db << endl;//da:10.01,db:20.02 MySwap(da, db);//編譯器根據你傳的值,進行型別自動推導 cout << "da:" << da << ",db:" << db << endl;//da:20.02,db:10.01 MySwap<double>(da, db); cout << "da:" << da << ",db:" << db << endl;//da:10.01,db:20.02 return 0; }

  函式模板,只適用於函式的引數個數相同而型別不同,且函式體相同的情況。如果個數不同,則不能用函式模板。

 3.函式模板與普通函式的區別

  • 函式模板不允許自動型別轉化
  • 普通函式能夠自動進行型別轉化

4.函式模板和普通函式在一起呼叫規則

  • 函式模板可以像普通函式那樣被過載;
  • C++編譯器優先考慮普通函式;
  • 如果函式模板可以產生一個更好的匹配,那麼選擇匹配;
  • 可以通過空模板實參列表的語法限定編譯器只能通過模板匹配。

5.編譯器對模板機制剖析

編譯器編譯原理

總結: 

(1)編譯器並不是把函式模板處理成能夠處理任意類的函式;

(2)編譯器從函式模板通過具體型別產生不同的函式;

(3)編譯器會對函式模板進行兩次編譯,在宣告的地方對模板程式碼本身進行編譯,在呼叫的地方對引數替換後的程式碼進行編譯。

函式模板案例——char和int型別陣列排序

 

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

//對char型別和int型別陣列進行排序

//列印函式
template<class T>
void PrintArray(T* arr, int len)
{
    for (int i = 0;i < len;i++)
    {
        cout << arr[i] << " ";
    }
    cout << endl;
}

//排序
template<class T>
void MySort(T* arr, int len)
{
    for (int i = 0;i < len;i++)
    {
        for (int j = i + 1;j < len;j++)
        {
            //從大到小排序
            if (arr[i] < arr[j])
            {
                T temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
    }
}

int main(void)
{
    int arr[] = { 2,6,1,8,9,2 };
    int len = sizeof(arr) / sizeof(int);

    cout << "排序前:";
    PrintArray(arr,len);//排序前:2 6 1 8 9 2
    MySort(arr, len);
    cout << "排序後:";
    PrintArray(arr, len);//排序後:9 8 6 2 2 1

    char chArr[] = { 'a','c','q','d','t', };
    len = sizeof(chArr) / sizeof(char);

    cout << "排序前:";
    PrintArray(chArr, len);//排序前:a c q d t
    MySort(chArr, len);
    cout << "排序後:";
    PrintArray(chArr, len);//排序後:t q d c a

    return 0;
}

 

 

 

 三、類模板

1.類模板定義

  類模板與函式模板的定義和使用類似,有時,有兩個或多個類,其功能是相同的,僅僅是資料型別不同,所以將類中的型別進行泛化。

類模板示例:

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>

using namespace std;

template<class T>
class Person
{
public:
    Person(T id, T age)
    {
        this->m_id = id;
        this->m_age = age;
    }

    void Show()
    {
        cout << "id:" << this->m_id << ",age:" << this->m_age << endl;
    }

public:
    T m_id;
    T m_age;
};

void test()
{
    //函式模板在呼叫的時候,可以自動型別推導
    //類模板必須顯式指定型別
    Person<int> p(10, 20);
    p.Show();
}

int main(void)
{
    test();//id:10,age:20
    return 0;
}

2.類模板派生普通類、類模板派生類模板

#include <iostream>
using namespace std;

template<class T>
class Person
{
public:
    Person(T age)
    {
        this->age = age;
    }
private:
    T age;
};

//模板類派生普通類
//為什麼繼承的型別需顯式,而不是T?
//原因:類去定義物件,這個物件需要編譯分配記憶體,所以要在
//public Person<int>這裡顯式的指定型別,可以知道給父類分配多少記憶體
class SubPerson1 :public Person<int>
{
public:
    SubPerson1(int age, int id) :Person<int>(age)
    {
        this->id = id;
    }
private:
    int id;
};

//模板類派生模板類
template<class T>
class SubPerson2 :public Person<T>
{
public:
    SubPerson2(T age, T id) :Person<T>(age)
    {
        this->id = id;
    }
private:
    int id;
};

 3、類模板實現

(1)類模板類內實現

(2)類模板類外實現(在一個.cpp中)

  模板類不要輕易使用友元函式

(3)類模板類外實現(在.h和.cpp中)

  由於二次編譯,模板類在.h在第一次編譯之後,並沒有最終確定類的具體實現,只是編譯器的詞法校驗和分析。在第二次確定類的具體實現後,是在.hpp檔案生成的最後的具體類,所以main函式需要引入.hpp檔案。

  引入hpp檔案一說也是曲線救國之計,所以實現模板方法建議在同一個檔案.h中完成。

(4)類模板中的static

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

template<class T>
class Person
{
public:
    static T a;
};
//類外初始化
template<class T> T Person<T>::a = 0;

int main(void)
{
    Person<int> p1, p2, p3;
    Person<char> pp1, pp2, pp3;

    p1.a = 10;
    pp1.a = 'c';

    cout << p1.a << " " << p2.a << " " << p3.a << endl;//10 10 10
    cout << pp1.a << " " << pp2.a << " " << pp3.a << endl;//c c c
//通過以上結果,說明p1,p2,p3是屬於Person<int>家族的,他們共享Person<int>::a; //pp1,pp2,pp3是屬於Person<char>家族的,他們共享Person<char>::a; return 0; }

練習:

設計一個數組模板類(MyArray),完成對int、char型別元素的管理。

需要實現 建構函式 拷貝建構函式  [] 過載=操作符。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

template<class T>
class MyArray
{
public:
    MyArray(int capacity)
    {
        this->mCapacity = capacity;
        this->mSize = 0;
        //申請記憶體
        this->pAddr = new T[this->mCapacity];
    }
    MyArray(const MyArray<T>& arr)
    {
        this->mCapacity = arr.mCapacity;
        this->mSize = arr.mSize;

        //申請記憶體空間
        this->pAddr = new T[this->mCapacity];
        //資料拷貝
        for (int i = 0; i < this->mSize;i++)
        {
            this->pAddr[i] = arr.pAddr[i];
        }
    }

    T& operator[](int index)
    {
        return this->pAddr[index];
    }

    MyArray<T> operator=(const MyArray<T>& arr)
    {
        if (this->pAddr != NULL)
        {
            delete[] this->pAddr;
        }

        this->mCapacity = arr.mCapacity;
        this->mSize = arr.mSize;
        //申請記憶體空間
        this->pAddr = new T[this->mCapacity];
        //資料拷貝
        for (int i = 0; i < this->mSize;i++)
        {
            this->pAddr[i] = arr.pAddr[i];
        }

        return *this;
    }

    void PushBack(T& data)
    {
        //判斷容器中是否有位置
        if (this->mSize >= this->mCapacity)
        {
            return;
        }

        //呼叫拷貝構造 =操作符
        //1.物件元素必須能夠被拷貝
        //2.容器都是值寓意,而非引用寓意 向容器中放入元素,都是放入的元素的拷貝
        //3.如果元素的成員有指標,注意深拷貝和淺拷貝問題
        this->pAddr[this->mSize] = data;
        this->mSize ++ ;
    }

    //T&& 對右值取引用
    void PushBack(T&& data)
    {
        //判斷容器中是否有位置
        if (this->mSize >= this->mCapacity)
        {
            return;
        }

        this->pAddr[this->mSize] = data;
        this->mSize++;
    }

    ~MyArray()
    {
        if (this->pAddr != NULL)
        {
            delete[] this->pAddr;
        }
    }

public:
    //一共可以容下多少元素
    int mCapacity;
    //當前陣列有多少元素
    int mSize;
    //儲存資料的首地址
    T* pAddr;
};

class Person 
{
    

};
void test2()
{
    Person p1, p2;

    MyArray<Person> arr(10);
    arr.PushBack(p1);
    arr.PushBack(p2);
}

void test1()
{
    MyArray<int> marray(20);
    int a = 10, b = 20, c = 30, d = 40;
    marray.PushBack(a);
    marray.PushBack(b);
    marray.PushBack(c);
    marray.PushBack(d);

    //不能對右值取引用
    //左值 可以在多行使用
    //臨時變數 只能當前行使用
    marray.PushBack(100);
    marray.PushBack(200);
    marray.PushBack(300);

    for (int i = 0;i < marray.mSize;i++)
    {
        cout << marray[i] << " ";
    }
    cout << endl;
}
int main(void)
{
    test1();
    return 0;
}