Template Class基本语法
基本语法
声明:
template < typename T> class ClassA;
定义:
template < typename T> class ClassA
{
T member;
}
template
表示接下来将会定义一个模板。和函数一样,模板也有一系列参数。这些参数都被囊括在template之后的< >中。在上文的例子中,typename T
便是模板参数。 来看一个函数声明:
void ff(int a);
typename T
可以类比int a
, T是形参,typename
表示模板参数中的T将匹配一个类型。
我们可以用类似ClassA<int>
来实例化类ClassA,那么ClassA<int>
等同于:
// 注意:这并不是有效的C++语法,只是为了说明模板的作用
typedef class {
int member;
} ClassA<int>;
怎么使用
定义一个类模板:
template <typename T>
class vector
{
public:
void push_back(T const&);
void clear();
private:
T* elements;
};
对于C++来说,类型最重要的作用之一就是用它去产生一个变量,因此,可以这样用:
vector<int> intArray;
vector<float> floatArray;
此时我们就可以执行以下的操作,获得我们想要的结果:
intArray.push_back(5);
floatArray.push_back(3.0f);
变量定义的过程可以分成两步来看:第一步,vector<int>
将int绑定到模板类vector上,获得了一个“普通的类vector<int>
”;第二步通过“vector”定义了一个变量。
我们把通过类型绑定将模板类变成“普通的类”的过程,称之为模板实例化(Template Instantiate)。实例化的语法是:
模板名 < 模板实参1 [,模板实参2,...] >
当然,在实例化过程中,被绑定到模板参数上的类型(即模板实参)需要与模板形参正确匹配。 就如同函数一样,如果没有提供足够并匹配的参数,模板便不能正确的实例化。
模板类的成员函数
C++11正式废弃模板导出这一特性,因此在模板类的变量在调用成员函数的时候,需要看到完整的成员函数定义。因此现在的模板类中的成员函数,通常是以内联的方式实现的。
template <typename T>
class vector
{
public:
void clear()
{
// Function body
}
private:
T* elements;
};
当然,我们也可以将vector::clear的定义部分放在类型之外,只不过这个时候的语法就显得蹩脚许多:
template <typename T>
class vector
{
public:
void clear(); // 注意这里只有声明
private:
T* elements;
};
template <typename T>
void vector<T>::clear() // 函数的实现放在这里
{
// Function body
}
在成员函数实现的时候,必须要提供模板参数。此外,为什么类型名不是vector而是vector<T>
呢? 如果你了解过模板的偏特化与特化的语法,应该能看出,这里的vector在语法上类似于特化/偏特化。实际上,这里的函数定义也确实是成员函数的偏特化
综上,正确的成员函数实现如下所示:
template <typename T> // 模板参数
void vector<T> /*看起来像偏特化*/ ::clear() // 函数的实现放在这里
{
// Function body
}
Template Function基本语法
template <typename T> void foo(T const& v);
template <typename T> T foo();
template <typename T, typename U> U foo(T const&);
template <typename T> void foo()
{
T var;
// ...
}
使用:
#include <list>
#include <vector>
template <typename T> T Add(T a, T b)
{
return a + b;
}
int main()
{
int a = 5;
int b = 3;
int result = Add<int>(a, b);
}
整型也可以是Template参数
模板参数除了类型外(包括基本类型、结构、类类型等),也可以是一个整型数(Integral Number)。这里的整型数比较宽泛,包括布尔型,不同位数、有无符号的整型,甚至包括指针。我们将整型的模板参数和类型作为模板参数来做一个对比:
template <typename T> class TemplateWithType;
template <int V> class TemplateWithValue;
我想这个时候你也更能理解 typename 的意思了:它相当于是模板参数的“类型”,告诉你 T 是一个 typename。
按照C++ Template最初的想法,模板不就是为了提供一个类型安全,容器调试的宏吗?有类型就够了,为什么要引入整形参数呢?考虑宏,它除了代码替换,还有一个作用是作为常量出现。所以整形模板参数最基本的用途就是定义一个常数。比如:
template <typename T, int Size> struct Array
{
T data[Size];
};
Array<int, 16> arr;
相当于
class IntArrayWithSize16
{
int data[16]; // int 替换了 T, 16 替换了 Size
};
IntArrayWithSize16 arr;
另外,因为模板的匹配是在编译时完成的,所以实例化模板的时候所使用的参数,也必须要在编译期就能确定:
template <int i> class A {};
void foo()
{
int x = 3;
A<5> a; // 正确!
A<x> b; // error C2971: '_1_3::A' : template parameter 'i' : 'x' : a local variable cannot be used as a non-type argument
}
因为x不是一个编译期常量,所以 A 就会告诉你,x是一个局部变量,不能作为一个模板参数出现。
嗯,这里我们再来写几个相对复杂的例子:
template <int i> class A
{
public:
void foo(int)
{
}
};
template <uint8_t a, typename b, void* c> class B {};
template <bool, void (*a)()> class C {};
template <void (A<3>::*a)(int)> class D {};
template <int i> int Add(int a) // 当然也能用于函数模板
{
return a + i;
}
void foo()
{
A<5> a;
B<7, A<5>, nullptr> b; // 模板参数可以是一个无符号八位整数,可以是模板生成的类;可以是一个指针。
C<false, &foo> c; // 模板参数可以是一个bool类型的常量,甚至可以是一个函数指针。
D<&A<3>::foo> d; // 丧心病狂啊!它还能是一个成员函数指针!
int x = Add<3>(5); // x == 8。因为整型模板参数无法从函数参数获得,所以只能是手工指定啦。
}
template <float a> class E {}; // ERROR: 别闹!早说过只能是整数类型的啦!