目录
1.类模板Stack的实现
类模板的声明和函数模板的声明相似,下面是一个类模板Stack的例子,这个类的类型是Stack<T>,其中T是模板参数。然而,当时用类名而不是类的类型时,就应该只用Stack;比如,指定类的名称、类的构造函数、析构函数时。总结如下:
- 和普通类的声明相似,先在类声明前加一行template <typename T>,然后将类声明中的数据类型换成虚拟类型参数T。
- 类模板的成员函数在类模板外定义时必须指定成函数模板,且需要使用这个类模板的完整类型限定符Stack<T>::。
#include <vector>
#include <stdexcept>
template <typename T>
class Stack{
private:
std::vector<T> elems; //存储元素的容器
public:
void push(T const&); //压入元素
void pop(); //弹出元素
T top() const; //返回栈顶元素
bool empty() const { //返回栈是否为空
return elems.empty();
}
};
template <typename T>
void Stack<T>::push(T const& elem)
{
elems.push_back(elem);
}
template <typename T>
void Stack<T>::pop()
{
if (elems.empty())
{
throw std::out_of_range("Stack<T>::pop(): empty stack");
}
elems.pop_back(); //删除最后一个元素
}
template <typename T>
T Stack<T>::top() const
{
if (elems.empty())
{
throw std::out_of_range("Stack<T>::pop(): empty stack");
}
return elems.back(); //返回最后一个元素的拷贝
}
2.类模板Stack的使用
类模板定义对象时形式为:类模板名<实际类型> 对象名或类模板名<实际类型> 对象名(实参表列)。例如,Stack<int> intStack。
Stack<int> intStack; //元素类型为int的栈
Stack<std::string> stringStack; //元素类型为字符串的栈
//使用int栈
intStack.push(7);
intStack.push('a');
std::cout << intStack.top() << std::endl;
//使用string栈
stringStack.push("chdayj");
std::cout << stringStack.top() << std::endl;
stringStack.pop();
同时,模板实参可以为任何类型:
Stack<float*> floatStrStack; //元素为浮点型指针的栈
Stack<Stack<int> > intStackStack; //元素为int栈的栈
注意,两个靠在一起的模板尖括号(即>) 之间要留一个空格;否则,编译器会误认为你在使用operator>>,从而导致语法错误。
3.类模板的特化
因为类模板中的定义不一定适用于所有数据类型,所以针对此类型应该重新定义,即所谓类模板的特化。如果要特化一个类模板,自然要特化此类模板所有的成员函数。
3.1.全特化
特化的类上面应该加着templlate<>,并且特化后的类的类型应该将T换成具体的数据类型,比如int,string等。同时,每个成员函数都必须重新定义为普通函数。下面是一个用std::string特化Stack<>的完整例子。
#include <deque>
#include <stdexcept>
#include <string>
template<>
class Stack<std::string>{
private:
std::deque<std::string> elems; //存储元素的容器
public:
void push(std::string const&); //压入元素
void pop(); //弹出元素
std::string top() const; //返回栈顶元素
bool empty() const { //返回栈是否为空
return elems.empty();
}
};
void Stack<std::string>::push(std::string const& elem)
{
elems.push_back(elem);
}
void Stack<std::string>::pop()
{
if (elems.empty())
{
throw std::out_of_range("Stack<<std::string>>::pop(): empty stack");
}
elems.pop_back(); //删除最后一个元素
}
std::string Stack<std::string>::top() const
{
if (elems.empty())
{
throw std::out_of_range("Stack<<std::string>>::pop(): empty stack");
}
return elems.back(); //返回最后一个元素的拷贝
}
上面的例子中用deque而不是vector,说明:特化的实现可以和基本类模板的实现完全不同。
3.2.局部特化
可以在特定的情况下指定类模板的特定实现,并且部分模板参数依然必须有用户来定义。例如类模板:
template <typename T1, typename T2>
class MyClass{
...
};
可以有以下几种局部特化:
//1.局部特化:两个模板参数具有相同的类型
template <typename T>
class MyClass<T, T>{
...
};
//2.局部特化:第2个模板参数类型为int
template <typename T>
class MyClass<T, int>{
...
};
//3.局部特化:两个模板参数都为指针类型
template <typename T1, typename T2>
class MyClass<T1*, T2*>{
...
};
下面的例子展示各种声明会使用哪个模板:
MyClass<int,float> mif; //使用MyClass<T1,T2>
MyClass<float,float> mFf; //使用MyClass<T,T>
MyClass<float,int> mfi; //使用MyClass<T,int>
MyClass<int*,float*> mp; //使用MyClass<T1*,T2*>
如果有多个局部特化同等程度地匹配某个声明,那么该声明就具有二义性:
MyClass<int,int> m; //ERROR:同时匹配MyClass<T,T>和MyClass<T,int>
MyClass<int*,int*> m; //ERROR:同时匹配MyClass<T,T>和MyClass<T1*,T2*>
为了解决例子2中二义性,你可以另外提供一个指向相同类型指针的特化:
template <typename T>
class MyClass<T*, T*>{
...
};
4.缺省模板实参
我们上面例子中的类模板Stack<>是通过C++标准库的vector<>来实现的;因此,我们不需要亲自实现内存管理、拷贝构造函数和赋值运算符。同时,类模板还可以为模板参数定义缺省值,这些值称为缺省模板实参。
#include <vector>
#include <stdexcept>
template <typename T, typename CONT = std::vector<T> >
class Stack
{
private:
CONT elems; //存储元素的容器
public:
void push(T const&); //压入元素
void pop(); //弹出元素
T top() const; //返回栈顶元素
bool empty() const { //返回栈是否为空
return elems.empty();
}
};
template <typename T, typename CONT>
void Stack<T, CONT>::push(T const& elem)
{
elems.push_back(elem);
}
template <typename T, typename CONT>
void Stack<T, CONT>::pop()
{
if (elems.empty())
{
throw std::out_of_range("Stack<T, CONT>::pop(): empty stack");
}
elems.pop_back(); //删除最后一个元素
}
template <typename T, typename CONT>
T Stack<T, CONT>::top() const
{
if (elems.empty())
{
throw std::out_of_range("Stack<T, CONT>::pop(): empty stack");
}
return elems.back(); //返回最后一个元素的拷贝
}
可以看到,上面的类模板有两个模板参数,因此每个成员函数的定义都必须具有这两个参数。我们仍然可以像前面的例子使用这个栈,就是说只传递第一个类型实参给这个类模板,将会利用vector来管理stack的元素。使用方式如下:
//int栈
Stack<int> intStack;
//double栈,使用std::deque来管理元素
Stack<double,std::deque<double> > dblStack;
参考:
《C++Templates》