类模板与操作符重载

简单了解模板观念

C++类模板

  • 与函数模板类似, 类页可以通过参数泛化,从而可以构建出一族不同类型的类实例
  • 类模板参数可以是某一类型或常量(仅限int或者enum)

一个类模板的例子:Stack

const std::size_t DefaultStackSize = 1024;

template <typename T, std::size_t n = DefaultStackSize>
class Stack {
public:
    void Push(const T const& element);
    int Pop(T& element);
    int Top(T& element) const;
private:
    std::vector<T> m_Members;
    std::size_t m_nMaxSize = n;
};

  • T可以是任意类别
  • 模板实参也可以是一个int或enum的常量(此处是size_t, 本质是int类型)
  • n是编译时定义的常量
  • n可以有默认值
  • size_t类别的成员变量可以用n初始化

- 类模板的声明

  • 声明类模板与申明函数模板类似
  • 关键字class和typename都可以用,但是还是更倾向于去使用typename

    template <typename T, std::size_t n> class Stack{...}
    template <class T, std::size_t n> class Stack{...}
  • 在类模板内部,T可以像其他函数类别一样(比如int , char等)定义成员变量和成员函数

    void Push(const T const& element)
    int Pop(T& element);
    int Top(T& element) const;
    std::vector<T> m_Members;
  • 除了Copy constructor 之外,如果在类模板中需要使用到这个类本身,比如定义operator,那么就应该医用其完整的定义(Stack)而不是省略类别T。
    template <typename T, std::size_t n>
    class Stack{
      public:
          ...
          Stack (Stack<T,n> const&); //copy constructor
          Stack<T>& operator = (Stack<T,n> const&); // assignment operator
          ...
    }

- 类模板的实现

  • 要定义一个类模板的成员函数,则要指明其是一个模板函数

    Push函数:

    template <typename T, std::size_t nMaxSize>
    void Stack<T, nMaxSize> :: Push(const T const& element) {
        if (m_Members.size() >= m_nMaxSize) {
            // error handing...
            return ;
        }
    
        m_Members.push_back(element);
    }

    Pop函数:

    template <typename T, std :: size_t nMaxSize>
    int Stack<T, nMaxSize> :: Pop(T& element) {
        if (m_Members.empty()) {
            return 0;
        }
    
        element = m_Members.back(); // we have to first store the back element
        m_Members.pop_back(); // because pop_back of a vector removes
        return 1; // the last element but doesn't return it
    }

    Top函数:

    template <typename T, std :: size_t nMaxSize>
    int Stack<T, nMaxSize> :: Top(T& element) const {
        if (m_Members.empty()) {
            return 0;
        }
    
        element = m_Members.back();
        return 1;
    }

- 使用类模板

  • Stack stack:定义了一类型为int的Stack, 大小为默认值
  • Stack stack:定义了一个类型为int, 大小为100的Stack
  • 将100个元素Push到Stack中
    for (int i = 0; i < 100; ++ i) {
        stack.Push(i);
    }
  • Pop出Stack顶部元素:
    int element
    stack.Pop(element);
  • 获取Stack顶部元素:
    stack.Top(element);
  • Stack的stack定义:
    Stack<Stack<int> >intStackStack; // 最右边要加一个空格
    
    Stack<Stack<int>> intStackStack; // ERROR: >> is not allowed 

- 类模板特化(specializations)

  • 允许对一个类模板的某些模板参数类型做特化
  • 特化的作用或好处在于:
    • 对于某种特殊的类别,可能可以做些特别的优化或提供不同的实现
    • 避免在实例化类的时候引起一些可能产生的诡异行为
  • 特化一个类模板的时候也意味着需要特化其所有参数的成员的类型
  • 如果要特化一个类,那么做法为:
    • 声明一个带template<>的类, 即空参数列表
    • 在类名后紧跟的尖括号中显式指明类别,例如:
      template<>
      class Stack<std::wstring> {
      
      }
  • 特化后的具体体现可以和柱模板的实现不一样,比如以下的特化增加了一个成员函数,并采用list作元素存取的实现
    template <>
    class Stack <std :: wstring> {
      public:
          void SetStackStack(const std :: size_t n) {m_nMaxSize = n;}
          // 添加一个新的成员函数
    
          std :: size_t CurrentSize() const {return m_Members.size();}
    
          void Push(const std :: wstring const& element);
          int Pop(std :: wstring const& element);
          int Top(std :: wstring const& element) const;
      private:
          std :: size_t m_nMaxSize;
          std :: list <std :: wstring> m_Members;
          //采用list作为Stack的内部实现,取代了主模板中用vector实现的方式
    };

- 偏特化(Partial specialization)

….不做了解,中间会产生二义性…晚点再说。

- 默认模板实参

  • 类似函数的默认参数,对于类模板而言也可以定义其模板参数的默认值,这些值就叫做默认模板参数
    template <typename T, typename TContainer = std :: vector<T>>
    class Stack {
        private : TContainer m_Container;
    }
  • Stack intStack: 使用默认的vector作为实参
  • Stack > wstrStack: 指定使用list作为容器而非默认的vector

C++操作符重载

  • 关键字operator定义了一种特殊的函数,该函数的行为是将操作符应用用于某一特定的类型,使之能能够通过该操作符进行操作。如果定义了string类型的operator + ,那么连接两个字符串a和b的行为就可以用a+b进行操作
  • 操作符重载给出了操作符的不同含义
  • 编译器通过具体类别来识别某个操作符在该类型上的意义
  • 本质上operator重载就是函数,即如果定义了string类型的Append函数,那么string类型的a+b和a.Append(b)是等价的

- 操作符重载的一般规则

  • 不可以用operator定义一种新的操作符,比如* ,因为没有两个的操作符
  • 对于内置类别(built-in type), 不能再用operator重载
  • 操作符重载的两种情况:
    • 静态成员函数
    • 静态全局函数(如果该全集函数需要访问类的private或protected成员,则必须声明为friend成员)
      class ComplexType {
          public: 
            //non-static member
            ComplexType operator < (ComplexType& );
      
            //global functions
            friend ComplexType operator + (int, ComplexType& );
      }
    • 一元操作符(Unary operators)如果声明为成员函数,则没有参数。如果声明为全局函数则有一个参数
    • 二元操作符(Binary operator)如果声明为成员函数,则有一个参数。如果声明为全局函数,则有两个参数
    • 如果一个操作符技能够用作一元操作,又能用作二元操作(&, *, +, -),则可以分别被重载
    • 操作符不能带有默认参数
    • 除了operator = ,所又其他操作符重载均可以被子类继承

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!