从零开始学C++之模板(三):缺省模板参数(借助标准模板容器实现Stack模板)、成员模板、关键字typename

一、缺省模板参数

回顾前面的文章,都是自己管理stack的内存,无论是链栈还是数组栈,能否借助标准模板容器管理呢?答案是肯定的,只需要多传一个模板参数即可,而且模板参数还可以是缺省的,如下:

template <typename T, typename CONT = std::deque<T> > class Stack { …

private:

    CONT c_; };

如果没有传第二个参数,默认为deque 双端队列,当然我们也可以传递std::vector<T>

下面程序借助标准模板容器管理内存来实现stack模板类:

Stack.h:

#ifndef _STACK_H_
#define _STACK_H_

#include <exception>
#include <deque>
using namespace std;

template <typename T, typename CONT = deque<T> >
class Stack
{
public:
    Stack() : c_()
    {
    }
    ~Stack()
    {
    }

    void Push(const T &elem)
    {
        c_.push_back(elem);
    }
    void Pop()
    {
        c_.pop_back();
    }
    T &Top()
    {
        return c_.back();
    }
    const T &Top() const
    {
        return c_.back();
    }
    bool Empty() const
    {
        return c_.empty();
    }
private:
    CONT c_;
};

#endif // _STACK_H_

main.cpp:

#include "Stack.h"
#include <iostream>
#include <vector>
using namespace std;


int main(void)
{
    /*Stack<int> s;*/
    Stack<int, vector<int> > s;
    s.Push(1);
    s.Push(2);
    s.Push(3);

    while (!s.Empty())
    {
        cout << s.Top() << endl;
        s.Pop();
    }
    return 0;
}

输出为 3 2 1

即如果没有传递第二个参数,堆栈和压栈等操作直接调用deque<int> 的成员函数,也由deque<int> 管理内存。

如程序中传递vector<int> ,则由vector<int> 成员函数处理。

二、成员模板

来看下面的例子:

#include <iostream>
using namespace std;


template <typename T>
class MyClass
{
private:
    T value;
public:
    void Assign(const MyClass<T> &x)
    {
        value = x.value;
    }
};

int main(void)
{
    MyClass<double> d;
    MyClass<int> i;

    d.Assign(d);        // OK
    d.Assign(i);        // Error
    return 0;
}

因为i 和 d 的类型不同,故会编译出错。可以用成员模板的方法解决:

#include <iostream>
using namespace std;

template <typename T>
class MyClass
{
private:
    T value;
public:
    MyClass() {}
    template <class X>
    MyClass(const MyClass<X> &x) : value(x.GetValue())
    {

    }
    template <class X>
    void Assign(const MyClass<X> &x)
    {
        value = x.GetValue();
    }
    T GetValue() const
    {
        return value;
    }
};

int main(void)
{
    MyClass<double> d;
    MyClass<int> i;
    d.Assign(d);        // OK
    d.Assign(i);        // OK

    MyClass<double> d2(i);

    return 0;
}

为了支持  MyClass<double> d2(i); 故也要将拷贝构造函数实现为成员模板函数,同理,如果想支持 d = i ; 也要将赋值运算符实现为成员模板。实际上auto_ptr<class> 中的实现就使用了成员模板,因为要支持类似下面的运算:

auto_ptr<X> x;

auto_ptr<Y> y;

x = y;

三、typename 关键字

看下面的例子:

#include <iostream>
using namespace std;

template <typename T>
class MyClass
{
private:
    typename T::SubType *ptr_;
};

class Test
{
public:
    typedef int SubType;
};
int main(void)
{
    MyClass<Test> mc;
    return 0;
}

typename T::SubType *ptr_; 如果前面没有typename 修饰,则SubType会被认为是T类型内部的静态数据成员,推导下去,* 就不再认为是指针,而被

认为是乘号,编译的时候就出错了。加上修饰,就知道SubType 是T 内部的自定义类型,ptr是指向这种类型的指针,编译通过。

在vector 的源码中也可以发现下面的一些片段:

template<> class _CRTIMP2_PURE allocator<void>
{
    // generic allocator for type void
public:
    template<class _Other>
    struct rebind
    {
        // convert an allocator<void> to an allocator <_Other>
        typedef allocator<_Other> other;
    };
    ....
}

typedef typename _Alloc::template rebind<_Ty>::other _Alty;

最后一行是类型定义,由于要解释_Alloc 类型需要引用的代码片段比较多,就姑且认为是allocator<int> 类型,可以看到 rebind<_Ty>

 是成员模板类,other是成员模板类中自定义类型,_Ty 可以认为是int , 那么other 类型也就是allocator<int>, 也就是说_Alty 是类型

allocator<int> 。

此外还可以看到 :

template<class _Ty> class allocator

 { 

template<> class _CRTIMP2_PURE allocator<void>  {  };

};

也就是说allocator<void> 是allocator 模板类的特化。

四、派生类与模板、面向对象与泛型编程

(一)、派生类与模板

1、为了运行的效率,类模板是相互独立的,即独立设计,没有使用继承的思想。对类模板的扩展是采用适配器(adapter)来完成的。通用性是模板库的设计出发点之一,这是由泛型算法(algorithm)和函数对象(functor)等手段达到的。

2、派生的目标之一也是代码的复用和程序的通用性,最典型的就是MFC,派生类的优点是可以由简到繁,逐步深入,程序编制过程中可以充分利用前面的工作,一步步完成一个复杂的任务。

3、模板追求的是运行效率,而派生追求的是编程的效率。

(二)、面向对象与泛型编程

1、面向对象与泛型都依赖于某个形式的多态

面向对象

动态多态(虚函数)

泛型

静态多态(模板类,模板函数)

2、面向对象中的多态在运行时应用存在继承关系。我们编写使用这些类的代码,忽略基类与派生类之间的类型差异。只要使用基类指针或者引用,基类类型对象、派生类类型对象就可以共享相同的代码。

3、在泛型编程中,我们所编写的类和函数能够多态地用于编译时不相关的类型。一个类或一个函数可以用来操纵多种类型的对象。

参考:

C++ primer 第四版 Effective C++ 3rd C++编程规范

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android相关

Kotlin---解构与委托

通常,函数的返回值中只能返回一个对象,而我们可能要返回很多的数据。此时就可以用到解构

593
来自专栏iOS开发随笔

iOS Swift基础语法(二)

953
来自专栏我是攻城师

关于Java里面的嵌套类,你了解多少?

最近在看《Core Java for the Impatient》这本书,当然为了方便我看的是英文电子版的PDF格式(有需要的朋友,可以后台留言给我),期间又重...

1544
来自专栏老九学堂

原来C语言和其他高级语言的最大的区别是这个...

我们需要知道——变量,其实是内存地址的一个抽像名字罢了。在静态编译的程序中,所有的变量名都会在编译时被转成内存地址。机器是不知道我们取的名字的,只知道地址。

933
来自专栏开发与安全

从零开始学C++之虚函数与多态(一):虚函数表指针、虚析构函数、object slicing与虚函数、C++对象模型图

一、多态 多态性是面向对象程序设计的重要特征之一。 多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为。 多态的实现: 函数重载 运算符...

1840
来自专栏海说

深入理解计算机系统(3.3)---数据传送(或者说复制)指令详解

  上一章我们已经介绍了汇编语言的基础部分,包括数据格式、寄存器以及操作数的标识方式,接下来我们就应该去认识一下汇编语言当中的各个指令了。这些指令大多数都非常简...

915
来自专栏余林丰

虚拟机类加载机制(2)——类加载器

《深入理解Java虚拟机》一书中将类的加载过程放到了类加载器前面一节,但在这里我想先讲“类加载器”。在上一篇类加载时机中我们用大量篇幅来讲解了类加载过程中的5个...

1966
来自专栏Celebi的专栏

C/C++ 学习笔记三(函数)

函数在编程语言中可谓“头等公民”,理解函数的实现原理,函数的一些方法论对于编程非常有好处。 我将从函数的实现原理以及编写函数的一些建议两个的角度来重新认识一下C...

830
来自专栏本立2道生

实例分析C程序运行时的内存结构

这段代码包含两个函数,因此可以测试函数调用,此外还包含了静态变量、局部变量、返回值等

701
来自专栏GreenLeaves

C# 委托基础

不知不觉,又开始委托的学习了,感觉这个东西我能学一辈子,哈哈哈!这次看的是官方的参考书(C#高编9),每次看不同的资料,总能学到不同的知识!言归正传 1、为什么...

1825

扫码关注云+社区