模板:就是与类型无关的逻辑代码
模板函数:就是要实现和类型无关的函数
那么为什么会有模板这个东西呢?
我们在实现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;
}
要注意的是浮点数和类对象不能做非类型的模板参数
类模板的特化——全特化和偏特化
全特化就是限定死模板实现的具体类型,偏特化就是如果这个模板有多个类型,那么只限定其中的一部分。
-
模板函数只能是全特化,不能是偏特化
-
模板类全特化,偏特化都可以
全特化
//普通模板类
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;
};
我们会发现偏特化不仅仅是指特殊部分参数,还是针对模板参数进行进一步限制得出来的另一个限制版本
并且如果有特化版本的类,那么编译器就会直接走特化版本就不需要在进行推演
模板总结:
优点
-
模板复用了代码,节省了资源,C++的模板库STL就由此产生
-
增强了代码的灵活性
缺点:
-
会增加编译器的负担要进行推演
-
代码变复杂不易定位错误