对于函数模板和类模板,模板参数并不局限于类型,普通值也可以作为模板参数。当要使用基于值的模板时,我们必须要显式的指定这些值,才能够对模板进行实例化。
我们用stack来做例子,当我们使用元素数目固定的数组来实现stack时,有一个优点就是无论是我们自己来管理内存还是由标准容器来管理内存,都可以避免内存管理的开销。但是缺点也是很明显的,决定一个栈的最佳容量是很困难的,如果我们指定的容量太小,那么栈可能会溢出,如果指定的容量太大,那可能会不必要的浪费内存,于是,不难想到,我们有没有什么办法让用户亲自指定数组的大小,并把它作为所需要的栈元素的最大个数,于是我们可以把数组大小定义为一个模板参数,用户在实例化栈的时候传递参数,就做到了由用户自己根据需要确定栈元素的最大个数。
我们给出一个栈的类模板:
#include
using namespace std;
template <typename T,int MAXSIZE>
class Stack {
public:
Stack();//构造函数
void push(T const&);//压栈
void pop();//出栈
T top() const;//取栈顶元素
bool empty() const {
//判断栈空
return numElems == 0;
}
bool full() const {
//判断栈满
return MAXSIZE == numElems;
}
private:
T elems[MAXSIZE];//包含元素的数组
int numElems;//当前元素个数
};
template <typename T,int MAXSIZE>
Stack<T, MAXSIZE>::Stack() :numElems(0)
{}
template <typename T,int MAXSIZE>
void Stack<T, MAXSIZE>::push(T const& elem)
{
if (Stack<T, MAXSIZE>::full()) return;
elem[numElems] = elem;//附加元素
++numElems;//增加当前栈中元素个数
}
template <typename T,int MAXSIZE>
void Stack<T, MAXSIZE>::pop()
{
if (Stack<T, MAXSIZE>::empty()) return;
--numElems;//减少当前栈中元素个数
}
template <typename T,int MAXSIZE>
T Stack<T, MAXSIZE>::top()const
{
if (Stack<T, MAXSIZE>::empty()) return;
return elems[numElems - 1];//返回最后一个元素
}
MAXSIZE是第二个模板参数,用来指定数组最多可以包含的栈元素的个数,所以当使用这个类模板的时候,我们需要同时指定栈的元素类型和个数(栈的最大容量)。
int main()
{
Stack<int, 40> int40Stack;//可以存储40个int元素的栈
Stack<int, 20> int20Stack;//可以存储20个int元素的栈
Stack<char, 10> char40Stack;//可以存储10个char元素的栈
int40Stack.push(5);
int20Stack.push(2);
char40Stack.push('a');
char ret = char40Stack.top();
cout << ret << endl;
return 0;
}
可以看出,每一个模板都有自己的实例,所以int40Stack、int20Stack和char40Stack属于不同的类型,而且这些类型之间也不存在显式或者隐式的类型转换,所以他们之间不能相互替换,更不能相互赋值。
我们也可以为模板参数指定缺省值:
template <typename T,int MAXSIZE = 50>
class Stack {
...
};
除了类,函数模板也可以定义非类型参数。
增加特定值的函数模板:
template <typename T,int VAL>
T addvalue(T const& x)
{
return x + VAL;
}
当然也可以设置VAL为默认值。
非类型模板参数是有限制的,通常而言,它们可以是常整数,包括枚举值或者是指向外部链接对象的指针。
模板可以具有值模板参数,而不仅仅是类型模板参数。
对于非类型模板参数,不能使用浮点数、class类型的对象和内部链接对象作为实参。
任何未被初始化的局部变量都具有一个不确定值,假设我们有一个模板,我们希望模板类型的变量都已经用缺省值初始化完毕,这时我们会遇到问题,内建类型并不能满足我们的要求,于是我们就要显式的调用内建类型的缺省构造函数,并把缺省值设为0,借助零初始化技术来实现,当我们用模板实例化出一个对象时,零初始化就会帮我们把变量初始化为对应类型的零值。
template <typename Type>
class Test
{
public:
Test(Type data = Type()) :_data(data)
{};
private:
Type _data;
};
void main()
{
Test<int> t;
Test<bool> t1;
}
template <typename Type>
void fun()
{
Type x = Type();//零初始化
}
void main()
{
fun<int>();
}
通常,我们会把声明放入一个头文件,再把实现放入另外一个源文件,但是对于模板来说,这样做是不支持的,就是因为模板不支持分离编译。