模板:泛型編程

模板:就是與類型無關的邏輯代碼

模板函數:就是要實現和類型無關的函數

那麼爲什麼會有模板這個東西呢?
我們在實現swap函數和isequal函數,他們都是一些與類型相關的函數,並且如果我們要根據類型實現多個swap函數的化那麼代碼的重複率就會特別高,此時我們就可以用模板來實現此函數

我們可以先寫一個交換函數的栗子
#include <iostream>
using namespace std;

template <class T>

void swap(T *a,T *b)
{
  T tmp = *a;
  *a = *b;
  *b = tmp;
}

int main()
{
  int a = 1;
  int b = 2;
  swap(a,b);
  cout<<a <<b <<endl;

  char c = 'c';
  char d = 'd';
  swap(c,d);
  cout<<c <<d <<endl;
  return 0;
}


但是此時如果模板參數不匹配的話那就不能調用此函數

模板函數的重載
模板函數是可以重載的,我們可以看一下比較相等的這個栗子
template <class T1,class T2>
bool IsEqual(const T1& left,const T2& right)
{
  return left == right;
}

template<class T>
bool IsEqual(const T& left,const T& right)
{
  return left == right;
}

int main()
{
  cout<<IsEqual<int>(1,1)<<endl;

  cout<<IsEqual<int,float>(1,1.2)<<endl;
  return 0;
}


針對模板函數我們可以畫個圖來理解一下,模板都是編譯器進行推演,生成相對應類型的代碼,然後在進行編譯

模板類:實現和類型無關的類——容器
先舉個栗子:順序表和鏈表(Vector,List)
可以先看代碼
我們寫模板類和我們普通類的代碼的時候最不一樣的是類型的問題,記住以下:

順序表
//vector是類名
//vector<T>是類型
#include <iostream>
#include <stdio.h>
#include <malloc.h>
#include <assert.h>
#include <string.h>
using namespace std;

template<class T>

//vector是類名
//vector<T>是類型
class Vector
{
public:
  Vector();

  ~Vector();

  Vector(const Vector<T>& v);
  Vector<T>& operator=(const Vector<T>& v);

  size_t Size();
  size_t Capacity();

  void PushBack(const T& x);

  void PopBack();
  void Expand(size_t n);
  void Insert(size_t pos,const T& x);
  //刪除指定位置的值
  void Erase(size_t pos);
  void Print();
  const T& Back() const;
  bool Empty();
protected:
  T *_start;
  T *_finish;
  T *_endofstorage;
};

鏈表
#include <iostream>
#include <stdio.h>
#include <assert.h>

using namespace std;
template <class T>
struct ListNode
{

  ListNode<T> *_prev;
  ListNode<T> *_next;
  T _data;

  ListNode(const T& x)
    :_prev(NULL)
    ,_next(NULL)
    ,_data(x)
  {}

};

template <class T>
class List
{
  typedef ListNode<T> Node;
public:
  List();

  ~List();

  //從頭上刪除一個節點
  void Pop();
  //取鏈表頭結點
  const T& Front();

  void PushBack(const T& x);

  void PopBack();
  //在pos之前插入
  void Insert(Node *pos,const T& x);

  void Erase(Node *pos);

  size_t Size();

  bool Empty();
protected:
  Node *_head;
};
模板類的推演過程(我們用順序表來舉例子,其他的模板類都是一樣的)



模板類——適配器
我們來看兩個代碼棧和隊列,他們都是要用我們以上說過的順序表和鏈表來當他們的容器
先看代碼

#include<stdio.h>
#include<iostream>
#include<vector>
#include"TempVector.h"
using namespace std;
//#pragma once

//模板類的模板

//Container:容器
//Stack:適配器
template <class T,class Container>
class Stack{
public:
  void Push(const T& x){
    _con.PushBack(x);
  }
  void Pop(){
    _con.PopBack();
  }
  //T& Top(){
  //  return _con.Back();
  //}
  const T& Top() const{
    return _con.Back();
  }
  size_t Size(){
    return _con.Size();
  }
  bool Empty(){
    return _con.Empty();
  }
protected:
  Container _con;
};

然後我們用Stack來畫圖理解一下他的實現過程




模板類的模板參數

當我們用上面的適配器都爲時候,我們在傳參數的時候,如果我們傳給容器的參數如果傳的和我們適配器的類型不相同的話,就會出現一些問題,舉個栗子
Queue<int , List<char>>
c++爲了防止這種情況的發生,給我們提供了另外一種操作就是模板類的模板參數

我們來看一下代碼啦啦啦
隊列
#pragma once

#include<stdio.h>
#include<iostream>
#include"../TempletList/TempletList.h"
using namespace std;

//模板類的模板參數
template <class T, template <class> class Container>
class Queue
{
public:
  //往隊尾插入節點
  void Push(const T& x)
  {
    _con.PushBack(x);
  }

  //刪除隊首節點
  void Pop()
  {
    _con.Pop();
  }
  //取隊首節點
  const T& Front()
  {
    return _con.Front();
  }

  bool Empty()
  {
    return _con.Empty();
  }
  size_t Size() const
  {
    return _con.Size();
  }

protected:
    Container _con;
};


調用此模板類
Queue<int ,List > q;//如此調用即可

那我們在畫個圖來理解一下這個的原理吧

這裏呢只是寫了類裏函數的聲明,其他的完整代碼都託管到碼雲上了https://gitee.com/zmmya/TemplateVector
有興趣的可以去看一下

非類型的模板參數

當我們寫一個靜態順序表的時候,它的大小都是固定的,那麼有些要存得數據較小,而有些就要存的數據較多,那要時用這個較大的順序表來存儲這個較小的數據,就會造成空間浪費,那麼這個時候就可以用到我們非類型的模板參數

#include<iostream>
using namespace std;


template<class T, size_t MAXSIZE = 10 >
class SeqList
{
  public:
    SeqList();
  protected:
    T _data[MAXSIZE];
    size_t size;
};

template<class T, size_t MAXSIZE>
SeqList<T,MAXSIZE>::SeqList()
  :size(0)
{}

void test()
{
  SeqList<int> s1;
  SeqList<int , 20> s2;
}


int main()
{
  test();
  return 0;
}


要注意的是浮點數和類對象不能做非類型的模板參數
類模板的特化——全特化和偏特化
全特化就是限定死模板實現的具體類型,偏特化就是如果這個模板有多個類型,那麼只限定其中的一部分。
  1. 模板函數只能是全特化,不能是偏特化
  2. 模板類全特化,偏特化都可以
全特化

//普通模板類
template <class T>
class SeqList
{
  public:
    SeqList();
    ~SeqList();
  protected:
    int _size;
    int _capacity;
    T *_data;
};



//特化版本
template <>
class SeqList<int>
{
  public:
    SeqList();
    ~SeqList();
  protected:
    int _size;
    int _capacity;
    int *_data;
};

template <>
class SeqList<char>
{
  public:
    SeqList();
    ~SeqList();
  protected:
    int _size;
    int _capacity;
    char *_data;
};


偏特化
//普通模板類
template <class T1,class T2>
class Date
{
  public:
    Date();
  protected:
    T1 _d1;
    T2 _d2;
};

//偏特化第二個類型
template <class T1>
class Date<T1,int>
{
  public:
    Date();
  protected:
    T1 _d1;
    int _d2;
};


我們會發現偏特化不僅僅是指特殊部分參數,還是針對模板參數進行進一步限制得出來的另一個限制版本
並且如果有特化版本的類,那麼編譯器就會直接走特化版本就不需要在進行推演

模板總結:
優點
  1. 模板複用了代碼,節省了資源,C++的模板庫STL就由此產生
  2. 增強了代碼的靈活性
缺點:
  1. 會增加編譯器的負擔要進行推演
  2. 代碼變複雜不易定位錯誤



發佈了55 篇原創文章 · 獲贊 17 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章