模板能够使开发人员快速建立具有类型安全的类库集合和函数集合,大大方便了大规模软件开发。
函数模板:
其本质上并不是一个实在的函数,编译器不能生成可执行代码。只是一个对函数功能框架的描述,根据传递的实际参数决定其功能。
函数模板的定义:
template <模板参数>
返回类型 函数名(形参列表)
{
...
}
//template是关键字,<模板参数>使用关键字class或typedef
例如求和模板:
#include<iostream>
#include<string>
using namespace std;
//函数模板
template<class type>
type Sum(type x,type y)
{
return x+y;
}
void main()
{
//使用函数模板,传递参数不同,实现的功能不同
int iresult = Sum(10,20);
double dresult = Sum(1.0,2.0);
cout<<iresult<<endl;
cout<<dresult<<endl;
}
函数模板的作用:按照字面意思理解,他就是一个模板,只要是要实现的功能一样,就可以使用这个模板。比如我要求两个数谁大谁小,一个模板就可以被任何数据类型的比较拿来用。
//求最大值
template<class type>
type Max(type x,type y)
{
return (x>y)?x:y;
}
void main()
{
int iresult = Max<int>(10,20);
double dresult = Max<double>(5.0,2.0);
cout<<iresult<<endl;
cout<<dresult<<endl;
}
//
//type相当于自定义的一个数据类型,类似于int,double
template<class type,int len>
type Max(type array[len])
{
type ret = array[0];
for(int i = 0;i<len;i++)
{
ret = (ret>array[i])?ret:array[i];
}
return ret;
}
void main()
{
int array[5] = {5,6,2,3,7};
int iret = Max<int,5>(array);//调用方式 函数名<输入模板参数>(函数本身的参数)
double shuzu[3] = {14,2,30};
double dret = Max<double,3>(shuzu);
cout<<iret<<endl;
cout<<dret<<endl;
}
类模板:
类模板可以说是类生成类,减少了类的定义数量。是用来描述通用数据类型或处理方法的机制,它使类中的一些数据成员和成员函数的参数或返回值可以取任意数据类型。
类模板的定义:
template<类型形式参数表> class 类模板名
{
...
};
//类模板成员函数定义为:
template <类型形式参数表>
返回类型 类模板名<类型名表>::成员函数名(形参列表)
{
...
}
代码实例:
简单类模板
#include<iostream>
#include<string>
using namespace std;
//简单类模板
template<class T1,class T2>//类模板有两个参数
class MyTemplate
{
T1 t1;
T2 t2;
public:
MyTemplate(T1 t_1,T2 t_2)//构造函数
{
t1 = t_1,t2 = t_2;
}
void display()
{
cout<<t1<<' '<<t2<<endl;
}
};
void main()
{
int a = 123;
double b = 3.1415;
MyTemplate<int,double> mt(a,b);//指明传入参数的类型
mt.display();
}
默认参数模板:在定义时设置类型参数表中一个类型参数默认值,有了默认的数据类型参数后,在定义新类时就可以不进行指定。
#include<iostream>
#include<string>
using namespace std;
//简单类模板
template<class T1,class T2=int>//定义默认参数
class MyTemplate
{
T1 t1;
T2 t2;
public:
MyTemplate(T1 t_1,T2 t_2)//构造函数
{
t1 = t_1,t2 = t_2;
}
void display()
{
cout<<t1<<' '<<t2<<endl;
}
};
void main()
{
int a = 123;
double b = 3.1415;
MyTemplate<int,double> mt1(a,b);//指明传入参数的类型
MyTemplate<int>mt2(a,b);//默认参数类型
mt1.display();
mt2.display();
}
123 3.1415
123 3
为具体类型的参数提供默认值:直接上实例
#include<iostream>
#include<string>
using namespace std;
//为具体类型的参数提供默认值
template<class T1,class T2,int num=10>//定义默认值
class MyTemplate
{
T1 t1;
T2 t2;
public:
MyTemplate(T1 t_1,T2 t_2)//构造函数
{
t1 = t_1+num;
t2 = t_2+num;
}
void display()
{
cout<<t1<<' '<<t2<<endl;
}
};
void main()
{
int a = 123;
double b = 3.1415;
MyTemplate<int,double> mt1(a,b);//默认加10
MyTemplate<int,double,100>mt2(a,b);//修改参数,可以加任意值
mt1.display();
mt2.display();
}
133 13.1415
223 103.141
有界数组模板 :C++语言不能检测数组下标是否越界,通过建立数组模板,对数组下标是否越界进行检测,在模板中想要获取下标值,需要重载数组下标运算符"[]",重载数组下标运算符后使用模板类实例化的数组,就可以进行下标越界检测。
因为程序需要使用assert来进行警告处理,且需要对话框显示,需要引入头文件cassert。
#include<iostream>
#include<iomanip>//用来对输入输出操作的格式进行更加方便的控制用来对输入输出操作的格式进行更加方便的控制
#include<cassert>
using namespace std;
//定义一个普通类Date
class Date
{
int Month,Day,Year;
char Format[128];
public:
Date(int m=0,int d=0, int y=0)//构造函数,用于对变量进行初始化
{
Month = m;
Day = d;
Year = y;
}
//ostream是output stream的简称,即输出流.ostream这个类型,往往出现在(操作重载中,作为某个类的友元函数出现)
friend ostream& operator<<(ostream& os, const Date t)
{
cout<<"Month:"<<t.Month<<' ';
cout<<"Day:"<<t.Day<<' ';
cout<<"Year:"<<t.Year<<' ';
return os;
}
void Display()
{
cout<<"Month:"<<Month;
cout<<"Day:"<<Day;
cout<<"Year:"<<Year;
cout<<endl;
}
};
//有界数组模板,用于检测是否越界(写法是固定的)
template<class T,int b>
class Array
{
T elem[b];
public:
Array(){}
T& operator [](int sub)
{
assert(sub>=0&&sub<b);
return elem[sub];
}
};
//主函数用于测试
int main()
{
Array<Date,3> dataArray;//有界数组检测模板,即数组长度为3
//定义三个日期
Date dt1(1,2,3);
Date dt2(4,5,6);
Date dt3(7,8,9);
//将三组数据添加到检测模板中
dataArray[0] = dt1;
dataArray[1] = dt2;
dataArray[2] = dt3;
//依次输出检测信息
for(int i = 0;i<3;i++)
cout<<dataArray[i]<<endl;
//第四组数据异常,超过数组长度
Date dt4(10,11,13);
dataArray[3] = dt4;
cout<<dataArray[3]<<endl;
return 0;
}
//程序输出
Month:1 Day:2 Year:3
Month:4 Day:5 Year:6
Month:7 Day:8 Year:9
Assertion failed!
Program: D:\\1\test\bin\Debug\test.exe
File: D:\桌面\1\test\main.cpp, Line 47
Expression: sub>=0&&sub<b
模板的使用
1、定制模板
定义完模板之后若想对模板功能进行扩充,需要对类模板进行覆盖,这种覆盖操作称之为定制。
例如:定制一个类模板,然后覆盖模板中所定义的所有成员。
#include<iostream>
#include<iomanip>//用来对输入输出操作的格式进行更加方便的控制用来对输入输出操作的格式进行更加方便的控制
using namespace std;
//定义一个普通类Date
class Date
{
int Month,Day,Year;
char Format[128];
public:
Date(int m=0,int d=0, int y=0)//构造函数,用于对变量进行初始化
{
Month = m;
Day = d;
Year = y;
}
//ostream是output stream的简称,即输出流.ostream这个类型,往往出现在(操作重载中,作为某个类的友元函数出现)
friend ostream& operator<<(ostream& os, const Date t)
{
cout<<"Month:"<<t.Month<<' ';
cout<<"Day:"<<t.Day<<' ';
cout<<"Year:"<<t.Year<<' ';
return os;
}
void Display()
{
cout<<"Month:"<<Month;
cout<<"Day:"<<Day;
cout<<"Year:"<<Year;
cout<<endl;
}
};
//定义Set类模板
template<class T>
class Set
{
T t;
public:
Set(T st):t(st){}
void Display()
{
cout<<t<<endl;
}
};
template<>
class Set<Date>//使用Date对整个类模板进行了定制
{
Date t;
public:
//两个成员函数都需要进行重写
Set(Date st):t(st){}
void Display()
{
cout<<"Date:"<<t<<endl;
}
};
//主函数用于测试
int main()
{
Set<int> intset(123);
Set<Date> dt=Date(1,2,3);
intset.Display();//原模板的输出 123
dt.Display();//定制后的输出 Date:Month:1 Day:2 Year:3
return 0;
}
若要覆盖模板中指定的成员:
template <class T>
//指定特定成员函数
void Set<Date>::Dispaly()
{
//进行重写
cout<<"Date:"<<t<<endl;
}
链表类模板
下例是一个基础的链表定义及操作:
#include<stdio.h>
#include<iostream>
using namespace std;
//节点类
class Node
{
public:
int data;
Node *next;
Node()
{
next = NULL;
}
};
//链表类
class List
{
private:
Node *Header;
int NodeSum;
public:
//链表初始化
List()
{
Header = NULL;
NodeSum = 0;
}
//移动尾节点
Node* MoveTrail()
{
Node *ptail = Header;
for (int i = 1;i<NodeSum;i++)
{
ptail = ptail->next;
}
return ptail;
}
//添加节点
void AddNode(Node *pNode)
{
if (NodeSum == 0)
Header = pNode;
else
{
Node *p = MoveTrail();
p->next = pNode;
}
NodeSum++;
}
//遍历链表
void PassList()
{
if (NodeSum > 0)
{
Node *ptemp = Header;
printf("%d",ptemp->data);
for(int i = 1;i<NodeSum;i++)
{
ptemp = ptemp->next;
printf("%d",ptemp->data);
}
}
}
//析构函数
~List()
{
if(NodeSum>0)
{
Node *Delate = Header;
Node *p = NULL;
for(int i = 0;i<NodeSum;i++)
{
p = Delate->next;
delete Delate;
Delate = p;
}
NodeSum = 0;
//指针置空,以防变成野指针
Delate = NULL;
p = NULL;
}
Header = NULL;
}
};
链表的最大缺陷就是不够灵活,其节点形式只能是Node类型。若让List能适应各种类型的节点的最简单的办法就是使用类模板。
则重新定义链表类使其成为链表类模板:
#include<stdio.h>
#include<iostream>
using namespace std;
class Node
{
public:
int data;
Node *next;
Node()
{
next = NULL;
}
};
template<class Type> //修改一:将原链表的Node类型全部替换为模板参数Type
class List
{
private:
Type *Header;
int NodeSum;
public:
//链表初始化
List()
{
Header = NULL;
NodeSum = 0;
}
//移动尾节点
Type* MoveTrail()
{
Type *ptail = Header;
for (int i = 1;i<NodeSum;i++)
{
ptail = ptail->next;
}
return ptail;
}
//添加节点
void AddNode(Type *pNode)
{
if (NodeSum == 0)
Header = pNode;
else
{
Type *p = MoveTrail();
p->next = pNode;
}
NodeSum++;
}
//遍历链表
void PassList()
{
if (NodeSum > 0)
{
Type *ptemp = Header;
printf("%d ",ptemp->data);
for(int i = 1;i<NodeSum;i++)
{
ptemp = ptemp->next;
printf("%d ",ptemp->data);
}
}
}
//析构函数
~List()
{
if(NodeSum>0)
{
Type *Delate = Header;
Type *p = NULL;
for(int i = 0;i<NodeSum;i++)
{
p = Delate->next;
delete Delate;
Delate = p;
}
NodeSum = 0;
//指针置空,以防变成野指针
Delate = NULL;
p = NULL;
}
Header = NULL;
}
};
int main(int argc,char* argv [])
{
List<Node> nodelist;//修改二,节点类型以参数的形式传入到模板中
//循环添加节点
for(int i = 0;i<5;i++)
{
Node *p = new Node;
p->data = i;
nodelist.AddNode(p);
}
//遍历链表
nodelist.PassList();
return 0;
}