模板参数可分为类型形参和非类型形参。
类型形参: 出现在模板参数列表中,跟在class或typename关键字之后的参数类型名称。
非类型形参: 用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
例如,我们要实现一个静态数组的类,就需要用到非类型模板参数。
template<class T, size_t N> //N:非类型模板参数
class StaticArray
{
public:
size_t arraysize()
{
return N;
}
private:
T _array[N]; //利用非类型模板参数指定静态数组的大小
};
使用非类型模板参数后,我们就可以在实例化对象的时候指定所要创建的静态数组的大小了。
int main()
{
StaticArray<int, 10> a1; //定义一个大小为10的静态数组
cout << a1.arraysize() << endl; //10
StaticArray<int, 100> a2; //定义一个大小为100的静态数组
cout << a2.arraysize() << endl; //100
return 0;
}
注意:
非类型模板参数只允许使用整型家族,浮点数、类对象以及字符串是不允许作为非类型模板参数的。
非类型的模板参数在编译期就需要确认结果,因为编译器在编译阶段就需要根据传入的非类型模板参数生成对应的类或函数。
这里举一个简单的例子来说明什么是特化,下面是用于比较两个任意相同类型的数据是否相等的函数模板。
template<class T>
bool IsEqual(T x, T y)
{
return x == y;
}
我们大概会这样使用该函数模板:
cout << IsEqual(1, 1) << endl; //1
cout << IsEqual(1.1, 2.2) << endl; //0
这样使用是没有问题的,它的判断结果也是我们所预期的,但是我们也可能会这样去使用该函数模板:
判断结果是这两个字符串不相等,这很好理解,因为我们希望的是该函数能够判断两个字符串的内容是否相等,而该函数实际上判断是确实这两个字符串所存储的地址是否相同,这是两个存在于栈区的字符串,其地址显然是不同的。
类似于上述实例,使用模板可以实现一些与类型无关的代码,但对于一些特殊的类型可能会得到一些错误的结果,此时就需要对模板进行特化,即在原模板的基础上,针对特殊类型进行特殊化的实现方式
对于上述实例,我们知道当传入的类型是char* 时,应该依次比较各个字符的ASCII码值进而判断两个字符串是否相等,或是直接调用strcmp函数进行字符串比较,那么此时我们就可以对char*类型进行特殊化的实现。
函数模板的特化步骤:
1.首先必须要有一个基础的函数模板。
2.关键字template后面接一对空的尖括号<>。
3.函数名后跟一对尖括号,尖括号中指定需要特化的类型。
4.函数形参表必须要和模板函数的基础参数类型完全相同,否则不同的编译器可能会报一些奇怪的错误。
//基础的函数模板
template<class T>
bool IsEqual(T x, T y)
{
return x == y;
}
//对于char*类型的特化
template<>
bool IsEqual<char*>(char* x, char* y)
{
return strcmp(x, y) == 0;
}
注意: 一般情况下,如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。例如,上述实例char*类型的特化还可以这样给出:
//基础的函数模板
template<class T>
bool IsEqual(T x, T y)
{
return x == y;
}
//对于char*类型的特化
bool IsEqual(char* x, char* y)
{
return strcmp(x, y) == 0;
}
不仅函数模板可以进行特化,类模板也可以针对特殊类型进行特殊化实现,并且类模板的特化又可分为全特化和偏特化(半特化)。
全特化即是将模板参数列表中所有的参数都确定化。
例如,对于以下类模板:
template<class T1, class T2>
class Dragon
{
public:
//构造函数
Dragon()
{
cout << "Dragon" << endl;
}
private:
T1 _D1;
T2 _D2;
};
当T1和T2分别是double和int时,我们若是想对实例化的类进行特殊化处理,那么我们就可以对T1和T2分别是double和int时的模板进行特化。
函数模板的特化步骤:
1.首先必须要有一个基础的类模板。
2.关键字template后面接一对空的尖括号<>。
3.类名后跟一对尖括号,尖括号中指定需要特化的类型。
对于T1是double,T2是int的特化如下:
//对于T1是double,T2是int时进行特化
template<>
class Dragon<double, int>
{
public:
//构造函数
Dragon()
{
cout << "Dragon" << endl;
}
private:
double _D1;
int _D2;
};
那么如何证明当T1是double,T2是int时,使用的就是我们自己特化的类模板呢?
当我们实例化一个对象时,编译器会自动调用其默认构造函数,我们若是在构造函数当中打印适当的提示信息,那么当我们实例化对象后,通过观察控制台上打印的结果,即可确定实例化该对象时调用的是不是我们自己特化的类模板了。
偏特化是指任何针对模板参数进一步进行条件限制设计的特化版本。
template<class T1, class T2>
class Dragon
{
public:
//构造函数
Dragon()
{
cout << "Dragon" << endl;
}
private:
T1 _D1;
T2 _D2;
};
偏特化又可分为以下两种表现形式:
1、部分特化
我们可以仅对模板参数列表中的部分参数进行确定化。
例如,我们可以对T1为int类型的类进行特殊化处理。
//对T1为int的类进行特化
template<class T2>
class Dragon<int, T2>
{
public:
//构造函数
Dragon()
{
cout << "Dragon" << endl;
}
private:
int _D1;
T2 _D2;
};
2、参数更进一步的限制
//两个参数偏特化为指针类型
template<class T1, class T2>
class Dragon<T1*, T2*>
{
public:
//构造函数
Dragon()
{
cout << "Dragon" << endl;
}
private:
T1 _D1;
T2 _D2;
};
//两个参数偏特化为引用类型
template<class T1, class T2>
class Dragon<T1&, T2&>
{
public:
//构造函数
Dragon()
{
cout << "Dragon" << endl;
}
private:
T1 _D1;
T2 _D2;
};
此时,当实例化对象的T1和T2同时为指针类型或同时为引用类型时,就会分别调用我们特化的两个类模板。
优点:
1.模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。
2.增强了代码的灵活性。
缺陷:
1.模板会导致代码膨胀问题,也会导致编译时间变长。
2.出现模板编译错误时,错误信息非常凌乱,不易定位错误。