以下主要实例都来自来自《c++ primer plus》
定义和使用模板类
#ifndef STACK_H
#define STACK_H
using std::string ;
using std::cout;
using std::endl;
template <typename T>
class Stack{
private :
enum{MAX=10};
T items[MAX];
int top;
public :
Stack();
bool isempty();
bool isfull();
bool push(const T& item);
bool pop(T& item);
};
template<typename T>
Stack<T>::Stack(){
top=0;
}
template<typename T>
bool Stack<T>::isempty(){
return top==0;
}
template<typename T>
bool Stack<T>::isfull(){
return top==MAX;
}
template<typename T>
bool Stack<T>::push(const T& item){
if(top<MAX){
items[top++]=item;
return true;
}
else
return false;
}
template<typename T>
bool Stack<T>::pop(T& item){
if(top>0){
item=items[--top];
return true;
}
else
return false;
}
#endif
和模板函数很像定义和使用时都要加上
template<typename T>
在使用模板累的时候一定要指定其类型
Stack<int> stack;
Stack<int> stack=new Stack<int>();
小心使用模板类
不正确使用指针栈
首先我们通过上面的Stack类来存储string 类型订单信息。
#include <iostream>
#include<cstring>
#include<cctype>
#include"stack.h"
using std::string ;
using std::cout;
using std::cin;
using std::endl;
int main(int argc, char** argv) {
Stack<string> st;
char ch;
string po;
cout<<"Please enter A to add a purchase order.\n"
<<"P to process a PO ,or Q to quit.\n";
while (cin>>ch&&std::toupper(ch)!='Q'){
while (cin.get()!='\n')
continue;
if(!std::isalpha(ch)){
cout<<'\a';
continue;
}
switch(ch){
case 'A':
case 'a':
cout<<"enter a PO number to add ";
cin>>po;
if(st.isfull())
cout<<"stack already full\n";
else
st.push(po);
break;
case 'P':
case 'p':
if(st.isempty())
cout<<"stack already empty\n";
else{
st.pop(po);
cout<<"PO # "<<po<<" popped\n";
}
break;
}
cout<<"Please enter A to add a purchase order.\n"
<<"P to process a PO ,or Q to quit.\n";
}
cout<<"Bye\n";
return 0;
}
注意事项1:
首先如果我们把
Stack<sting> st;
string po
改为
Stack<char *> st;
char *po
会怎么样呢?
因为我们没有为指针定义存储的空间当运行到cin>>po时程序会奔溃
注意事项1:
首先如果我们把
Stack<sting> st;
string po
改为
Stack<char *> st;
char po[40]
会怎么样呢?
这样po 是char * 类型的满足Stack < char * > 。
而且也有内存空间可以存储字符串。但是数组又会和pop方法出现了冲突,例如
template<typename T>
bool Stack<T>::pop(T& item){
if(top>0){
item=items[--top];
return true;
}
else
return false;
}
首先引用一定是左值,而数组名是一个常量,不能赋值给他,其次
item=items[--top];
它给数组名(一个常量)赋值了。之显然也是错误的。
注意事项3:
首先如果我们把
Stack<sting> st;
string po
改为
Stack<char *> st;
char 8 po=new char[40]?
会怎么样呢?
好像没问题了。po也可写入数据了,也不会和pop方法冲突了。但是好像还有问题,因为所有的压栈和出栈都是对同一个指针进行操作。所以栈中所有的指针都是指向最后写进来的数据。
正确使用指针栈
某人将一车文件交给Plodson,如果Plodson的收取篮(in-basket)是空的,就会把车中最上层的文件放入收取蓝中,如果收取蓝是满的,将把收取蓝最上层的文件拿出来处理放入发出蓝中,如果收取蓝即不是空的也不是满的,就会扔硬币的方式来决定文件的去和留下来
#ifndef STACKP_H
#define STACKP_H
using std::string ;
using std::cout;
using std::endl;
template <typename T>
class Stack{
private :
enum{SIZE=10};
int stacksize;
T *items;
int top;
public :
explicit Stack(int ss=SIZE);
Stack(const Stack &);
~Stack(){delete [] items;}
Stack & operator=(const Stack & st);//返回类型Stack 和Stack<T>一样的
bool isempty(){return top==0;}
bool isfull(){return top==stacksize;}
bool push(const T& item);
bool pop(T& item);
};
template<typename T>
Stack<T>::Stack(int ss):stacksize(ss),top(0){
items=new T [stacksize];
}
template<typename T>
Stack<T>::Stack(const Stack & st){
stacksize=st.stacksize;
top=st.top;
items=new T [stacksize];
for(int i=0;i<top;i++)
items[i]=st.stems[i];
}
template<typename T>
Stack<T>& Stack<T>::operator=(const Stack<T> & st){
if(this==&st)
return *this;
delete [] items;
stacksize=st.stacksize;
top=st.top;
items=new T [stacksize];
for(int i=0;i<top;i++)
items[i]=st.stems[i];
return *this;
}
template<typename T>
bool Stack<T>::push(const T& item){
if(top<stacksize){
items[top++]=item;
return true;
}
else
return false;
}
template<typename T>
bool Stack<T>::pop(T& item){
if(top>0){
item=items[-
-top];
return true;
}
else
return false;
}
#endif
#include <iostream>
#include<cstdlib>//for rand(),srand()
#include<ctime>//for time()
#include"stackp.h"
using std::string ;
using std::cout;
using std::cin;
using std::endl;
const int Num=10;
int main(int argc, char** argv) {
std::srand(std::time(0));//randomize rand();
std::cout<<"please enter stack size ";
int stacksize;
std::cin>>stacksize;
//data
const char * in[Num]={
"1:Hank Gilgamesh"
,"2:Kiki Ishtar"
,"3:Betty Rocker"
,"4:Ian Flagranti"
,"5:Wolfgang Kibble"
,"6:Portia Koop"
,"7:Joy Kibble"
,"8:Xaverie Paprika"
,"9:Juan Moore"
,"10:Misha Mache"
};
//creat an empty stack with stacksize slots
//in basket
Stack<const char *>st(stacksize);
//out basket
const char* out[Num]={};
int processed=0;
int nextin=0;
while(processed<Num){
if(st.isempty())
st.push(in[nextin++]);
else if(st.isfull())
st.pop(out[processed++]);
else if(std::rand()%2&&nextin<Num)
st.push(in[nextin++]);
else
st.pop(out[processed++]);
}
for(int i=0;i<Num;i++)
cout<<out[i]<<std::endl;
cout<<"Bye\n";
return 0;
}
数组模板示例和非类型参数
#ifndef ArrayT_H
#define ArrayT_H
#include<iostream>
#include<cstdlib>
template<typename T,int n>
class ArrayT{
private :
T ar[n];
public :
ArrayT(){};
explicit ArrayT(const T& v);
virtual T & operator[](int i);
virtual T operator[](int i)const;
};
template <typename T,int n>
ArrayT<T,n>::ArrayT(const T& v){
for(int i=0;i<n;i++)
ar[i]=v;
}
template<typename T,int n>
T& ArrayT<T,n>::operator[](int i)
{
if(i<0||i>=n)
{
std::cerr<<"Error in array limits:"<<i
<<"is out of range\n";
std::exit(EXIT_FAILURE);
}
return ar[i];
}
template <typename T,int n>
T ArrayT<T,n>::operator[](int i)const
{
if(i<0||i>=n){
std::cerr<<"Error in array limits:"
<<"is out of range\n";
std::exit(EXIT_FAILU
RE);
}
return ar[i];
}
#endif
template<typename T,int n>
上述T指出类型参数,int指出n的类型为int。这种参数(指定特殊类型而不用作泛型名)称为非类型(non-type)或表达式(expression)参数
非类型参数有一些限制
- 表达式参数可以为整型,枚举,引用和指针
- 模板代码不能修改参数的值
- 实例模板使,表达式参数的值必须为常量表达式
对于上面的模板创建一个实例ArrayT<double ,12> arr
编译器会用double代替T使用12代替n。但是如果我们给n传递一个变量m,如`ArrayT
ArrayT <double ,19> ar1;
ArrayT<double 20>ar2;
上面会生成两个模板实例
Stack<double > st1;
Stack<double> st2;
上面只会生成一个模板实例。
在实际使用中我们的Stack类还是比ArrayT类更加通用,因为Stack使用的是一个指针,可以指向不同大小的数组,方便更改。
模板多功能性
模板类可以被用作基类,组件类,还可以用与其他模板的类型参数
template <typename T>
class Array{
private :
T entry;
...
}
template <typebane Type>
class GrowArray:public Array<Type>//inheritance
template<typename Tp>
class Stack{
Array<Tp> ar; //a component
...
}
...
Array <Stack<int>> asi;//argument
Note:
最后一条语句再c++98中要> >中间要有一个空格以区分运算符>>但是c++11中就不需要了
递归使用模板
对于ArrayT我们可以递归使用它,如
ArrayT<ArrayT<int,5>,10>
twoart
与之定价的常规数组声明为
int twoart [10][5]
默认类型模板参数
template<typename T1,typename T2=int>
class A{
……
}
A<double,double>a1
A<double >a2//T1 double,T2 int
Note:
- 模板类型参数默认值,函数模板没有这样的功能
- 表达式参数可以提供默认值,这对于类模板和函数模板都适用
模板具体化
1 隐式实例化
Stack<char *> st
编译器使用通用模板来生成具体的类定义
Stack<char *> * p
p=new Stack<char *> ;
编译器在需要对象之前是不毁创建对象的。如上面第一条语句是不会创建对象的,而第二条会创建对象。
2显式实例化
template class Stack<char * >;
使用关键字template来显式实例化类定义,
虽然没有创建对象,但是编译器会生成类的声明
3显式具体化
template<>class Stack<char*>
{
...
}
使用关键字template<>,虽然没有创建对象,但是编译器会在使用在使用时生成类的声明,
而且调用顺序和函数模板一样。具体类优先于显式具体化模板优先于通用模板
Tips::
早期的格式为
class Stack<char*>
{
...
}
这种格式是没有关键字template<>
部分具体化模板
template <typename T1,typename T2> class A{}//模板
template <typename T1> class A<T1,int>{}//部分具体化模板
Note:
注意具体化的部分要从template<>参数中移除,并加入后面的<>中
成员模板
模板可以作为结构,类和模板类的成员
template<typename T>
class A{
private :
template<typename V>
class B{
private:
V val;
public :
B(V v=0):val(v){};
void show const {cout<<va<<endl;}
V value() const {return val;}
};
B<T>q;
B<int> n;
public:
A(T t,int i):q(t),n(i){}
template<typenam U>
U blab(U u ,T t){return (n.Value()+q.Value())*u/t;}
void Show() const{q.show();n.show);}
};
代码为定义如下
template<typename T>
class A{
private :
template<typename V>
class B;
B<T>q;
B<int> n;
public:
A(T t,int i):q(t),n(i){}
template<typenam U>
U blab(U u ,T t);
void Show() const{q.show();n.show();}
};
template<typename T>
template<typename V>
class A<T>::B{
private:
V val;
public :
B(V v=0):val(v){};
void show const {cout<<va<<endl;}
V value() const {return val;}
};
template<typename T>
template<typename U>
U A<T>::blab(U u ,T t)
{
return (n.Value()+q.Value())*u/t;
}
模板参数
template<template <typename T> class A>
class B{
private :
A<int> a1;
A<double> a2;
}
如果有一个别的模板类C,它的类型和上面声明的(templateclass ) 完全一样
template<typename V>
class C{
}
那么我们就可以把它当作B的模板参数了
B<C> b1;
这样B中的
A<int> a1;
A<double> a2;
就会被替换为
C<int> a1;
C<double> a2;
模板和友元
模板类的有源分为三类
- 非模板友元
- 约束(bound)模板友元,即友元类型取决于类被实例时的类型
- 非约束模板友元,即友元的所有具体化都是类的每一个具体化的友元
非模板友元
template<typename T>
class HasFriend{
public :
friend void counts();
}
couts函数将会时模板所有实例的友元,如count函数将会是Hasfriend,Hasfriend的友元。那我们能不能这样呢
friend void couts(HasFriend &)
答案是不可以。因为根本不存在HasFriend这个类型。不过我们可以这样
template<typename T>
class HasFriend{
public :
friend void report(HasFriend<T>&);
}
当我们隐式实例化的时候
HasFriend<int> hf
的时候编译器会用int来替换T,原先的式子就会变成这样
class HasFriend{
public :
friend void report(HasFriend<int>&);
}
因为友元函数couts不是模板类的成员,所以我们必须在外面定义一个接受HasFriend&参数的report函数,同样的如果需要HasFriend&,HasFriend&版本的report也都需要独立定义。
Note:
因为我们定义的模板时是没有实例的,那我们的友元函数怎么访问模板生成的类,类生成的对象呢。但是它可以访问如下
- 全局对象(全局指针可以访问非全局对象)
- 可以创建自己的对象
- 可以访问独立与模板类的静态变量
using std::endl;
using std::cout;
template<typename T>
class HasFriend{
private:
T item;
static int ct;
public:
HasFriend(const T& i):item(i){ct++;}
~HasFriend(){ct--;}
friend void counts();
friend void report(HasFriend<T> &);
};
template<typename T>
int HasFriend<T>::ct=0;
void counts(){
cout<<"int counts:"<<HasFriend<int>::ct<<";";
cout<<"double counts:"<<HasFriend<double>::ct<<endl;
}
void report(HasFriend<int> & hf){
cout<<"HasFriend<int>:"<<hf.item<<endl;
}
void report(HasFriend<double> & hf){
cout<<"HasFriend<double>:"<<hf.item<<endl;
}
int main(){
cout<<"No object declared:";
counts();
HasFriend<int> hf1(10);
cout<<"After hf1 declared:";
counts();
HasFriend<int> hf2(19);
cout<<"After hf2 declared:";
counts();
HasFriend<double> hf3(19.0);
cout<<"After hf3 declared:";
counts();
report(hf1);
report(hf2);
report(hf3);
return 0;
}
约束模板友元
就像上面所说的,如果对于普通模板的友元,我们可能需要写很多差不多的代码,效率上就会差很多。而约束模板友元就大不相同了。
我们需要三步来完成这项事情
第一步:首先要把友元函数声明成模板函数
template<T>void counts();
template<T>void report(T&);
第二步:在模板类中在此声明友元函数,但是这些语句要根据模板参数的类型声明具体化
template<typename T>
class HasFriend{
……
friend void counts<T>();
friend void report<>(HasFriend<T>&);
}
对于report我们也可以这样声明
friend void report<HasFriend<T>>(HasFriend<T>&);
因为可以从参数列表判断模板类型,所以也可以不写<>。但是counts必须要写。
当我们完成上面的工作后声明一个对象
HasFriend<int> hf
模板类就会变成这样
class HasFriend<int>{
……
friend void counts<int>();
friend void report<>(HasFriend<int>&)`
}
第三步:定义之前声明的模板函数counts和report
当我们调用counts和report函数时就会根据参数自动生成相应的函数版本,而这些版本也会刚好和上面的友元函数模板具体化匹配上
#include<iostream>
using std::endl;
using std::cout;
template <typename T> void counts();
template <typename T> void report(T&);
template<typename TT>
class HasFriend{
private :
TT item;
static int ct;
public:
HasFriend(const TT& i):item(i){ct++;}
~HasFriend(){ct--;}
friend void counts<TT>();
friend void report<>(HasFriend<TT> &);
};
template<typename T>
int HasFriend<T>::ct= 0;
template<typename T>
void counts(){
cout<<"template size:"<<sizeof (HasFriend<T>)<<";";
cout<<"template counts():"<<HasFriend<T>::ct<<endl;
}
template<typename T>
void report(T& hf){
cout<<hf.item<<endl;
}
int main(){
counts<int>();
HasFriend<int> hf1(10);
HasFriend<int> hf2(19);
HasFriend<double> hf3(19.0);
report(hf1);
report(hf2);
report(hf3);
cout<<"counts<int>() ouput"<<endl;
counts<int>();
cout<<"counts<double>() ouput"<<endl;
counts<double>();
return 0;
}
非约束模板友元
通过在类中声明模板,就会创建非约束的友元函数,即每个函数的具体化都是没给类具体化的友元
#include<iostream>
using std::endl;
using std::cout;
template<typename T>
class ManyFriend{
private :
T item;
public:
ManyFriend(const T& i):item(i){}
template <typename C,typename D>friend void show(C & ,D &);
};
template<typename C,typename D>
void show(C & c ,D & d){
cout<<c.item<<";"<<d.item<<endl;
}
int main(){
ManyFriend<int> hf1(10);
ManyFriend<int> hf2(19);
ManyFriend<double> hf3(19.1);
cout<<"hf1,hf2:";
show(hf1,hf2);
cout<<endl;
cout<<"hf2,hf3:";
show(`
f2,hf3);
return 0;
}
模板别名
可以使用typedef为模板起别名如
typedef std::array<double ,12>arrd;
typedef std::array<int ,12>arri;
typedef std::array<string ,12>arrst;
arrd a;
arri b;
arrst c;
c++11新增了一项模板别名的功能
template<typename T>
using arrtype =std:array<T,12>;
.....
arrtype<double> a;//array<double,12>
arrtype<int> a;
//array<int,12>
arrtype<string> a;//
array<string,12>
c++11还可以把using用于非模板下,这时候和typedef等价
嵌套类模板
下面是一个”队列“的简单实现
#ifndef QUEUET_H
#define QUEUET_H
template<typename Item>
class QueueT{
private:
enum {Q_SIZE=10};
//Node is nested class definition(It's more like thiat linked list)
class Node{
public :
Item item;
Node * next;
Node(const Item& i):Item(i),next(0){}
};
Node * front;
Node * rear;
int items;
const int qsize;
QueueT(const QueueT & q):qsize(0){};
QueueT & operator=(const QueueT & q){return * this;}
public :
QueueT(int qs=Q_SIZE);
~QueueT();
bool isempty()const
{
return items==0;
}
bool isfull() const
{
return items==qsize;
}
int queuecount()const
{
return items;
}
bool enqueue(const Item &);//add item to end;
bool dequeue( Item &);//remove item from front;
};
//QueueT method
template <typename Item>
QueueT<Item>::QueueT(int qs):qsize(qs){
front=rear=0;
items=0;
}
template <typename Item>
QueueT<Item>::~QueueT(){
Node *temp;
while(front!=0){ //while queue is not yet empty
temp= front; //save address of front item
front =front->next;//reset pointer to next item
delete temp; //delete former front
}
}
//add item to queue
template<typename Item>
bool QueueT<Item>::enqueue(const Item & item){
if(item.isfull())
return false;
Node *add=new Node(item); //create node
//on failure,new thoews std::bad_alloc excepion
items++;
if(front==0) //if queue is empty
front=add; //place item at fornt
else
rear->next=add;//else place at rear
rear=add; //have rear point to new node
return true;
}
//place itme into item variable and remove from queue
template<typename Item>
bool QueueT<Item>::dequeue(Item & item){
if(front==0)
return false;
item=front->item;
items--;
Node *temp=front;
f``
ont=front->next;
delete temp;
if(items==0)
rear=0;
return true;
}
#endif
上面对于每一个Queue模板的实例都是会生成一个Node
Queue<double>::Node
Queue<int>::Node
所以上面两个名称是不会冲突的