模板元编程之类模板(二)
一、类模板Stack的实现
template <typename T> class Stack { private: std::vector<T> elems; //存储元素的容器 public: void push(T const&); //压入元素 T pop(); //弹出元素 T top() const; //返回栈顶元素 }; template <typename T> void Stack<T>::push(T const& elem) { elems.push_back(elem); //把elems的拷贝附加到末尾 } template <typename T> T Stack<T>::pop() { if (elems.empty()) { throw std::out_of_range("Stack<>::pop():empty stack"); } T elem = elems.back()
elems.pop_back(); return elem; } template <typename T> T Stack<T>::top() const { if (elems.empty) { throw std::out_of_range("Stack<>::pop():empty stack"); } return elems.back(); //返回最后一个元素的拷贝 }
二、类模板的声明
类模板的声明和函数模板的声明很类似:
template <typename T> class Stack { ... };
另外,可以再次使用关键字class来代替typename:
template <class T> class Stack { ... };
在类模板的内部,T可以像其他任何类型一样,用于声明成员变量和成员函数,在下面的示例中,T被用于声明vector的元素类型,声明push()是一个接收常量T引用为唯一实参的成员函数,声明top是返回类型为T的成员函数:
template <typename T> class Stack { private: std::vector<T> elems; //存储元素的容器 public: void push(T const&); //压入元素 void pop(); //弹出元素 T top() const; //返回栈顶元素 };
这个类的类型是Stack<T>,其中T是类模板参数,因为,在声明中需要使用该类的类型时,必须使用Stack<T>。例如,如果你要声明自己实现的拷贝构造函数和赋值运算符,那应该如下述示例:
template <typename T> class Stack { ... Stack(Stack<T> const&); //拷贝构造函数 Stack<T>& operator=(Stack<T> const&); //赋值运算符 };
但是,但使用类名而不是类的类型时,就应该只用Stack,比如,当指定类的名称,类的构造函数,析构函数时,就应该使用Stack。
为了定义类模板的成员函数,必须指定该成员函数是一个函数模板,而且还需要使用这个类模板的完整类型限定符,因为类型Stack<T>的成员函数push()的实现如下:
template <typename T> void Stack<T>::push(T const& elem) { elems.push_back(elem); //把elemd的拷贝附加到末尾 }
三、类模板Stack的使用
为了使用类模板对象,必须显式的指定模板实参,下面示例展示了如何使用类模板Stack<>:
int main() { Stack<int> intStack; //元素类型为int的栈 Stack<std::string> stringStack; //元素类型为字符串的栈 //使用int栈 intStack.push(10); std::cout << intStack.top() << std::endl; //使用string栈 stringStack.push("hello"); std::cout << stringStack.top() << std::endl; stringStack.pop(); stringStack.pop(); return 0; }
注意,只有那些被调用的成员函数,才会产生这些函数的实例化代码,对于类模板,成员函数只有在被使用的时候才会被实例化,,显然这样可以节省空间和时间。如果类模板中含有静态成员,那么用来实例化的每种类型,都会实例化这些静态成员。
四、类模板的特化
4.1 完全特化
可以使用模板实参来特化类模板,和函数模板的重载类似,通过特化类模板,可以优化基于某种特定类型的实现。或者客户某种特定类型在实例化类模板时所出现的不足,如果要实例化一个类模板,还要特化该类模板的所有成员函数。
template<> class Stack<std::string> { ...
// string类型的特化实现 };
4.2 局部特化
类模板也可以局部特化,可以在特定的环境下指定类模板的特定实现,并且要求某些模板参数仍然由用户来定义。
//局部特化:两个模板参数具有相同的类型 template <typename T> class MyClass<T, T> { ... }; //局部特化:两个模板参数的类型都是int template <typename T> class MyClass<T, int> { ... }; //局部特化:两个模板参数都是指针类型 template <typename T1, typename T2> class MyClass<T1*, T2*> { ... };
如果有多个局部特化同等程度的匹配某个声明,那么就称该声明具有二义性:
MyClass<int, int> m; //错误:同等程度的匹配MyClass<T, T>和MyClass<T, int> MyClass<int*, int*> m; //错误:同等程度的匹配MyClass<T, T>和MyClass<T*, T*>
为了解决上述的二义性,可以提供一个指向相同类型指针的特化:
template<typename T> class MyClass<T*, T*> { ... };
五、缺省模板实参
对于类模板,还可以为模板参数定义缺省值,这些值就被称为缺省模板实参,而且还可以应用之前的模板参数,例如在类stack<>中,可以把用于管理元素的容器定义为第二个模板参数,并且使用std::vector<>作为它的缺省值:
template <typename T, typename CONT = std::vector<T> > class Stack { private: CONT elems; public: void push(T const&); void pop(); }; template <typename T, typename CONT> void Stack<T, CONT>::push(T const& elem) { elems.push_back(); } 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(); }
从上述示例可以看到:类模板含有两个模板参数,因此每一个成员函数的定义都必须具有这两个参数,我们仍然可以像前面的示例一样使用这个stack,如果只传递第一个类型实参给这个类模板,那么将会利用vector来管理stack的元素。另外当在程序中声明stack对象的时候,还可以指定容器的类型:
int main() { //int栈 Stack<int> intStack; //double栈,它使用std::deque来管理元素 Stack<double, std::deque<double> > dblStack; return 0; }
六、总结
(1).类模板是具有如下性质的类:在类的实现中,可以有一个或多个类型还没有被指定
(2).为了使用类模板,可以传入某个具体类型作为模板参数;然后编译器将会基于该类型来实例化类模板
(3).对于类模板而言,只有那些被调用的成员函数才会被实例化
(4).可以用某种特定类型特化类模板
(5).可以用某种特定类型局部特化类模板
(6).可以为类模板的实参定义缺省值,这些值还可以引用之前的模板参数
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?