《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,因为,这正是从缓冲数组中取出的数据的类型。
|
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.编写一个程序,它会创建且操作两个Queue<int>类型的队列。对于其中一个队列,直接以Queue<int>方式来声明。对于另一个队列,使用类型定义(typedef)来声明。
2.使用练习1中开发的代码,来验证,是否可以将一个队列赋值给另一个队列。结果与妳的猜测一致吗?
3.编写一个程序,它会创建且操作两个Queue<Location>类型的队列。对于其中一个队列,直接以Queue<Location>方式来声明。对于另一个队列,使用类型定义(typedef)来声明。
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. 以下,是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.使用妳所实现的Array 模板。编写一个测试程序,它会创建且操作一个二维的整数数组:typedef Array< Array<int> > Matrix。对于这样一个二维数组,该如何访问其中的元素?
7.编写一个测试程序,它会声明且操作一个由队列组成的数组。
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 opinionsHxLauncher: Launch Android applications by voice commands