1.1 Template Class基本语法

优质
小牛编辑
119浏览
2023-12-01

1.1.1 Template Class的与成员变量定义

我们来回顾一下最基本的Template Class声明和定义形式:

Template Class声明:

template <typename T> class ClassA;

Template Class定义:

template <typename T> class ClassA
{
  T member;
};

template 是C++关键字,意味着我们接下来将定义一个模板。和函数一样,模板也有一系列参数。这些参数都被囊括在template之后的< >中。在上文的例子中, typename T便是模板参数。回顾一下与之相似的函数参数的声明形式:

void foo(int a);

T则可以类比为函数形参a,这里的“模板形参”T,也同函数形参一样取成任何你想要的名字;typename则类似于例子中函数参数类型int,它表示模板参数中的T将匹配一个类型。除了 typename 之外,我们在后面还要讲到,整型也可以作为模板的参数。

在定义完模板参数之后,便可以定义你所需要的类。不过在定义类的时候,除了一般类可以使用的类型外,你还可以使用在模板参数中使用的类型 T。可以说,这个 T是模板的精髓,因为你可以通过指定模板实参,将T替换成你所需要的类型。

例如我们用ClassA<int>来实例化模板类ClassA,那么ClassA<int>可以等同于以下的定义:

// 注意:这并不是有效的C++语法,只是为了说明模板的作用
typedef class {
  int member;
} ClassA<int>;

可以看出,通过模板参数替换类型,可以获得很多形式相同的新类型,有效减少了代码量。这种用法,我们称之为“泛型”(Generic Programming),它最常见的应用,即是STL中的容器模板类。

1.1.2 模板的使用

对于C++来说,类型最重要的作用之一就是用它去产生一个变量。例如我们定义了一个动态数组(列表)的模板类vector,它对于任意的元素类型都具有push_back和clear的操作,我们便可以如下定义这个类:

template <typename T>
class vector
{
public:
  void push_back(T const&);
  void clear();				
	
private:
  T* elements;
};

此时我们的程序需要一个整型和一个浮点型的列表,那么便可以通过以下代码获得两个变量:

vector<int> intArray;
vector<float> floatArray;

此时我们就可以执行以下的操作,获得我们想要的结果:

intArray.push_back(5);
floatArray.push_back(3.0f);

变量定义的过程可以分成两步来看:第一步,vector<int>int绑定到模板类vector上,获得了一个“普通的类vector<int>”;第二步通过“vector”定义了一个变量。 与“普通的类”不同,模板类是不能直接用来定义变量的。例如:

vector unknownVector; // 错误示例

这样就是错误的。我们把通过类型绑定将模板类变成“普通的类”的过程,称之为模板实例化(Template Instantiate)。实例化的语法是:

模板名 < 模板实参1 [,模板实参2,...] >

看几个例子:

vector<int>
ClassA<double>

template <typename T0, typename T1> class ClassB
{
  // Class body ...
};

ClassB<int, float>

当然,在实例化过程中,被绑定到模板参数上的类型(即模板实参)需要与模板形参正确匹配。 就如同函数一样,如果没有提供足够并匹配的参数,模板便不能正确的实例化。

1.1.3 模板类的成员函数定义

由于C++11正式废弃“模板导出”这一特性,因此在模板类的变量在调用成员函数的时候,需要看到完整的成员函数定义。因此现在的模板类中的成员函数,通常都是以内联的方式实现。 例如:

template <typename T>
class vector
{
public:
  void clear()
  {
    // Function body
  }
	
private:
  T* elements;
};

当然,我们也可以将vector<T>::clear的定义部分放在类型之外,只不过这个时候的语法就显得蹩脚许多:

template <typename T>
class vector
{
public:
  void clear();  // 注意这里只有声明
private:
  T* elements;
};

template <typename T>
void vector<T>::clear()  // 函数的实现放在这里
{
  // Function body
}

函数的实现部分看起来略微拗口。我第一次学到的时候,觉得

void vector::clear()
{
  // Function body
}

这样不就行了吗?但是简单想就会知道,clear里面是找不到泛型类型T的符号的。

因此,在成员函数实现的时候,必须要提供模板参数。此外,为什么类型名不是vector而是vector<T>呢? 如果你了解过模板的偏特化与特化的语法,应该能看出,这里的vector在语法上类似于特化/偏特化。实际上,这里的函数定义也确实是成员函数的偏特化。特化和偏特化的概念,本文会在第二部分详细介绍。

综上,正确的成员函数实现如下所示:

template <typename T> // 模板参数
void vector<T> /*看起来像偏特化*/ ::clear() // 函数的实现放在这里
{
  // Function body
}