Template 模板
一. 模板的概念
说到模板,就要提到泛型编程,那什么是泛型编程呢?
泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。
模板就是泛型编程的基础。模板就是用来实现泛型编程。
模板定义本身不参与编译,而是编译器根据模板的用户使用模板时提供的类型参数生成代码,再进行编译,这一过程被称为模板实例化。用户提供不同的类型参数,就会实例化出不同的代码。
模板分为模板函数与模板类两种。
二. 模板函数
先举个例子来说,c++库中有swap函数,在这里就用swap举例。在之前我们肯定都写过swap函数,用来交换两个值,但是之前都是针对一个数据类型来实现,都是一次性写死,是什么类型就是什么类型,但是这就导致我们要在一个工程中交换两个数据类型时,就得写两个swap交换函数,就会很麻烦,也会使代码量变大,有冗余。
#include <iostream>
using namespace std;
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
void Swap(char* a, char* b)
{
char tmp = *a;
*a = *b;
*b = tmp;
}
int main()
{
int a = 10;
int b = 20;
Swap(a, b);
char ch1 = a;
char ch2 = b;
Swap(ch1,ch2);
printf("a = %d b = %d\n", a, b);
return 0;
}
模板函数就可以有效的解决这个问题。像下面这样的代码就是模板函数的一般形式:
template <class T>
void Swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int a = 10;
int b = 20;
Swap<int>(a,b);
Swap(a,b);
printf("a = %d b = %d\n", a, b);
return 0;
}
这段代码只定义了一个swap函数,利用模板的方法。既可以用来交换int类型也可以用来交换char类型,当然不止这两个,任何类型都是交换的。
模板函数的一般格式
template <class 形参1,class 形参2,class 形参3···,class 形参n>
函数返回值 函数名(函数参数)
{
···
}
模板参数列表的定义可以使用class也可以使用typename。含义是相同的。
模板函数的匹配与显式实例化
模板函数必须是要匹配的,在一段代码中参数的类型必须是相同的。不可以出现一个参数有多个类型的情况。就像上面的swap函数,一个函数中的参数必须相同,不可以交换两个不同的数据类型的参数。这就是模板函数的匹配问题。
编译器可以自己识别当前类型,进行自动推演,也可以我们自己显式的告诉编译器让其推演为什么样的类型。
模板函数的重载
模板函数也与其他函数一样可以被重载。可以通过函数参数的不同来实现重载。
三. 模板类
模板类的功能与模板函数的相似,都是为了可以实现代码的复用性,可以实现一个类是不同的数据类型。我们用顺序表来举例:
普通的顺序表:
typedef char datatype;
//type int datatype
class Seqlist
{
public:
Seqlist();
Seqlist(const Seqlist& s);
~Seqlist();
private:
datatype *_data;
int _size;
int _capacity;
};
我们之前写的顺序表都是这样写的,为了顺序表还可以存不同的数据类型,我们使用typedef类型重定义的方式来实现,在需要改变存储数据类型只需将typedef后的类型更改即可,虽然也比较方便,但是,这种方法也有缺点,比如,在一个工程中,我既需要一个储存int的顺序表还需要一个存储char的顺序表,用typedef就不可以实现。这时,就需要我们的模板类了。
使用模板类顺序表的实现:
template <class T>
class Seqlist
{
public:
Seqlist();
Seqlist(const Seqlist& s);
~Seqlist();
private:
T *_data;
int _size;
int _capacity;
};
template<class T>
Seqlist<T>::Seqlist()
:_size(0)
,_capacity(10)
,_data(new T[_capacity])
{}
在写模板类的时候需要注意,若是在类外定义函数时,需要将模板参数列表加在函数之上。
注意:(用Seqlist举例)
模板类的类名是:Seqlist
模板类的类型是:Seqlist <T>
下面是我用模板类实现的顺序表与链表
Vector.h
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <string>
#include <assert.h>
using namespace std;
template <class T>
class Vector
{
public:
Vector()
{
_start = new T[10];
_finish = _start;
_endofstorage = _start + 10;
}
Vector(const Vector<T>& v)
{
_start = new T[v.Size()];
size_t i = 0;
for(; i < v.Size(); i++)
{
_start[i] = v._start[i];
}
_finish = _start + v.Size();
_endofstorage = _finish;
}
Vector<T>& operator=(Vector<T> v)
{
_start = new T[v.Size()];
swap(_start,v._start);
swap(_finish,v._finish);
swap(_endofstorage,v._endofstorage);
return *this;
}
void PushBack(const T& x)
{
Insert(Size(),x);
}
void PopBack()
{
Erase(Size() - 1);
}
void Expand(size_t n)
{
T* old = _start;
size_t size = Size();
_start = new T[n];
_finish = _start + size;
_endofstorage = _start + n;
delete[] old;
}
void Insert(size_t pos,const T& x)
{
if(Size() >= Capacity())
Expand(2 * Capacity());
size_t i = pos;
for(;i < Size(); i++)
{
_start[i+1] = _start[i];
}
_start[pos] = x;
_finish++;
}
void Erase(size_t pos)
{
assert(pos < Size());
size_t i = pos;
for(;i < Size()-1; i++)
{
_start[i] = _start[i+1];
}
_finish--;
}
void reserve(size_t n)
{
if(n > Capacity())
Expand(n);
else if(n < Capacity())
_finish = _start + (n > Size() ? n : Size());
else
{
return ;
}
}
size_t Size() const
{
return _finish - _start;
}
size_t Capacity() const
{
return _endofstorage - _start;
}
size_t Find(const T& x)
{
size_t i = 0;
for(; i<Size();i++)
{
if(_start[i] == x)
return i;
}
return -1;
}
const T& Top()
{
return _start[Size()-1];
}
bool Empty()
{
if(Size() == 0)
return true;
else
{
return false;
}
}
~Vector()
{
delete[] _start;
_start = NULL;
_finish = _endofstorage = _start;
}
void show()
{
size_t i = 0;
for(;i <Size(); i++)
{
cout<<_start[i]<<" ";
}
cout<<endl;
}
protected:
T* _start;
T* _finish;
T* _endofstorage;
};
Vector.cpp
#include "Vector.h"
void testInsert()
{
Vector<char> v;
v.Insert(0,'a');
v.Insert(1,'b');
v.show();
}
void testErase()
{
Vector<string> v;
v.Insert(0,"fihwuehds");
v.Insert(1,"dijwiufhdc");
v.Insert(0,"kdwojcs");
v.show();
v.PopBack();
v.show();
}
void testFind()
{
Vector<string> v;
v.PushBack("This ");
v.PushBack("is ");
v.PushBack("a ");
v.PushBack("statement!");
cout<<v.Find("a")<<endl;
}
int main()
{
testInsert();
testErase();
testFind();
return 0;
}
链表:
list.h
#pragma once
#include <stdio.h>
#include <iostream>
#include <assert.h>
#include <string.h>
using namespace std;
template<class T>
struct ListNode
{
T data;
ListNode<T>* _prev;
ListNode<T>* _next;
ListNode()
:data(T())
,_next(NULL)
,_prev(NULL)
{}
};
template <class T>
class List
{
public:
List()
{
_head = new ListNode<T>;
_head->_next = _head;
_head->_prev = _head;
}
List(const List<T>& l)
{
ListNode<T>* cur = l._head->_next;
_head = CreateNode(T());
_head->_next = _head;
_head->_prev = _head;
while(cur != l._head)
{
PushBack(cur->data);
cur = cur->_next;
}
}
List& operator=(List<T> l)
{
swap(_head,l._head);
}
void PushBack(const T& x)
{
Insert(_head,x);
}
void PopBack()
{
Erase(_head->_prev);
}
void PushFront(const T& x)
{
Insert(_head->_next,x);
}
void PopFront()
{
Erase(_head->_next);
}
void Insert(ListNode<T>* pos,const T& x)
{
ListNode<T> *newnode = CreateNode(x);
newnode->_next = pos;
newnode->_prev = pos->_prev;
pos->_prev = newnode;
newnode->_prev->_next = newnode;
}
void Erase(ListNode<T>* pos)
{
assert(pos != _head);
ListNode<T>* next = pos->_next;
ListNode<T>* prev = pos->_prev;
delete pos;
next->_prev = prev;
prev->_next = next;
}
bool Empty()
{
if(_head->_next == _head)
return true;
else
{
return false;
}
}
const T& Top()
{
return _head->_next->data;
}
void show()
{
ListNode<T>* cur = _head->_next;
while(cur != _head)
{
cout<<cur->data<<" ";
cur = cur->_next;
}
cout<<endl;
}
~List()
{
ListNode<T> *cur = _head->_next;
while(cur != _head)
{
ListNode<T>* next = cur->_next;
delete cur;
cur = next;
}
}
protected:
ListNode<T>* CreateNode(const T& x)
{
ListNode<T>* newnode = new ListNode<T>;
newnode->data = x;
newnode->_next = NULL;
newnode->_prev = NULL;
}
ListNode<T>* _head;
};
list.cpp
#include "List.h"
void test()
{
List<string> l;
l.PushBack("jofifdjs");
l.PushBack("d98dehwu");
l.PushBack("23435");
l.PushBack("0odjcs98");
l.show();
l.PopBack();
l.show();
l.PushFront("123456");
l.show();
l.PopFront();
l.show();
List<string> l1(l);
l1.show();
l1.PushBack("jiduhv");
l1.show();
l1 = l;
l1.show();
}
int main()
{
test();
return 0;
}
在使用模板类时,我们不能只考虑一种类型,我们得考虑到一些特殊的类型,是否适用与当前的代码。就像是顺序表中,实现拷贝构造函数时,我们就需要考虑到string类型,若是不考虑string这个类型,我们只需要用memmove将被拷贝的值挪动到当前的数据处。但是因为string类型的数据是存储在一个char* 指针中,拷贝到的只是指针,当被拷贝的对象析构之后,就会出现错误。所以我们就需要将一个元素一个元素的挪动,这样就属于是赋值运算,string内部的机制会自动判别是深拷贝还是浅拷贝。