C/C++编程:类模板

- 日理万妓 2022-12-25 04:53 270阅读 0赞

以Stack为例学习类模板的使用

类模板Stack的实现

stack.hpp

  1. #pragma once
  2. #include <vector>
  3. #include <stdexcept>
  4. template<typename T>
  5. class Stack{
  6. private:
  7. std::vector<T> elems; // 存储元素的容器
  8. public:
  9. void push(T const &); // 压入元素
  10. void pop(); // 弹出元素
  11. T top() const ; // 返回栈顶元素
  12. bool empty() const {
  13. // 返回栈是否为空
  14. return elems.empty();
  15. }
  16. /*
  17. 拷贝构造函数
  18. 赋值运算符
  19. */
  20. //Stack(Stack<T>const &);
  21. //Stack<T>& operator=(Stack<T>const &);
  22. };
  23. template<typename T>
  24. void Stack<T>::push(const T & elem) {
  25. elems.push_back(elem); // 将elem的拷贝添加到末尾
  26. }
  27. template<typename T>
  28. void Stack<T>::pop() {
  29. {
  30. if (empty()){
  31. throw std::out_of_range("Stack<>::pop(): empty stack");
  32. }
  33. elems.pop_back(); // 删除最后一个元素
  34. }}
  35. template <typename T>
  36. T Stack<T>::top () const
  37. {
  38. if (elems.empty()) {
  39. throw std::out_of_range("Stack<>::top(): empty stack");
  40. }
  41. return elems.back(); // 返回最后一个元素的拷贝
  42. }

可以看出,类模板Stack<>是通过C++标准库的类模板vector<>来实现的:因此,我们不需要亲自实现内存管理、拷贝构造函数和赋值运算符,只需要把精力放在该类模板的接口上

类模板Stack的使用

  1. #include <iostream>
  2. #include <cstring>
  3. #include <string>
  4. #include "stack.h"
  5. void foo(Stack<int>const &s){
  6. Stack<int> isStack[10];
  7. }
  8. // 和上面效果一样
  9. typedef Stack<int>IntStack ; // 起一个别名
  10. void foo1(IntStack const &s){
  11. IntStack intStack[10];
  12. }
  13. int main ()
  14. {
  15. try {
  16. Stack<int> intStack;
  17. Stack<std::string> stringStack;
  18. intStack.push(7);
  19. std::cout << intStack.top() << std::endl;
  20. stringStack.push("hello");
  21. std::cout << stringStack.top() << std::endl;
  22. stringStack.pop();
  23. stringStack.pop();
  24. } catch (std::exception const& ex) {
  25. std::cerr << "Exception: " << ex.what() << std::endl;
  26. return EXIT_FAILURE; // exit program with ERROR status
  27. }
  28. }
  • 对于类模板对象,会根据传参会实例化一个对象
  • 对于类成员函数,只有被调用的时候才会被初始化
  • 只要类型提供了被调用的所有操作,就可以用来初始化一个类模板对象

类模板的特化

可以使用模板实参来特化类模板。

  • 和函数重载类似,通过特化类模板,可以优化基于某种特定类型的是实现,或者克服某种特定类型在实例化类模板时所出现的不足(比如该类型没有提供某些操作等)。
  • 另外,如果要特化一个类模板,你还要特化该类模板的所有成员函数,虽然也可以只特化某个成员函数,但这个做法并没有特化整个类,也就没有特化整个类模板。

为了特化一个类模板,你必须在起始处声明一个template<>,接下来声明用来特化类模板的类型。这个类型被用作模板实参,而且必须在类名的后面直接指定:

  1. template<>
  2. class Stack<std::string>{
  3. ...
  4. }

进行类模板的特化时,每个成员函数都必须重新定义为普通函数,原来模板函数中的每个T也相应的被进行特化的类型取代:

  1. void Stack<std::string>::push (std::string const& elem){
  2. elems.push_back(elem);
  3. }

下面stack2.hpp是一个用std::string特化Stack<>的完整例子:

  1. #pragma once
  2. #include "stack.h"
  3. #include <deque>
  4. #include <string>
  5. #include <stdexcept>
  6. template<>
  7. class Stack<std::string> {
  8. private:
  9. std::deque<std::string> elems; // elements
  10. public:
  11. void push(std::string const&);
  12. void pop();
  13. std::string top() const;
  14. bool empty() const {
  15. return elems.empty();
  16. }
  17. };
  18. void Stack<std::string>::push (std::string const& elem){
  19. elems.push_back(elem);
  20. }
  21. void Stack<std::string>::pop ()
  22. {
  23. if (elems.empty()) {
  24. throw std::out_of_range("Stack<std::string>::pop(): empty stack");
  25. }
  26. elems.pop_back(); // 删除末端元素
  27. }
  28. std::string Stack<std::string>::top () const
  29. {
  30. if (elems.empty()) {
  31. throw std::out_of_range("Stack<std::string>::top(): empty stack");
  32. }
  33. return elems.back();
  34. }

在上面的例子中,我们使用了deque而不是vector来管理元素,可以看出,特化的实现可以和基本类模板的实现完全不同

使用方法和第一个一样,只是在使用Stack<std::string>时会调用stack1.hpp而不是stack.hpp。当然Stack<int> intStack还是调用stack.hpp

类模板特化时:

  • 可以只实现某些特定的成员函数而不强制要求实现所有的成员函数(不推荐)

    • 如果没有实现,那么在使用Stack<std::string>就只能调用已经实现的
  • 可以在原来的成员函数的基础上再扩展别的功能

当然,我们也可以使用每种特定类型局部特化类模板(待补)

局部特化

类模板可以被局部特化,你可以在特定的环境下指定类模板的实现,并且要求某些模板参数仍然必须由用户来定义,比如类模板:

  1. template<typename T1, typename T2>
  2. class MyClass{
  3. ...
  4. };

就可以有下面几种局部特化:

  1. // 局部特化1:两个模板参数具有相同的类型
  2. template<typename T>
  3. class MyClass<T, T>{
  4. ...
  5. };
  6. // 局部特化:第二个模板参数的类型时int
  7. template<typename T>
  8. class MyClas<T, int>{
  9. ...
  10. };
  11. //局部特化:两个模板参数都是指针类型
  12. template<typename T1, typename T2>
  13. class MyClass<T1*, T2*>{
  14. ...
  15. }

下面的例子展示各种声明会使用哪个模板:

  1. MyClass<int, float> mif; // MyClass<T1, T2>
  2. MyClass<float, float> mff; // MyClass<T, T>
  3. MyClass<float, int> mfi; // MyClass<T, int>
  4. MyClass<int*, float*> mp; //MyClass<T1*, T2*>

如果有多个局部特化同等程度的匹配某个声明,那么就称该声明具有二义性:

  1. MyClass<int, int> m; // 错误:同时匹配MyClass<T, T>和MyClass<T, int>
  2. MyClass<int*, int*> m; // 错误:同时匹配MyClass<T, T>和MyClass<T, int>

为了解决第2种二义性,你可以提供一个指向相同类型指针的特化:

  1. template<typename T>
  2. class MyClass<T*, T*>{
  3. ...
  4. };

缺省模板实参

对于类模板,我们可以为模板参数定义缺省值,这些值就被称为缺省模板实参;比如:在上面我们知道,对于Stack<>,我们可以用数组、队列等来管理元素。我们可以把用于管理元素的容器定义为第2个参数,并且使用std::vector<>作为它的缺省值

  1. #pragma once
  2. #include <vector>
  3. #include <stdexcept>
  4. #include <vector>
  5. #include <stdexcept>
  6. template <typename T, typename CONT = std::vector<T> >
  7. class Stack {
  8. private:
  9. CONT elems; // elements
  10. public:
  11. void push(T const&); // push element
  12. void pop(); // pop element
  13. T top() const; // return top element
  14. bool empty() const {
  15. // return whether the stack is empty
  16. return elems.empty();
  17. }
  18. };
  19. template <typename T, typename CONT>
  20. void Stack<T,CONT>::push (T const& elem)
  21. {
  22. elems.push_back(elem); // append copy of passed elem
  23. }
  24. template <typename T, typename CONT>
  25. void Stack<T,CONT>::pop ()
  26. {
  27. if (elems.empty()) {
  28. throw std::out_of_range("Stack<>::pop(): empty stack");
  29. }
  30. elems.pop_back(); // remove last element
  31. }
  32. template <typename T, typename CONT>
  33. T Stack<T,CONT>::top () const
  34. {
  35. if (elems.empty()) {
  36. throw std::out_of_range("Stack<>::top(): empty stack");
  37. }
  38. return elems.back(); // return copy of last element
  39. }

使用

  1. #include <iostream>
  2. #include <cstring>
  3. #include <string>
  4. #include <deque>
  5. #include "stack2.h"
  6. int main ()
  7. {
  8. try {
  9. Stack<int> intStack;
  10. // 指定容器的类型
  11. Stack<double,std::deque<double> > dblStack;
  12. intStack.push(1);
  13. dblStack.push(42.42);
  14. std::cout << dblStack.top() << std::endl;
  15. dblStack.pop();
  16. dblStack.pop();
  17. }
  18. catch (std::exception const& ex) {
  19. std::cerr << "Exception: " << ex.what() << std::endl;
  20. return EXIT_FAILURE; // exit program with ERROR status
  21. }
  22. }

总结

  • 类模板是具有一些性质的类:在类的实现中,可以有一个或者多个类型还没有被指定
  • 为了使用类模板,我们可以传递某个具体类型作为模板实参,然后编译器将会基于该类型来实例化模板
  • 对于类模板而言,只有被调用的成员函数才会被实例化
  • 可以使用某种特定类型特例化或者局部特例化模板
  • 可以为类模板的参数定义缺省值

发表评论

表情:
评论列表 (有 0 条评论,270人围观)

还没有评论,来说两句吧...

相关阅读