容量类等有些类并不能使用继承和包含来实现。容器类设计用来存储其他对象或数据类型,比如Stack、Queue。
与其编写类声明,不如编写泛型栈,然后将具体的类型作为参数传递这个类。既类模板。
定义类模板
- 代码开头
template <typename Type>
- Type应作为标识符
- 类声明定义了方法,则可以省略模板前缀和类限定符
- 模板的具体实现被称为实现化或具体化,模板必须与特点模板实例化请求一起使用,最简单的方法是将所有模板信息放在header中。
#ifndef STACKTP_H_
#define STACKTP_H_
template <class Type>
class Stack
{
private:
enum{ MAX=10};
Type items[MAX]; //Type作为标识符
int top;
public:
Stack();
bool isempty();
bool isfull();
bool push(const Type& item);
bool pop(Type& item);
} ;
template <class Type> //不是在类声明中,每次定义需要模板前缀和限定符
Stack<Type> ::Stack() //Stack::变为Stack<Type> ::
{
top=0;
}
template <class Type>
bool Stack<Type>::isempty()
{
return top==0;
}
template <class Type>
bool Stack<Type>::isfull()
{
return top == MAX;
}
template <class Type>
bool Stack<Type>::push(const Type& item)
{
if(top<MAX)
{
items[top++] = item;
return true;
}
else
return false;
}
template <class Type>
bool Stack<Type>::pop(Type& item)
{
if(top>0)
{
item = items[--top];
return true;
}
else
return false;
}
#endif
使用类模板
- 使用具体类型替换泛型名,如
Stack<string> st
#include<iostream>
#include"stacktp.h"
#include<cctype>
#include<string>
using namespace std;
int main()
{
Stack<string> st;
char ch;
string po;
cout<<"Enter A to add a purchase order\nP to process a PO\nQ to quit\n";
while(cin>>ch&&toupper(ch)!='Q')
{
while(cin.get()!='\n')
continue;
if(!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 is full\n";
else
st.push(po);
break;
case'p':case'P':
if(st.isempty())
cout<<"Stack is empty\n";
else
{
st.pop(po);
cout<<"PO #"<<po<<" poped\n";
}
break;
}
cout<<"Enter A to add a purchase order\nP to process a PO\nQ to quit\n";
}
cout<<"Done\n";
return 0;
}
image.png
深入探讨模板类
创建指针栈的问题:
- 不正确使用指针栈
Stack<char* > st;
①将string po
替换成char* po
没有创造用于保存字符串的空间
②string po
替换为char po[40]
数组与pop操作冲突
③string po
替换为char* po = new char[40]
只有一个pop变量,该变量总是指向相同的内存地址。压入栈时,保存的都是相同地址。- 正确使用
提供指针数组
PS.① 要一个可变大小的栈,则用Type * items; int stacksize;
构造函数:
items = new Type [ stacksize ];
析构函数
delete [] items
②在主程序里
Stack<const char*> st(stacksize); const char* in[NUM] ={"..." ....}; st.push(in[next++]);
构造函数new创建一个用于保存指针的数组,数组元素是指向字符串的指针,析构函数是删除该数组,而不是指针指向的字符串
数组模板和非类型参数
template < class T, int n >
class ArrayTP
{
private:
T ar [n]
....
}
- int n 这种参数叫非类型或表达式参数。表达式参数可以是整型、枚举、引用或指针。模板代码不能修改参数值,用作表达式参数的值必须是常量表达式。
- 表达式参数方法使用的是为自动变量维护的内存栈,执行速度更快。缺点是每种数组大小都要生成自己的模板。
ArrayTP<double ,12> a1
和ArrayTP <int, 12> a2
共享一个模板,但ArrayTP<double , 13> a3
则是新的模板.构造函数方法会更通用。
模板多功能性
- 递归使用模板:
ArrayTP< ArrayTP<int ,5> ,10>
等于int [10][5]
- 使用多个类型参数
#include<iostream>
#include<string>
template<class T1, class T2>
class Pair
{
private:
T1 a;
T2 b;
public:
T1& first() ;
T2& second() ;
T1 first() const{return a;}
T2 second() const{return b;}
Pair(const T1& aval,const T2& bval):a(aval),b(bval){}
Pair() {}
};
template<class T1,class T2>
T1& Pair<T1,T2>::first()
{
return a;
}
template<class T1,class T2>
T2& Pair<T1,T2>::second()
{
return b;
}
int main()
{
using namespace std;
Pair<string,int> r[4]=
{
Pair<string,int>("JEff1",10),
Pair<string,int>("JEff2",11),
Pair<string,int>("JEff3",12),
Pair<string,int>("JEff4",13),
};
int lim = sizeof(r)/sizeof(Pair<string,int>);
for(int i=0;i<lim;i++)
{
cout<<r[i].first()<<endl;
cout<<r[i].second()<<endl;
}
r[3].first() ="Jeff edit";
r[3].second() = 100;
cout<<r[3].first()<<" "<<r[3].second();
return 0;
}
- 默认类型模板参数
template< class T1, class T2 =int >
没有T2值,则默认为int
模板具体化
- 隐式实例化
即它们声明一个或多个对象,指出所需的类型,而编译器使用通用模板提供的处方生成具体的类定义。就需要对象的时候会隐式根据类模板创造对象。- 显示实例化
使用template并指出所需类型来声明类,编译器将生成类声明的显示实例化(explicit instantiation)。声明必须位于模板定义所在的名字空间。
template class ArrayTP<string,100>
,这种情况虽然没有创建或提及类对象,编译器也将生成类声明(方法定义)。和隐式实例化一样,根据通用模板来生成具体化。- 显示具体化
显式具体化是特定类型(用于替换模板泛型)的定义。比如一些类可以用T::operator>()
,但如果是const char*,就得用strcmp来比较大小比较好。这时候可以采用具体类型定义的模板
格式如下:template <> class Classname(type_name)
比如template <> class SortedArray<const char* > { ....}
这样当请求const char* 模板类时,编译器将使用上述定义而不是通用模板定义。
- 部分具体化
通用模块template<class T1,class T2> class Pair {...}
部分具体化template<class T1> class Pair<T1,int>
显式具体化template<> class Pair<int ,int>
当有多个模板供选择时,编译器将使用具体化程度最高那个。
也可以通过指针提供特殊版本来部分具体化现有模板:template <class T> class Feeb{...}; 通用模板 template<class T*> class Feeb{...}
成员模板
#include<iostream>
using namespace std;
template<class T>
class Beta
{
private:
template<class V> 创建模板类成员
class Hold
{
private:
V val;
public:
Hold(V v=0):val(v){}
void show() const{cout<<val<<endl;}
V value() const {return val;}
};
Hold<T> q; 创建两个模板类对象
Hold<int> n;
public:
Beta(T t,int i) : q(t),n(i){}
template<class U>
U blab(U u,T t){return (q.value()+n.value())*u/t;}
//U类型由方法调用时显式确定,T类型由对象实例化确定
void show() const {q.show();n.show();}
};
int main()
{
Beta<double> guy(3.5,3); set为T为double
cout<<"T was set to double\n";
guy.show();
cout<<"V was set to T,which is double ,then V was set to int\n";
cout<<guy.blab(10,2.3)<<endl;
cout<<"U was set to int\n";
cout<<guy.blab(10.0,2.3)<<endl;
cout<<"U was set to double\n";
cout<<"Done!";
return 0;
}
image.png
也可以在Beta模板中声明hold类和blah方法,在Beta模板外定义它们。
template<class T>
class Beta
{
private:
template<class V>
class Hold; 声明类
Hold<T> q;
Hold<int> n;
public:
Beta(T t,int i):q(t),n(i){}
template<class U>
U blab(U u,T t); 声明方法
void show() const{q.show();n.show();}
};
template<class T>
template<class V> 嵌套模板类
class Beta<T>::Hold
{
private:
V val;
public:
Hold(V v=0):val(v){}
void show() const{std::cout<<val<<std::endl;}
V value() const{return val;}
};
template<class T>
template<class U>
U Beta<T>::blab(U u,T t)
{
return (n.value()+q.value()*u/t);
}
将模板用作参数
template<template<class T> class Thing>
class Crap
{
...
}
其中template<class T> class是类型,Thing是参数。
Crap<Stack> nebula;
就是声明一个Crap类的类对象,模板参数为Stack模板,然后如果类里有pop()方法,
就可以nebula.pop();
模板类和友元
- 非模板友元
- 约束模板友元:友元的类型取决于被实例化时的类型
- 非约束模板友元 : 友元的所有具体化都是类的每一个具体化的友元
- 非模板友元:
template<class T>
class has
{
public:
friend void count();
...
}
上述count()函数成为模板所有实例化友元.可以通过访问全局对象、使用全局指针访问非全局对象、创建自己的对象、访问独立于对象模板类的静态数据成员来访问has对象。
friend void report(has& )
是不可以的,因为不存在这样的对象,必须具体化
template<class T>
class has
{
public:
friend void report(has<T> &)
}
class has<int>
{
friend void report(has<int>&)
}
那将成为has<int>的类友元。
如果has模板有一个静态成员,这说明这个类每一个特定具体化都将有自己的静态成员。
例子:
#include<iostream>
using namespace std;
template<class T>
class has
{
private:
T item;
static int ct;
public:
has(const T& t):item(t){ct++;}
~has(){ct--;}
friend void count();
friend void report(has<T>&);
} ;
template<class T>
int has<T>::ct = 0;
void count()
{
cout<<"int count: "<<has<int>::ct;
cout<<", Double count: "<<has<double>::ct<<endl;
}
void report(has<int>& t)
{
cout<<"has<int> item: "<<t.item<<endl;
}
void report(has<double>& t)
{
cout<<"has<double> item: "<<t.item<<endl;
}
int main()
{
cout<<"No objects: \t";
count();
has<int> h1(10);
cout<<"After h1: \t";
count();
has<double> h2(30.0);
cout<<"After h2: \t";
count();
has<int> h3(8);
cout<<"After h3: \t";
count();
report(h1);
report(h2);
report(h3);
return 0;
}
- 每一种模板类都有一个静态数据成员
- 静态数据成员定义如上,说明是T模板的静态成员
- count()函数是引用不同类模板数据,用作用域解析运算符
- report()是调用参数的数据,用点运算符
report函数要显式具体化,因为你要输出不同的字符串内容
image.png
- 模板类约束模板友元函数
#include<iostream>
using namespace std;
template<class T> void count();
template<class T> void report(T &);
template<class TT>
class has
{
private:
TT item;
static int ct;
public:
has(const TT& t):item(t){ct++;}
~has(){ct--;}
friend void count<TT>();
friend void report<>(has<TT>&);
} ;
template<class T>
int has<T>::ct = 0;
template<class T>
void count()
{
cout<<"template size:\t"<<sizeof(has<T>);
cout<<", template count():\t"<<has<T>::ct<<endl;
}
template<class T>
void report(T& t)
{
cout<<"object item:\t"<<t.item<<endl;
}
int main()
{
has<int> h1(10);
has<double> h2(30.0);
has<int> h3(8);
report(h1); 因为<>为空,可以omit
report(h2);
report(h3);
cout<<"Template int:\t";
count<int>(); 要在<>直接指出类型。
cout<<"Template double:";
count<double>();
return 0;
}
- 在类定义面前声明每个模板函数
- 在声明中指出模板具体化,可以在<>直接指出,也可以在通过参数推断出模板类型
- 为友元模板提供定义
非约束模板友元函数
通过类内部声明模板,可以创建非约束友元函数。约束友元函数就是你已经约束了友元函数用T模板去实现。非约束模板,你可以不仅使用一个模板去实现友元函数。
声明template<class C, class D> friend void show(C&, D&)
using namespace std;
template<class T>
class has
{
private:
T item;
public:
has(const T& t):item(t){}
~has(){}
template<class C,class D>friend void show(C&,D&);
} ;
template<class C, class D> void show(C& c,D& d)
{
cout<<c.item<<","<<d.item<<endl;
}
int main()
{
has<int> h1(10);
has<double> h2(30.4);
has<int> h3(8);
cout<<"h1 and h2 : ";
show(h1,h2);
cout<<"h1 and h3 : ";
show(h1,h3);
return 0;
}
image.png
模板别名
typedef std::array<double, 12> arrd
arrd d1
就声明了std::array<double,12> d1template<class T> using arrtype = std::array<T,12> arrtype<double> gallones
C++11允许using = 用于非模板
using pc = const char*