typename使用场景
使用模板中的内嵌类型(如重定义的类型),但模板还没有初始化时,需要加上typename
template <class T>
void print(list<T>& x)
{
list<T>::iterator it = x.begin();
}
函数接收一个list对象,但list存储的数据类型还未确定,用模板参数代替,接着使用list中重定义的迭代器类型,定义一个迭代器,上面的代码是错的,有两种解释:1.类的静态变量也是这样使用,类名::静态变量名,编译器无法区分。2.类模板没有实例化,没有这个类模板,里面的内嵌类型自然也没有,编译器找不到这个类型,所以报错
正确的写法
template <class T>
void print(list<T>& x)
{
typename list<T>::iterator it = x.begin();
}
同时,在类外定义函数,并且以内嵌类型作为函数返回值时,也需要加上typename
非类型模板参数
模板参数通常是类型,如int,double,这样的模板参数被称为类型模板参数,除此之外还有非类型模板参数,这个参数只能接收整形值,不能接收浮点数,string这样的对象。
有一个使用非类型模板参数例子,STL库中的array,C++11的更新中加入了array容器,但这个容器实在是太过鸡肋导致很少被使用。array就是一个静态数组,创建array对象时需要两个模板参数,一个是存储的对象类型,为类型模板参数,一个是数组的大小,为非类型模板参数。
array只是讲解非类型模板参数的一个例子,与原生数组相比,array的唯一优势就是越界的检查严格,而原生数组的检查为抽查,可能检查不出越界。但与vector相比array几乎被吊打。
(非类型模板参数是一个常数,不能修改)
调用func函数试图修改N的值。
模板的特化
函数模板的特化
结果可能是0也可能是1,因为比较的是指针不是指针指向的数据(在堆上开辟空间是随机的),要对指针指向的数据进行比较需要对函数进行特化,
template <class T>
bool Less(T x1, T x2)
{
return x1 < x2;
}
template<>
bool Less<int*>(int* x1, int* x2)
{
return *x1 < *x2;
}
特化模板的规则:
1.必须先有一个基础模板
2.关键词template后跟上<>
3.写函数,函数名后跟<>,里面写要特化的类型(比如刚刚的代码,我要特化int*,尖括号里就写int*)
4.函数形参表的数据类型要和模板的类型对应
关于第4点要特别注意,如果函数模板的形参列表的形参为引用,特化函数的形参也要为引用
而特化函数模板其实不如直接写一个函数来的方便,如果实参已经和函数的形参对应,程序会直接调用该函数不会根据模板生成函数
就刚刚的代码,实参为两个int类型的参数,而已经有了形参为两个int类型的函数,所以直接调用这个函数,相比特化函数,还是直接写对应的函数更好,实际中很少使用函数模板的特化
类模板的特化
全特化
类模板的特化和函数特化规则相同,现在有一个基础类test
template<class T1, class T2>
class test
{
public:
test()
{
cout << "class test<T1, T2>" << endl;
}
};
对test类进行全特化
template<>
class test<int, double>
{
public:
test()
{
cout << "class test<int, double>" << endl;
}
};
当用int和double实例化类模板时,程序就会走特化模板。
半特化/偏特化
半特化也称偏特化,半特化是对模板参数进一步的限制,体现在两个方面:1.对部分参数的特化。2.对参数的限制。
对于上面的基础类模板,可以将第二个参数特化为char
template<class T>
class test<T, char>
{
public:
test()
{
cout << "class test<T, char>" << endl;
}
};
也能将参数限制成指针或者引用
template<class T1, class T2>
class test<T1*, T2*>
{
public:
test()
{
cout << "class test<T1*, T2*>" << endl;
}
};
这样的话,如果用两个指针实例化模板就会调用这个特化版本。