模板:泛型编程

模板:就是与类型无关的逻辑代码

模板函数:就是要实现和类型无关的函数

那么为什么会有模板这个东西呢?
我们在实现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. 代码变复杂不易定位错误



猜你喜欢

转载自blog.csdn.net/qq_36767247/article/details/80342834