模板
泛型编程:编写与类型无关的逻辑代码。
模板是泛型编程的基础,模板为复用而生,它可以分为两类:模板函数和模板类。
模板函数: 对于一些功能相同而数据类型不同的函数,不必一一定义各个函数,可以定义一个函数模板,系统会在调用函数时根据实参的类型取代函数模板中的数据类型,从而得到具体的函数。
举个简单的例子
#include<iostream>
#include<string>
using namespace std;
template<typename T>
bool IsEqual(const T& left,const T& right)
{
return left == right;
}
void test()
{
string s1("s1"),s2("s2");
cout<<IsEqual(s1,s2)<<endl;
cout<<IsEqual(1,1)<<endl;
}
int main()
{
test();
return 0;
}
模板函数的实例化
还有一种形式,调用一个模板函数时,在函数名前加上<类型>,这时候系统就不需要从形参那里推断类型,直接使用括号里的类型就可以。
template<typename T>
bool IsEqual(const T& left,const T& right)
{
return left == right;
}
int main()
{
//cout<<IsEqual(1,1.2)<<endl;//模板参数不匹配
cout<<IsEqual<int>(1,1.2)<<endl;//显示实例化
cout<<IsEqual<double>(1,1.2)<<endl;
return 0;
}
模板类
就像类是对象的抽象,对象是类的实例化 在这里,模板类是类的抽象,类是模板类的实例化。
让我们来看一个模板类的实现—一个简单的链表
#include<iostream>
#include"template.h"
template<class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
ListNode(const T&x)
:_data(x)
,_next(NULL)
,_prev(NULL)
{}
};
template<class T>
class List
{
typedef ListNode<T>Node;
public:
List()
:_head(NULL)
,_tail(NULL)
{}
List(const List<T>&l)
:_head(NULL)
,_tail(NULL)
{
Node* cur=l._head;
while(cur)
{
PushBack(cur->data);
cur=cur->_next;
}
}
List<T>&operator=(List<T>l)
{
Swap(l);
return *this;
}
void Swap(List<T>l)
{
Swap(_head,l._head);
Swap(_tail,l._tail);
}
~List()
{
Clear();
}
void Clear()
{
Node*cur=_haed;
while(cur)
{
Node* next=cur->next;
delete cur;
cur=next;
}
_head=_tail=NULL;
}
void PushBack(const T&x)
{
if(_head==NULL)
{
_head=_tail=new Node(x);
}
else
{
Node*tmp=new Node(x);
_tail->_next=tmp;
tmp->_prev=_tail;
_tail=tmp;
}
}
void PopBack()
{
if(_head==NULL)
{
return;
}
else if(_head=_tail)
{
delete _tail;
_head=_tail=NULL;
}
else
{
Node* prev=_tail->_prev;
delete _tail;
_tail=prev;
_tail->next=NULL;
}
}
void PushFront(const T&x)
{
assert(_head!=NULL);
if(_head==NULL)
{
PushBack(x);
}
else
{
Node*cur=new Node(x);
cur->next=_head;
_head->_prev=cur;
_head=cur;
}
}
void PopFront()
{
if(_head==NULL)
{
return;
}
else if(_head==_tail)
{
delete _head;
_head=_tail=NULL;
}
else
{
Node*next=_head->_next;
delete _head;
_head=next;
}
}
T& Front()
{
assert(_head);
return _head->_data;
}
T& Back()
{
assert(_tail);
return _dail->_data;
}
void Print()
{
Node cur=_head;
while(cur)
{
cout<<cur->_data<<" ";
cur=cur->_next;
}
cout<<endl;
}
private:
Node* _head;
Node* _tail;
};
注 类模板对象的创建:在类的后面跟上一个尖括号<>,并且在括号里注明相应的类型 。
模板的分离编译
先来看看什么是模板的分离编译:
//template.h
template<class T>
class A
{
public:
A();
private:
int a;
};
//template.cpp
#include"template.h"
template<class T>
A<T>::A()
{
cout<<"哈哈"<<endl
}
//mian.cpp
#include<iostream>
#include"template.h"
using namespace std;
int main()
{
A<int>a;
return 0;
}
结果
想要解决这个问题,首先我们要清楚
一个可执行程序的生成过程。
1.预处理阶段:(1)宏替换 (2)条件编译 (3).h文件的展开 (4)删除注释
2. 编译阶段:此时只能看到.c/.cpp文件,进而产生汇编代码
3. 汇编阶段:汇编代码转换为cpu机器代码。
4. 链接阶段:链接所有的机器码,由链接地址合并所有代码形成可执行程序。
对于上个例子:
解决办法: (1)在模板头文件xxx.h里面显示实例化.(这种办法一般不推荐,一方面老编译器可能不支持,另一方面实例化依赖调用者)
(2)将声明和定义放到一个文件 “xxx.cpp”里面,推荐使用这种方法。
//template.cpp
#include<iostream>
using namespace std;
#include"template.h"
template<class T>
A<T>::A()
{
cout<<"哈哈"<<endl;
}
int main()
{
A<int> a1;
}
模板总结:
优点:模板复用了代码,节省资源,更快的迭代开发,c++的标准模板库(STL)由此产生。 增强了代码的灵活性。
缺点:模板让代码变得凌乱复杂,不易维护,编译代码时间变长。 出现模板编译错误时,错误信息非常凌乱,不易定位错误。(模板出错不一定是当前行的问题)。