2.类模板与操作符重载

项目地址

C++类模板(1)

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

C++类模板(2)

  • 一个类模版的例子:<code>Stack<T></code>

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初始化

C++类模板(3)

  • 类模板的声明
  • 声明类模板与声明函数模板类似
  • 关键字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;};

C++类模板(4)

  • 类模板的声明(续)
  • 除了Copy constructor之外,如果在类模板中需要使用到这个类本身,比如定义operator=,那么应该使用其完整的定义(Stack<T>),而不是省略型别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
}

C++类模板(5)

  • 类模板的实现
  • 要定义一个类模板的成员函数,则要指明其是一个模板函数,例如,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);
}

C++类模板(6)

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

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!
}

C++类模板(7)

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

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

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

C++类模板(8)

  • 使用类模板
  • <code>Stack<int> stack:</code>定义了一个型别为int的Stack,大小为默认值
  • <code>Stack<int, 100> Stack:</code>定义了一个型别为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<Stack<int> > intStackStack
    Stack<Stack<int>> intStackStack; //ERROR:>> is not allowed

C++类模板(9)

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

C++类模板(10)

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

template <> class Stack<std::wstring> {

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<std::wstring> m_Members;//采用list作为Stack的内部实现,取代了主模板中用vector实现的方式
}

C++类模板(11)

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

C++类模板(12)

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

C++类模板(13)

  • 默认模板实参
  • 类似函数的默认参数,对于类模板而言也可以定义其模板参数的默认值,这些值就叫做默认模板实参
    template <typename T, typename TContainer = std::vector<T> >//使用std::vector作为默认实参
    class Stack {
    private: TContainer m_Container;
    ...
    };
  • Stack<int> intStack:使用默认的vector作为实参
  • Stack<std::wstring, std::list<std::wstring> > 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=,所有其他操作符重载均可以被子类继承
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容