C/C++编程:类模板
以Stack为例学习类模板的使用
类模板Stack的实现
stack.hpp
#pragma once
#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();
}
/*
拷贝构造函数
赋值运算符
*/
//Stack(Stack<T>const &);
//Stack<T>& operator=(Stack<T>const &);
};
template<typename T>
void Stack<T>::push(const T & elem) {
elems.push_back(elem); // 将elem的拷贝添加到末尾
}
template<typename T>
void Stack<T>::pop() {
{
if (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<>
是通过C++标准库的类模板vector<>
来实现的:因此,我们不需要亲自实现内存管理、拷贝构造函数和赋值运算符,只需要把精力放在该类模板的接口上
类模板Stack的使用
#include <iostream>
#include <cstring>
#include <string>
#include "stack.h"
void foo(Stack<int>const &s){
Stack<int> isStack[10];
}
// 和上面效果一样
typedef Stack<int>IntStack ; // 起一个别名
void foo1(IntStack const &s){
IntStack intStack[10];
}
int main ()
{
try {
Stack<int> intStack;
Stack<std::string> stringStack;
intStack.push(7);
std::cout << intStack.top() << std::endl;
stringStack.push("hello");
std::cout << stringStack.top() << std::endl;
stringStack.pop();
stringStack.pop();
} catch (std::exception const& ex) {
std::cerr << "Exception: " << ex.what() << std::endl;
return EXIT_FAILURE; // exit program with ERROR status
}
}
- 对于类模板对象,会根据传参会实例化一个对象
- 对于类成员函数,只有被调用的时候才会被初始化
- 只要类型提供了被调用的所有操作,就可以用来初始化一个类模板对象
类模板的特化
可以使用模板实参来特化类模板。
- 和函数重载类似,通过特化类模板,可以优化基于某种特定类型的是实现,或者克服某种特定类型在实例化类模板时所出现的不足(比如该类型没有提供某些操作等)。
- 另外,如果要特化一个类模板,你还要特化该类模板的所有成员函数,虽然也可以只特化某个成员函数,但这个做法并没有特化整个类,也就没有特化整个类模板。
为了特化一个类模板,你必须在起始处声明一个template<>
,接下来声明用来特化类模板的类型。这个类型被用作模板实参,而且必须在类名的后面直接指定:
template<>
class Stack<std::string>{
...
}
进行类模板的特化时,每个成员函数都必须重新定义为普通函数,原来模板函数中的每个T也相应的被进行特化的类型取代:
void Stack<std::string>::push (std::string const& elem){
elems.push_back(elem);
}
下面stack2.hpp是一个用std::string特化Stack<>
的完整例子:
#pragma once
#include "stack.h"
#include <deque>
#include <string>
#include <stdexcept>
template<>
class Stack<std::string> {
private:
std::deque<std::string> elems; // elements
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>::top(): empty stack");
}
return elems.back();
}
在上面的例子中,我们使用了deque而不是vector来管理元素,可以看出,特化的实现可以和基本类模板的实现完全不同
使用方法和第一个一样,只是在使用Stack<std::string>
时会调用stack1.hpp
而不是stack.hpp
。当然Stack<int> intStack
还是调用stack.hpp
类模板特化时:
可以只实现某些特定的成员函数而不强制要求实现所有的成员函数(不推荐)
- 如果没有实现,那么在使用
Stack<std::string>
就只能调用已经实现的
- 如果没有实现,那么在使用
- 可以在原来的成员函数的基础上再扩展别的功能
当然,我们也可以使用每种特定类型局部特化类模板(待补)
局部特化
类模板可以被局部特化,你可以在特定的环境下指定类模板的实现,并且要求某些模板参数仍然必须由用户来定义,比如类模板:
template<typename T1, typename T2>
class MyClass{
...
};
就可以有下面几种局部特化:
// 局部特化1:两个模板参数具有相同的类型
template<typename T>
class MyClass<T, T>{
...
};
// 局部特化:第二个模板参数的类型时int
template<typename T>
class MyClas<T, int>{
...
};
//局部特化:两个模板参数都是指针类型
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; // 错误:同时匹配MyClass<T, T>和MyClass<T, int>
MyClass<int*, int*> m; // 错误:同时匹配MyClass<T, T>和MyClass<T, int>
为了解决第2种二义性,你可以提供一个指向相同类型指针的特化:
template<typename T>
class MyClass<T*, T*>{
...
};
缺省模板实参
对于类模板,我们可以为模板参数定义缺省值,这些值就被称为缺省模板实参;比如:在上面我们知道,对于Stack<>,我们可以用数组、队列等来管理元素。我们可以把用于管理元素的容器定义为第2个参数,并且使用std::vector<>
作为它的缺省值
#pragma once
#include <vector>
#include <stdexcept>
#include <vector>
#include <stdexcept>
template <typename T, typename CONT = std::vector<T> >
class Stack {
private:
CONT elems; // elements
public:
void push(T const&); // push element
void pop(); // pop element
T top() const; // return top element
bool empty() const {
// return whether the stack is empty
return elems.empty();
}
};
template <typename T, typename CONT>
void Stack<T,CONT>::push (T const& elem)
{
elems.push_back(elem); // append copy of passed elem
}
template <typename T, typename CONT>
void Stack<T,CONT>::pop ()
{
if (elems.empty()) {
throw std::out_of_range("Stack<>::pop(): empty stack");
}
elems.pop_back(); // remove last element
}
template <typename T, typename CONT>
T Stack<T,CONT>::top () const
{
if (elems.empty()) {
throw std::out_of_range("Stack<>::top(): empty stack");
}
return elems.back(); // return copy of last element
}
使用
#include <iostream>
#include <cstring>
#include <string>
#include <deque>
#include "stack2.h"
int main ()
{
try {
Stack<int> intStack;
// 指定容器的类型
Stack<double,std::deque<double> > dblStack;
intStack.push(1);
dblStack.push(42.42);
std::cout << dblStack.top() << std::endl;
dblStack.pop();
dblStack.pop();
}
catch (std::exception const& ex) {
std::cerr << "Exception: " << ex.what() << std::endl;
return EXIT_FAILURE; // exit program with ERROR status
}
}
总结
- 类模板是具有一些性质的类:在类的实现中,可以有一个或者多个类型还没有被指定
- 为了使用类模板,我们可以传递某个具体类型作为模板实参,然后编译器将会基于该类型来实例化模板
- 对于类模板而言,只有被调用的成员函数才会被实例化
- 可以使用某种特定类型特例化或者局部特例化模板
- 可以为类模板的参数定义缺省值
还没有评论,来说两句吧...