2.类模板与操作符重载

项目地址

C++类模板(1)

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

C++类模板(2)

  • 一个类模版的例子:Stack

const std::size_t DefaultStackSize = 1024;
template  class Stack {

public:
    void Push(const T const& element);
    int Pop(T& element);
    int Top(T& element) const;

private:
    std::vector m_Members;
    std::size_t m_nMaxSize = n;
};

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

C++类模板(3)

  • 类模板的声明
  • 声明类模板与声明函数模板类似
  • 关键字class和typename都可以用,但还是倾向于使用typename
    template class Stack {...};
    template class Stack {...};
  • 在类模板内部,T可以像其他型别一样(比如int,char等)定义成员变量和成员函数
    void Push(const T const& element);
    int Pop(T& element);
    int Top(T& element) const;
    std::vector m_Members;};

C++类模板(4)

  • 类模板的声明(续)
  • 除了Copy constructor之外,如果在类模板中需要使用到这个类本身,比如定义operator=,那么应该使用其完整的定义(Stack),而不是省略型别T。如下面的例子所示:

template  class Stack
{
public:
    ...
    Stack(Stack const&);                //copy constructor
    Stack& operator = (Stack const&); //assignment operator
}

C++类模板(5)

  • 类模板的实现
  • 要定义一个类模板的成员函数,则要指明其是一个模板函数,例如,Push函数的定义应当如下:

template 
void Stack::Push(const T const& element)
{
    if(m_Members.size() >= m_nMaxSize) {
        //error handing...
        return;
    }

    m_Members.push_back(element);
}

C++类模板(6)

  • 类模板的实现(续)
  • Pop函数:从Stack中弹出顶部元素

template 
int Stack::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!
}

C++类模板(7)

  • 类模板的实现(续)
  • GetTop函数:获取Stack顶部元素,但没有Pop出该元素

template 
int Stack::GetTop(T& element) const
{
    if (m_Members.empty())
        return 0;

    element = m_Members.back();
    return 1;
}

C++类模板(8)

  • 使用类模板
  • 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.GetTop(element);
  • Stack的Stack定义:
    Stack > intStackStack
    Stack> intStackStack; //ERROR:>> is not allowed

C++类模板(9)

  • 类模板特化(specializations)
  • 允许对一个类模板的某些模板参数型别做特化
  • 特化的作用或好处在于:
  • 对于某种特殊的型别,可能可以做些特别的优化或提供不同的实现
  • 避免在实例化类的时候引起一些可能产生的诡异行为
  • 特化一个类模板的时候也意味着需要特化其所有参数化的成员函数
  • 如果要特化一个类,那么做法是:
  • 声明一个带template<>的类,即空参数列表
  • 在类名称后面紧跟的尖括号中显式指明型别,例如:
    template<>
    class Stack {
    ...
    }

C++类模板(10)

  • 类模板特化(specializations)(续)
  • 特化后的具体实现可以和主模板的实现不一样,比如一下的特化增加了一个成员函数,并采用list作为元素存取的实现

template <> class Stack {

public:
    void SetStackSize(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& element);
    int GetTop(std::wstring& element) const;

private:
    std::size_t m_nMaxSize;
    std::list m_Members;//采用list作为Stack的内部实现,取代了主模板中用vector实现的方式
}

C++类模板(11)

  • 偏特化(Partial specialization)
  • 类模板也可以被偏特化,比如主模板如果定义为:
    template class MyClass {...}; //Primary————1
  • 可能产生以下几种对于主模板的偏特化:
    • 将模板参数偏特化为同样型别:
      template class MyClass {...}; ————2
    • 将第二个模板参数偏特化为int型别,不再是泛型的T
      template class MyClass {...}; ————3
    • 将两个型别偏特化为指针:
      template class MyClass, T2> {...}; ————4

C++类模板(12)

  • 偏特化(Partial specialization)(续)
  • 使用示例:
使用 原型
MyClass obj; MyClass ————1
MyClass MyClass ————2
MyClass MyClass ————3
MyClass, float> MyClass, T2> ————4
  • 如果有不止一个偏特化同等程度地能够匹配某个调用,那么该调用具有二义性。编译器不会通过编译:
Usage Prototype
MyClass obj; ERROR:matches MyClass and MyClass
MyClass, int> obj; ERROR:matches MyClass and MyClass, T2>

C++类模板(13)

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

C++类模板(14)

  • 总结
  • 模板类的性质是,有一个或多个型别未被指定
  • 要使用一个模板类,就传入具体的型别作为实参;编译器会基于该型别来实例化类模板
  • 对于类模板而言,只有被调用到的成员函数才会被实例化
  • 类模板可以用特定的型别特化(specialization)
  • 类模板也可以用特定的型别偏特化(partial specialization)
  • 类模板参数可以有默认值

C++操作符重载(1)

  • 关键字operator定义了一种特殊的函数,该函数的行为是将操作符应用于某一特定的型别,使之能够通过该操作符进行操作。比如,如果定义了string型别的operator+,那么连接两个字符串a和b的行为就可以用a+b进行操作
  • 操作符重载给出了操作符的不同函数
  • 编译器通过具体型别来识别某个操作符在该型别上的意义
  • 本质上operator重载就是函数,即如果定义了string型别的Append函数,那么string型别的a+b和a.Append(b)是等价的
  • 大多数内置的操作符支持重载,比如:!,!=,%,%=,&,&=,&&,||,(),,=,+,+=,-,-=,/,/=,,=,|,|=,<,<=,>,>=,=,==,<<,<<=,>>,>>=,~,[],new,delete

C++操作符重载(2)

  • 操作符重载的一般规则
  • 不可以用operator定义的一种新的操作符,比如**
  • 对于内置型别(built-in type),不能再用operator重载
  • 操作符重载的两种情况:
    • 非静态成员函数,或
    • 静态全局函数(如果该全局函数需要访问类的private或protected成员,则须声明为friend成员)

class ComplexType {
public:
    //non-static member
    ComplexType operator<(ComplexType&);

    //global functions
    friend ComplexType operator+(int, ComplexType&);
}

``
# C++操作符重载(3)

- 操作符重载的一般规则(续)
 - 一元操作符(Unary operator)如果声明为成员函数,则没有参数;如果声明为全局函数则有一个参数
 - 二元操作符(Binary operator)如果声明为成员函数,则有一个参数;如果声明为全局啊还是农户,则有两个参数
 - 如果一个操作符既能够用作一元操作,又能够用作二元操作(比如:&,*,+,-),则可以分别被重载
 - 操作符重载不能带有默认参数值
 - 除了operator=,所有其他操作符重载均可以被子类继承

你可能感兴趣的:(2.类模板与操作符重载)