1、非类型模板参数
之前提及的模板参数全部都是类型,其实模板的参数也可以是非类型的常量。如下面这个模板
template<class T, int N>
class Array
{
T _a[N];
};
int main()
{
Array<int, 100> ar1;
return 0;
}
这实际上是实现了一个定长度的数组,stl里的array的实现原理就是如此,由传入模板参数的数字确定。
需要注意几点。(1)非类型的模板参数在传参时,只能传常量,及不能用变量来传参。(2)只有整形家族能做非类型模板参数,即char、int、long、long long等,浮点类型和自定义类型不能做模板参数。
2、模板的特化
通常情况下,使用模板可以实现一些与类型无关的代码,如实现一个进行变量比较的函数模板,如下。
template<class T>
bool IsEqual(T a, T b)
{
return a == b;
}
int main()
{
cout << IsEqual(1, 2) << endl;
cout << IsEqual(1.1, 1.1) << endl;
cout << IsEqual("abc", "abc") << endl;
return 0;
}
但是在进行字符串比较的时候,传入函数的参数时字符串的首地址,即两个地址进行比较,这显然是不正确的,我们希望的是堆字符串的内容进行比较。这也是这种写法的局限性。这个时候解决方法是模板的特化,特化是对某些类型的特殊化处理。如下。
template<class T>
bool IsEqual(T a, T b)
{
return a == b;
}
template<>
bool IsEqual<const char*>(const char* a, const char* b)
{
return strcmp(a, b) == 0;
}
int main()
{
cout << IsEqual(1, 2) << endl;
cout << IsEqual(1.1, 1.1) << endl;
const char* a = "abc";
const char* b = "bcd";
cout << IsEqual(a, b) << endl;
return 0;
}
类模板也可以特化。
template<class T>
class Data
{
T _dt;
};
template<>
class Data<char>
{
char _dt;
};
以上的特化为全特化的情况,也可以有偏特化的情况,更加灵活。如下。
template<class T1, class T2>
class Data
{
T1 d1;
T2 d2;
};
template<class T1>
class Data<T1, int>
{
T1 d1;
int d2;
};
template<>
class Data<int, int>
{
int d1;
int d2;
};
上面的为原始模板,第二个是一个偏特化(也称半特化)的模板,第三个是全特化的模板。注意(1)在参数匹配的上的情况下,编译器会优先调用已经特化的函数模板或者类模板。(2)不论是偏特化还是全特化的模板,本质还是模板,并没有实例化。
3、模板分离编译
在工程中我们一般是函数的声明和定义分离,便于维护代码。但是函数模板我们是不会声明和定义分离的,将函数模板放在头文件中。这是因为,编译器不会编译模板本身的,在预处理阶段,编译器检查我们主程序调用模板的地方进行特定实例化,如果模板的声明和定义分离,在定义的文件中如果没有调用模板,则不会实例化特定的函数或者类,在主程序中会链接不上。
怎么解决上述问题?
(1)对模板进行显式实例化
template<class T>
bool IsEqual(T& left, T& right)
{
return left == right;
}
template
bool IsEqual<int>(int& left, int& right);
在定义的文件中显式的实例化就可以了
(2)将模板的定义写在头文件中,简单粗暴。
4、模板总结
优点(1)模板复用了代码,节省资源,更快的迭代开发。(2)增强了代码的灵活性。
缺点(1)会导致代码膨胀问题,也会导致编译时间变长。这是因为编译器根据模板的调用实例化出函数或者类。(2)出现模板编译错误时,编译信息非常凌乱,不易定位错误。