StupidBeauty
Read times:3320Posted at:Sun May 28 11:07:30 2017 - no title specified

《C++面向对象软件设计及构建》文章翻译:7.1模板,7.1 Templates

在若干种常见的编程场景中,需要对于多种不同的数据类型用上相同的类结构。以下列举出这种场景的例子:

队列类

  • 由用户输入的字符组成的队列

  • 已发生且等待被处理的鼠标事件组成的队列

列表类

  • 屏幕上当前显示的窗口组成的列表

  • 菜单中的菜单项组成的列表

集合类

  • 由一个或多个按钮组成的集合

  • 当前可用的画笔或颜色组成的集合

在每种示例中,所需要的是相同的基本算法及对应的数据结构。在使用这种类的过程中,所发生变化的东西就是,具体被操作的数据的类型。

作为对于这些场景进行处理的一个好的机制,应当避免编写重复的代码,并且,保留一个良好设计的类中所具有的类型安全性。避免编写重复的代码,就意味着,当两个队列的区别 仅仅 是其中包含的数据类型不同的话,没必要将MouseEventQueue 和CharacterQueue 类实现为各自独立的类。保留类型安全性,意味着,我们不希望为了避免编写重复的代码而写出过于通用(对数据类型完全一无所知)的类。例如,我们在技术上能够实现一个单一的Queue 类,它操作的数据类型是"void*",但是,这种设计就是过于通用了,因为,它将允许任何东西都能够被放置到队列中(例如,将MouseEvent放置到CharacterQueue中)。

模板,是一个带参数的类,它的参数就指明了具体发生变化的数据类型。C++中,用于声明一个队列类的模板的语法是:

template < class QueueItem > class Queue { ... }

在关键字template 后面,以尖括号包围起来的部分,就是模板参数。在这个示例中,所用到的模板参数,QueueItem,是一个类。对于Queue 来说,QueueItem就是用来指明,这个Queue 中要容纳的条目的类型。在参数化的类(Queue)中,可按照以下一种或多种方式来使用模板参数:用来定义参数化类中某个方法的参数类型;用来定义参数化类中某个方法返回的数据类型;或者,用来定义参数 化类中定义的某个本地数据的数据类型。在下面示例的Queue 类中,展示了上述各种模板参数的用法。

在下面展示的Queue 模板类代码中,首先用QueueItem来定义缓冲数组的数据类型,这个缓冲数组用来容纳队列中当前存在的元素。Insert 方法的参数,也被定义为QueueItem,这就与那个用来储存输入值的缓冲数组的声明一致。最后,由Remove 方法返回的结果,其类型也是QueueItem,因为,这正是从缓冲数组中取出的数据的类型。


Queue模板

template <class QueueItem> class Queue {

   private:

QueueItem buffer[100];

      int head, tail, count;

    public:

                Queue();

      void      Insert(QueueItem item);

QueueItem Remove();

               ~Queue();

   };

参数化类型(模板),可用来创建一个新的类,具体做法就是,使用适当的参数来将模板实例化。模板,可以用来以两种方式来创建新的类。第一种方式,就是直接创建一个对象,如下所示:

Queue<int> intQueue;               // 由整数对象组成的队列

在这个示例中,intQueue 这个对象,即是被定义为一个参数化类型Queue 的实例,其中,模板参数即为int 类型。第二种方式,利用类型定义(typedef)来定义一个类型(类)名字:

typedef Queue<int> IntegerQueue;  // 一个类,它是由整数组成的队列

IntegerQueue intQueue;

无论使用哪种方式,创建出来的那个对象,都是一个由整数组成的队列,并且对它的操作方式都是一样的。例如,整数队列可按照以下方式来测试:

intQueue.Insert(100);           // 加入100

intQueue.Insert(200);           // 加入200

int x = intQueue.Remove();      // 删除100

intQueue.Insert(300);           // 队列 中目前包含 (200,300)

int x = intQueue.Size();        // 尺寸 2

注意,对于intQueue 对象的访问方式,没有任何区别。利用实例化的模板创建的对象,用起来跟一个利用非参数化类创建的对象没有区别。

要完成模板类的实现的话,它的每个方法都必须被定义。在模板方法的语法中,必须带上一个前置声明,用来将它与模板及模板的参数关联起来。由此,产生了如下的(有点冗长的)语法。


用来实现模板类中的方法的语法

template<class QueueItem>           Queue<QueueItem>::Queue()

                                         { ... }

 template<class QueueItem> void      Queue<QueueItem>::Insert(QueueItem item)

                                         { ... }

 template<class QueueItem> QueueItem Queue<QueueItem>::Remove()          

                                         { ... }

 template<class QueueItem> int       Queue<QueueItem>::Size()

                                         { ... }

 template<class QueueItem>           Queue<QueueItem>::~Queue()

                                         { ... }

每个方法定义的前面,都带上了一个模板说明(template< class QueueItem>)。并且,类名中还包含了模板语法(Queue<QueueItem>::)。注意,对于Remove 方法,其返回类型,位于最开始的模板说明代码之后,位于类名之前。

参数化的Queue 类的完整代码,如下所示。


完整的模板类

template <class QueueItem> class Queue {

   private:

      QueueItem buffer[100];

      int head, tail, count;

    public:

                Queue();

      void      Insert(QueueItem item);

      QueueItem Remove();

      int       Size();

               ~Queue();

   };

   template <class QueueItem>          

      Queue<QueueItem>::Queue() : count(0), head(0), tail(0) {}

   template <class QueueItem>

      void Queue<QueueItem>::Insert(QueueItem item) {

             assert(count <100);

             buffer[tail] = item;

             tail = (tail + 1)% 100;

             count++;

      }

   template <class QueueItem>

      QueueItem Queue<QueueItem>::Remove() {

            assert(count > 0);

            int val = head;

            head = (head + 1)%100;

            count--;

            return buffer[val];

      }

   template <class QueueItem>

      int Queue<QueueItem>::Size() { return count; }

   template <class QueueItem>          

      Queue<QueueItem>::~Queue() {}

模板,与非参数化类的另一个区别就是,方法定义的位置不同。在非参数化的类中,方法的定义是放置于代码(.cc.C.cpp)文件中的。而对于参数化的类呢,方法定义是直接放置于头(.h)文件中的。这是必要的,在这种情况下,当编译器被要求对某个模板进行实例化的时候(例如,见到Queue<int>这种形式的声明),它仅仅需要查看头文件(通过#include语句引用),就能够得到所有相关的信息,就能够理解该如何对模板进行实例化并且生成对应的类的代码。

1 练习

  1. 1.编写一个程序,它会创建且操作两个Queue<int>类型的队列。对于其中一个队列,直接以Queue<int>方式来声明。对于另一个队列,使用类型定义(typedef)来声明。

  2. 2.使用练习1中开发的代码,来验证,是否可以将一个队列赋值给另一个队列。结果与妳的猜测一致吗?

  3. 3.编写一个程序,它会创建且操作两个Queue<Location>类型的队列。对于其中一个队列,直接以Queue<Location>方式来声明。对于另一个队列,使用类型定义(typedef)来声明。

  4. 4. 命名的数据集:实现并编写一个程序,用来测试一个模板类,它维护着一个由最多100 个元素组成的数据集。其中,每个元素都拥有一个关联名字。元素本身的类型由模板参数来指定,而元素的关联名字,一定是一个字符串(一个char*)。以下代码展示了这个模板可能的用法:

    NamedCollection<int> collection;

    ...

    collection.Add("first", 10);    // 10关联到名字"first"上

    collection.Add("next", 20);     // 将20 关联到名字"next"上

    ...

    if (collection.Contains("next"))

    { int val = collection.ValueOf("next");

    ...

    }

    妳可以假设,不会有名字重复。

  5. 5. 以下,是IntArray 类的代码。为这个类创建一个通过模板实现的版本,并且编写一个程序来测试妳的模板。

    class IntArray {

    private:

    int array[100];

    public:

    Array();

    int& At(int i);

    ~Array();

    };

    IntArray::IntArray() {};

    int& IntArray::At(int i)

    {assert(0<i && i < 100);

    return array[i];

    }

    IntArray::~IntArray() {}

  6. 6.使用妳所实现的Array 模板。编写一个测试程序,它会创建且操作一个二维的整数数组:typedef Array< Array<int> > Matrix。对于这样一个二维数组,该如何访问其中的元素?

  7. 7.编写一个测试程序,它会声明且操作一个由队列组成的数组。

  8. 8.编写一个测试程序,它会声明且操作一个由数组组成的队列。

1 价格汇总

品名

数量:

单价:

金额

PowerEdge R920

1

175,235.53

175,235.53

PowerVault MD3820f, 16G光纤通道,2U-24块硬盘

1

222,085.88

222,085.88

PowerVault(TM) MD1200磁盘存储盘柜(2U)

1

80,633.41

80,633.41

总计(含税) CNY 477,954.82

未知美人

馥甄

林志玲

杨童舒

金巧巧

Your opinions
Your name:Email:Website url:Opinion content: