1. 程式人生 > >C++模板的實現(模板函式和模板類,附帶模板實現順序表和連結串列程式碼)

C++模板的實現(模板函式和模板類,附帶模板實現順序表和連結串列程式碼)

模板

 當我們實現一個交換函式時,我們可以寫成如下。

void Swap(int& x, int& y)
{
    int tmp = x;
    x = y;
    y = tmp;
}

 這裡只能交換兩個整數,當我們一會需要實現兩個字元交換時,我們有需要重新寫個函式,然而兩份程式碼有很多相同的部分,這樣是不是很麻煩。假如我們只需要寫一份程式碼便可以實現不同型別的交換,是不是很棒。是的,這個編譯器已經幫我們設計好了,這就是所謂的泛型程式設計。
 模板是泛型程式設計的基礎,所謂泛型程式設計就是編寫與型別無關的邏輯程式碼,是一種複用的方式。模板分為模板函式和模板類。

一、模板函式

模板函式的格式:

template< class 形參名1, class 形參名2, class 形參名n>
返回型別 函式名(引數列表)
 {…}
 模板形參的定義既可以使用class,也可以使用typename,含義是相同的。
 剛剛的Swap函式就可以用模板函式搞定了。

模板引數隱式例項化
template<class T>
void Swap(T& x, T& y)
{
    T tmp = x;
    x = y;
    y = tmp;
}

看看是不是可以進行多種型別交換,測試結果:
這裡寫圖片描述

這就是模板函式的實現,當然我們很好奇為什麼一個函式就可以搞定。其實在底層實現了函式過載,我們轉到彙編程式碼便可得知。

int main()
{
00394D30  push        ebp  
00394D31  mov         ebp,esp  
00394D33  sub         esp,114h  
00394D39  push        ebx  
00394D3A  push        esi  
00394D3B  push        edi  
00394D3C  lea         edi,[ebp-114h]  
00394D42  mov         ecx,45
h 00394D47 mov eax,0CCCCCCCCh 00394D4C rep stos dword ptr es:[edi] 00394D4E mov eax,dword ptr [__security_cookie (039A000h)] 00394D53 xor eax,ebp 00394D55 mov dword ptr [ebp-4],eax int a1 = 1, a2 = 2; 00394D58 mov dword ptr [a1],1 00394D5F mov dword ptr [a2],2 Swap(a1, a2); 00394D66 lea eax,[a2] 00394D69 push eax 00394D6A lea ecx,[a1] 00394D6D push ecx 00394D6E call Swap<int> (039137Ah) 00394D73 add esp,8 char c1 = 5, c2 = 6; 00394D76 mov byte ptr [c1],5 00394D7A mov byte ptr [c2],6 Swap(c1, c2); 00394D7E lea eax,[c2] 00394D81 push eax 00394D82 lea ecx,[c1] 00394D85 push ecx 00394D86 call Swap<char> (0391375h) 00394D8B add esp,8 double d1 = 1.222, d2 = 2.011111111111; 00394D8E movsd xmm0,mmword ptr [__real@3ff38d4fdf3b645a (0397BD0h)] 00394D96 movsd mmword ptr [d1],xmm0 00394D9B movsd xmm0,mmword ptr [__real@400016c16c16c072 (0397BD8h)] 00394DA3 movsd mmword ptr [d2],xmm0 Swap(d1, d2); 00394DA8 lea eax,[d2] 00394DAB push eax 00394DAC lea ecx,[d1] 00394DAF push ecx 00394DB0 call Swap<double> (039137Fh) 00394DB5 add esp,8 return 0; 00394DB8 xor eax,eax }

 可以看到在底層,每一次呼叫Swap函式都會建立一個棧幀,而每次棧幀建立,形參的型別是不同的,建立棧幀也是不同的。當我們使用模板時編譯器會進行一個推演的過程,這個過程在編譯之前進行。推演時,編譯器會根據傳遞引數的型別例項化(編譯器隱式例項化
出相應的函式,在進行編譯。例如:
這裡寫圖片描述
 但是當我們遇到這樣Swap(1,1.2302102);,此時編譯器如何判斷到底例項化成那種型別?
其實我們如果把模板宣告為這樣既可以解決了。模板函式過載(與上面的函式構成過載)

template<typename T1 ,class T2> //使用class 和 typename一樣的效果
void Swap(T1& x,T2& y)
{
    T1 tmp = x;
    x = y;
    y = tmp;
}

有時候我們可能會要到這樣的奇葩問題。

template< class T>
const T Add(T& x,T& y)
{
    return x+y;
}

當我們這樣呼叫時Add(1,5.222222);,編譯器又該如何例項化呢?

模板引數顯示例項化

這就涉及必須顯示指定例項化型別 模板引數顯示例項化
Add<double> (1.5.2222222); 這樣就可以搞定剛剛的問題。

二、模板類

模板類的格式
template<class 形參名1, class 形參名2, ...class 形參名n>
class 類名
{ ... };

當我們剛開始用c++寫順序表和連結串列之前,我們是這樣的。

typedef int Datatype;

typedef struct SeqList
{
    struct SeqList* _data;
    size_t _size;
}SeqList;

typedef struct ListNode
{
    struct ListNode* _prev;
    struct ListNode* _next;
    Datatype _data;
}ListNode;

我們這樣定義順序表和連結串列的,但是會存在很大一個問題,如下。

當我們在一個程式中要使用兩個不同資料型別順序表和連結串列,這樣是無法完成的,除非我們每種型別定義一個型別

```
typedef int Datatype; //存int型別

typedef struct SeqList
{
    struct SeqList* _data;
    size_t _size;
}SeqList;

typedef struct ListNode
{
    struct ListNode* _prev;
    struct ListNode* _next;
    Datatype _data;
}ListNode;
```
typedef char Datatype; //存char型別

typedef struct SeqList
{
    struct SeqList* _data;
    size_t _size;
}SeqList;

typedef struct ListNode
{
    struct ListNode* _prev;
    struct ListNode* _next;
    Datatype _data;
}ListNode;

這樣就會很麻煩,在我們學了模板之後,我們可以這樣。

模板類示例

模板實現順序表

template<class T>
class Vector
{
public:
    Vector():_first(NULL),_finish(NULL),_endofstorge(NULL)//建構函式
    {}
    ~Vector()//解構函式
    {
        delete[]_first;
        _first = _finish = _endofstorge = NULL;
    }
    Vector(const Vector<T>& v)//拷貝構造
        :_first(NULL)
        ,_finish(NULL)
        ,_endofstorge(NULL)
    {
        int len = v._finish - v._first;
        _first = _finish = new T[len];
        T* start = v._first;
        while(start != v._finish)
        {
            *(_finish) = *start;
            ++_finish;
            ++start;
        }
        _endofstorge = _first+len;
    }

    Vector<T>& operator=(Vector<T>& v) //賦值運算子過載
    {
        delete[]_first;
        Vector<T> v1(v);
        swap(_first ,v1._first);
        swap(_finish , v1._finish);
        swap(_endofstorge , v1._endofstorge);

        return *this;
    }

    void PushBack(const T& x) //尾插
    {
        if(_finish == _endofstorge)
            Expand(Capacity()*2+1);
        _first[Size()] = x;
        ++_finish;
    }
    void PopBack()//尾刪
    {
        Erase(Size()-1);
    }
    void Expand(size_t n) //擴容
    {
        int size = Size();
        if(n>Capacity())
        {
            T* tmp = new T[n];
            for (int i = 0;i<size;i++)
            {
                *(tmp+i) = *(_first+i);
            }
            delete[]_first;
            _first = tmp;
            _finish = _first + size;
            _endofstorge = _first + n;
        }
    }
    void Insert(size_t pos,const T& x)//隨機插入
    {
        assert(_first+pos <= _finish);
        if(_finish == _endofstorge)
            Expand(2*Capacity());
        T* end = _finish;
        while(end != _first + pos)
        {
            *(end) = *(end-1);
            --end;
        }
        _first[pos] = x;
        ++_finish;
    }
    void Erase(size_t pos)//刪除任意位置的資料
    {
        assert(_first+pos < _finish);
        int size = Size();
        T* start = _first + pos + 1;
        while(start != _finish)
        {
            *(start - 1) = *(start);
            ++start;
        }
        --_finish;
    }
    size_t Find(const T& x)//查詢
    {
        int size = Size();
        for(int i = 0;i<size;i++)
        {
            if (_first[i] == x)
                return i;
        }
        return -1;
    }
    T& operator[](size_t pos)//獲取任意位置的資料
    {
        assert(pos<Size());
        return _first[pos];
    }
    const T& operator[](size_t pos) const
    {
        assert(_first+pos < _finish);
        return _first[pos];
    }
    size_t Size() //求取順序表的有效容量
    {
        return _finish - _first;
    }
    size_t Capacity()//求取順序表的容量
    {
        return _endofstorge - _first;
    }
    bool Empty() //判斷是否為空順序表
    {
         return Size()==0;
    }

protected:
    T* _first;
    T* _finish;
    T* _endofstorge;
};

測試程式碼及結果:

void VectorTest()
{
    Vector<int> v;
    v.PushBack(1);
    v.PushBack(2);
    v.PushBack(3);
    v.PushBack(4);
    v.PushBack(5);
    v.PushBack(6);
    v.PushBack(7);
    PrintVocter(v);

    Vector<string> v1;
    v1.PushBack("hello");
    v1.PushBack("world !");
    v1.PushBack("i");
    v1.PushBack("love");
    v1.PushBack("you");
    PrintVocter(v1);

}

這裡寫圖片描述
模板實現雙鏈表

#ifndef __LIST_H__
#define __LIST_H__


#include<string>
#include<iostream>
using namespace std;

template <class T>
struct ListNode
{
    struct ListNode* _prev;
    struct ListNode* _next;
    T _data;

};

template <class T>
class List
{
    typedef ListNode<T> Node;
public:
    List()//建構函式
    {
        _head = new Node;
        _head->_next = _head->_prev = _head;
    }
    List(const List<T>& h) //拷貝構造
    {
        Node* head = h._head;
        Node* tmp = head->_next;

        _head = new Node;
        _head->_next = _head->_prev = _head;
        while (tmp != head)
        {
            PushBack(tmp->_data);
            tmp = tmp->_next;
        }
    }
    ~List() //解構函式
    {
        Clear();
        delete _head;
        _head = NULL;
    }
    void PushBack(const T& x) //尾插
    {
        Node *tmp = new Node;
        tmp->_data = x;
        Node* tail = _head->_prev;

        tail->_next = tmp;
        tmp->_prev = tail;
        _head->_prev = tmp;
        tmp->_next = _head;

    }
    void PushFront(const T& x) //頭插
    {
        Node *tmp = new Node;
        tmp->_data = x;
        Node* cur = _head->_next;
        tmp->_prev = _head;
        _head->_next = tmp;
        tmp->_next = cur;
        cur->_prev = tmp;
    }
    void PopBack() //尾刪
    {
        Node* cur = _head->_prev;
        _head->_prev = cur->_prev;
        cur->_prev->_next = _head;

        delete[]cur;
        cur->_next = cur->_prev = NULL;
    }
    void PopFront() //頭刪
    {
        Node* cur = _head->_next;
        _head->_next = cur->_next;
        cur->_next->_prev = _head;
    }
    void Insert(Node* pos,const T& x) //隨機插入
    {
        Node* cur = new Node;
        cur->_data = x;
        cur->_prev = pos->_prev;
        pos->_prev->_next = cur;
        pos->_prev = cur;
        cur->_next = pos;
    }
    void Erase(Node* pos) //刪除隨機位置
    {
        Node* cur = pos->_next;
        pos->_prev->_next = cur->_next;
        cur->_next->_prev = pos->_prev;
    }
    void Clear() //清除連結串列資料
    {
        Node* cur = _head->_next;
        while(cur != _head)
        {
            Node* tmp = cur;
            cur = cur->_next;
            delete tmp;
        }
        _head->_next = _head;
        _head->_prev = _head;
    }
    Node* Find(const T& x) //查詢
    {
        Node* cur = _head->_next;
        while (cur != _head)
        {
            if(cur->_data==x)
                return cur;
            cur = cur->_next;
        }
        return NULL;
    }

    size_t Size() //求取連結串列長度
    {
        size_t count = 0;
        Node* cur = _head->_next;
        while (cur != _head)
        {
            count++;
            cur = cur->_next;
        }
        return count;
    }
    bool Empty()
    {
        if (_head->_next = _head->_prev)
            return 1;
        return 0;
    }
    void PrintList() //列印連結串列
    {
        Node* tmp = _head->_next;
        while (tmp != _head)
        {
            std::cout<<tmp->_data<<" ";
            tmp = tmp->_next;
        }
        std::cout<<std::endl;
    }

protected:
    Node* _head;

};

#endif//__LIST_H__

這裡寫圖片描述
這樣我們的順序表和連結串列就可以實現任意型別的程式了。