8.5.1 定义类模板
我们将选择一个简单的示例来说明如何定义和使用类模板,并且不过多考虑因误用而可能出现的错误-- 那样将使问题复杂化。假设我们希望定义几个可以存储大量数据样本的类,每个类都应当提供一个求出所存储样本中最大值的Max()函数。该函数类似于我们在第6章讨论类模板时介绍的那个Max()函数。我们可以定义一个类模板,用来生成可以存储任何类型样本的CSamples类。
- template <class T>
- class CSamples
- {
- public:
- // Constructor definition to accept an array of samples
- CSamples(const T values[], int count)
- {
- m_Free = count < 100 count:100;
// Don't exceed the array - for(int i = 0; i < m_Free; i++)
- m_Values[i] = values[i]; // Store count number of samples
- }
-
- // Constructor to accept a single sample
- CSamples(const T& value)
- {
- m_Values[0] = value; // Store the sample
- m_Free = 1; // Next is free
- }
-
- // Default constructor
- CSamples(){ m_Free = 0 } // Nothing stored, so first is free
-
- // Function to add a sample
- bool Add(const T& value)
- {
- bool OK = m_Free < 100; // Indicates there is a free place
- if(OK)
- m_Values[m_Free++] = value;
// OK true, so store the value - return OK;
- }
-
- // Function to obtain maximum sample
- T Max() const
- {
- // Set first sample or 0 as maximum
- T theMax = m_Free m_Values[0] : 0;
-
- for(int i = 1; i < m_Free; i++)
// Check all the samples - if(m_Values[i] > theMax)
- theMax = m_Values[i]; // Store any larger sample
- return theMax;
- }
-
- private:
- T m_Values[100]; // Array to store samples
- int m_Free; // Index of free location in m_Values
- };
为了指出正在定义的是模板而非简单的类,我们在关键字class和类名CSamples之前,插入关键字template和尖括号内的类型形参T。该语法实质上与第6章定义函数模板的语法相同。形参T是类型变量,它将在我们声明类对象时被具体类型代替。类定义中出现形参T的任何位置,都将被对象声明中指定的类型代替,这将创建一个对应于指定类型的类定义。我们可以指定任何类型(基本数据类型或类类型),但指定的类型必须在类模板的上下文中有意义。任何用来根据模板实例化某个类的类类型,都必须已经定义过模板的成员函数处理本类对象时要用到的所有运算符。例如,如果我们的类没有实现operator>(),则不能使用CSamples类模板。一般来说,如果需要的话,我们可以在类模板中指定多个形参,我们稍后再来讨论这种可能性。
回到本示例上来,存储样本的数组的类型被指定为T。因此,该数组将成为我们声明CSamples对象时为T指定的那种类型的数组。可以看出,我们不仅在Add()和Max()函数中,而且还在类的两个构造函数中也使用了类型T。当使用该模板实例化类对象时,构造函数中出现的T同样将被替换掉。
构造函数支持创建空对象、只有一个样本的对象以及用样本数组进行初始化的对象。Add()函数允许一次一个地将样本添加到对象中。我们也可以重载这个函数,以允许样本数组相加。类模板提供了基本的措施来防止在Add()函数和接受样本数组的构造函数中m_Values数组的最大容量被超过。
前面曾经说过,理论上我们可以创建可处理任何数据类型的CSamples类的对象:int类型、double类型或任何已经定义过的类类型。在实践中,这种可能性并不意味着所创建的对象必定能够编译,且像我们预期的那样工作。实际情况完全取决于模板定义所做的事情,通常一个模板仅仅适用于特定的类型范围。例如,Max()函数隐含地认为>运算符可以用于被处理的任何类型。如果实际情况不是这样,则程序将不能编译。无疑,通常我们定义的模板只是为了处理某些类型而非此外的其他类型,但无法限制施加到模板上的类型。
模板成员函数
我们或许希望将类模板成员函数的定义放在模板定义的外部。实现该功能的语句不是特别明显,因此我们来看一下应该如何做。我们应当以正常的方式将函数声明放在类模板定义的内部。例如:
- template <class T>
- class CSamples
- {
- // Rest of the template definition...
- T Max() const; // Function to obtain maximum sample
- // Rest of the template definition...
- }
此处的代码将Max()函数声明为类模板的成员,但没有定义该函数。现在,我们需要为这个成员函数的定义创建单独的函数模板,创建时必须使用模板类的名称加上尖括号内的形参,以标识函数模板所属的类模板:
- template<class T>
- T CSamples<T>::Max() const
- {
- T theMax = m_Values[0]; // Set first sample as maximum
-
- for(int i = 1; i < m_Free; i++) // Check all the samples
- if(m_Values[i] > theMax)
- theMax = m_Values[i]; // Store any larger sample
- return theMax;
- }
我们在第6章学过函数模板的语法。因为该函数模板是形参为T的类模板的成员,所以这里的函数模板定义应该有与类模板定义相同的形参。本例中只有一个形参T,但通常可能有好几个。如果类模板有两个或更多形参,则每个定义成员函数的模板也应该有同样的形参。
注意,作用域解析运算符之前只能使用附带形参名称T的类模板名。这是必需的-- 形参对于识别出根据该模板生成的函数属于哪个类非常重要。类模板的类型是CSamples<T>,其中T是我们创建类模板实例时指定的类型。指定的类型将被插入到类模板中,从而生成类定义。还将被插入到函数模板中,从而生成本类中Max()函数的定义。每个根据类模板生成的类都需要有自己的Max()函数定义。
在类模板定义外部定义构造函数或析构函数与此类似。我们可以将接受样本数组的那个构造函数的定义写成下面的形式:
- template<class T>
- CSamples<T>::CSamples(T values[], int count)
- {
- m_Free = count < 100 count:100; // Don't exceed the array
-
- for(int i = 0; i < m_Free; i++)
- m_Values[i] = values[i]; // Store count number of samples
- }
我们以定义普通成员函数时使用的相同方式,在模板中指定构造函数属于哪个类。注意,构造函数名不要求指定返回类型-- 它只能是CSamples,但需要用类模板类型CSamples<T>加以限定。我们在作用域解析运算符之前只能使用附带形参名称的类模板名。