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