与函数模板相似,类也可以被一种或多种类型参数化。
容器类是一个典型案例,通常用于管理和组织某种类型的元素。只要使用类模板,就可以实现容器类,而不需要确定容器中元素的类型。
我们以stack作为类模板的例子。
与函数模板的处理方式一样,在头文件中声明和定义类statck<>
#include
#include
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& a)
{
elems.push_back(a);
}
template<typename T>
void stack<T>::pop()
{
if(elems.empty()) {
throw std::out_of_range("stack<> pop(): empty stack");
}
elems.pop_back(); // 删除最后一个元素
}
template<typename T>
T stack<T>::top() const
{
if(elems.empty()) {
throw std::out_of_range("stack<>::top(): empty stack");
}
return elems.back(); // 返回最后一个元素的拷贝
}
这个类的类型是stack,T是模板参数; 类名是stack。
当你在声明中需要使用该类的类型时,就必须使用stack, 当需要使用类名时就应该是stack。
template<typename T>
class stack{
stack(stack<T> const&); // 拷贝构造,这里使用类型stack作为参数, 函数名是类名
stack(); // 构造函数
};
对于类模板的任何成员函数,你都可以把它实现为内联函数,将它定义于类声明里面。
template<typename T>
class stack{
...
void push(T const& elem) {
elems.push_back(elem);
}
...
};
为了使用类模板对象,必须显式地指定模板参数。下面例子展示如何使用类模板stack<>
#include
#include
#include
#include "stack.h" // 这里声明和定义stack的实现
int main()
{
try {
stack<int> intStack;
stack<std::string> stringStack;
// 使用init 栈
intStack.push(7);
std::cout<<intStack.top()<<std::endl;
// 使用string栈
stringStack.push("hello");
std::cout<<stringStack.top() << std::endl;
stringStack.pop();
stringStack.pop();
} catch(std::execption const& ex) {
std::cerr << "Exception: " << ex.what() << std::endl;
return EXIT_FAILURE;
}
}
通过声明类型stack, 在类模板内部就可以用int实例化T。对于所有被调用的成员函数,都会实例化出基于int类型的函数代码。类似stack, 将会创建一个stack对象,对于所有被调用的成员函数,也会实例化出基于std::string的函数代码。
void foo(stack<int> const& s)
{
stack<int> istack[10]; /// istack是含有10个栈的数组
}
借助于类型定义,你可以方便地使用类模板:
typedef stack<int> intStack;
void foo(intstack const& s)
{
intStack istack[10]; /// istack 是一个含有10个int栈的数组
....
}
C++的类型定义只是定义了一个类型别名,并没有定义一个新类型,是可以互相赋值。
模板实参可以是任何类型, eg:
stack floatStack; // 元素类型为浮点型指针的栈
stack > intStackStack; // 元素类型为int栈的栈
要求是该类型必须提供被调用的所有操作
用模板实参来特化类模板。和函数模板的重载类似,通过特化类模板,你可以优先基于某种特定类型的实现,或者克服某种特定类型在实例化模板时所出现的不足,比如该类型没有提供某种操作。
特化一个类模板,你还要特化该类模板的所有成员函数。虽然也可以只特化某个成员函数,但这个做法并没有特化整个类,也就是没有特化整个类模板。
为了特化一个类模板,必须在起始处声明一个template<>, 接下来声明用来特化类模板的类型。这个类型被用作模板实参,且必须在类名的后面直接指定:
template<>
class stack<std::string> {
...
};
进行类模板的特化时,每个成员函数都必须重新定义为普通函数,原来模板函数中的每个T也相应地被进行特化的类型取代:
void stack<std::string>::push(std::string const& elem)
{
elems.push_back(elem);
}
用std::string特化stack<>
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::pop(): empty stack" );
}
elems.pop();
}
std::string stack<std::string>::top()const
{
if (elems.empty()) {
throw std::out_of_range("stack::top(): empty stack" );
}
return elems.back();
}
我们使用deque而不是vector来管理stack内部的元素,目的是为了说明一个特点:特化的实现可以和基本类模板的实现完全不同。
类模板可以被局部特化。你可以在特定环境下指定类模板的特定类型,并且要求某些模板参数仍然必须由用户来定义。
譬如下面我们给类模板:
template<typename T1, typename T2>
class Myclass {
...
};
然后我们看看几种局部特化:
// 两个模板参数具有相同的类型
template<typename T>
class Myclass<T, T> {
...
};
// 第二个模板参数的类型是int
template<typename T, int>
class Myclass<T, int> {
....
};
template<typename T1, typename T2>
class Myclass<T1*, T2*> {
...
};
然后我们看一下,下面各种声明使用哪个模板:
Myclass<int, float> mif; // 使用Myclass
Myclass<float, float> mff; // 使用Myclass
Myclass<float, int> mfi; //使用Myclass
Myclass<iint *, float*> mp; //使用Myclass
如果有多个局部特化同等程度地匹配某个声明,那么就称为声明具有二义性:
Myclass m; // 错误,同等程度地匹配Myclass和Myclass
Myclass m; // 错误,同等程度地匹配Myclass和Myclass
为了解决二义性,你可以另外提供一个指向相同类型指针的特化:
template
class Myclass {};
类模板还可以定义缺省值;这些值就被称为缺省模板实参;而且,它们还可以引用之前的模板参数。例如类stack<>中,可以把用于管理元素的容器定义为第二个模板参数,并且使用std::vector<>作为它的缺省值:
#include
#include
template<typename T, typename COUNT = std::vector<T> >
class stack {
private:
COUNT elems; // 包含元素的容器
public:
void push(T const&);
void pop();
T top() const;
bool empty() const {
return elems.empty();
}
};
template<typename T, typename COUNT>
void stack<T, COUNT>::push(T const& elem)
{
elems.push_back(elem);
}
template<typename T, typename COUNT>
void stack<T, COUNT>::pop()
{
if (elems.empty()) {
throw std::out_of_range("stack<>::pop(): empty stack");
}
elems.pop_back();
}
template<typename T, typename COUNT>
T stack<T, COUNT>::top() const
{
if (elems.empty()) {
throw std::out_of_range("stack<>::top(): empty stack");
}
return elems.back();
}
类模板含有两个模板参数,因此每个成员函数的定义都必须具有这两个参数:
template<typename T, typename COUNT>
void stack<T, COUNT>::push(T const& elem)
{
elems.push_back(elem);
}
你仍然可以像前面例子一样使用这个栈,只是说,你只传递一个类型实参给这个类模板,将使用缺省的vector来管理栈元素。
下面我们看一下在程序中使用stack对象,指定容器类型:
#include
#include
#include
#include "stack3.h"
int main()
{
stack<int> intStack; // int栈
stack<double, std::deque<double> > dblStack; // double栈,使用std::deque管理元素
}