非类型模板参数
模板参数分为 类型模板参数 和 非类型模板参数.
非类型模板参数,是用一个整型常量作为 类模板/函数模板的一个参数.
例:在stl的deque容器中,可以在模板参数里显式传一个buffer容纳的数据量.
模板特化
--引入
在某些场景下,要对某些具体类型进行特殊化处理.
为特定类型定义专用化模板,就是模板特化.
例:当对vector里的数据进行排序时,如果vector里存的是数据的地址,需要特殊处理.
template<class T>
void mySort(vector<T>& v)
{
//用算法库的sort,传less默认排升序
sort(v.begin(), v.end(), less<T>());
}
void test()
{
//存待排序数字
int number[] = { 8, 7, 6, 5};
//存待排序数字的地址
int* numberAdr[] = { number, number + 1, number + 2, number + 3 };
//此时一个vector存待排序数字,另一个vector存待排序数字地址
vector<int> vNum(number, number + 4);
mySort(vNum);//此时T实例化为int,对数据进行排序
for (auto x : vNum)
cout << x << " ";
cout << endl;
vector<int*> vNumAdr(numberAdr, numberAdr + sizeof(numberAdr) / sizeof(numberAdr[0]));
mySort(vNumAdr);//此时T实例化为int*,对数据地址进行排序,不符合要求
for (auto x : vNumAdr)
cout << *x << " ";
cout << endl;
}
--类模板特化
前提:类模板的特化不能单独存在,必须先要有完整的类模板.
格式如下:
//完整的类模板
template<class T1, class T2>
struct Test
{
Test()
{
cout << "泛化版本Test" << endl;
}
};
template<>//填写没有被特化的模板参数
struct Test<int, int>//类后面<>有序填写 所有模板参数 和 被特化/被特殊处理的类型
{
Test()
{
cout << "T1、T2都被特化版本,T1和T2都为int类型,Test类会特殊处理1" << endl;
}
};
template<class T2>//填写没有被特化的模板参数
struct Test<int, T2>//类后面<>有序填写 所有模板参数 和 特化类型
{
Test()
{
cout << "T1被特化版本, T1为int类型,Test类会特殊处理2" << endl;
}
};
template<class T1>//填写没有被特化的模板参数
struct Test<T1, int>//类后面<>有序填写 所有模板参数 和 特化类型
{
Test()
{
cout << "T2被特化版本,T2为int类型,Test类走这步特殊处理3" << endl;
}
};
void test1()
{
//根据传递的类型,走不同的分支/特殊处理不同情况
Test<int, int> t1;
Test<int, double> t2;
Test<char, int> t3;
Test<char, char> t4;
}
--特化的分类
( 1 ) 全特化:指定模板参数列表里的所有模板参数.
例:
template<>//填写没有被特化的模板参数
struct Test<int, int>//类后面<>有序填写 所有模板参数 和 被特化/被特殊处理的类型
{
Test()
{
cout << "T1、T2都被特化版本,T1和T2都为int类型,Test类会特殊处理1" << endl;
}
};
( 2 ) 偏特化:将一部分模板参数进行特化.
例:
template<class T2>//填写没有被特化的模板参数
struct Test<int, T2>//类后面<>有序填写 所有模板参数 和 特化类型
{//类内特殊处理方式};
template<class T1>//填写没有被特化的模板参数
struct Test<T1, int>//类后面<>有序填写 所有模板参数 和 特化类型
{};
--函数模板特化
前提:函数模板的特化不能单独存在,必须要有完整的函数模板/泛化版本
注意:函数模板的特化只能进行全特化
格式与类模板特化类似
template<class T1, class T2>
void func(T1 a1, T2 a2)
{
cout << "<T1、T2>泛化版本" << endl;
}
template<>
void func<int, int>(int a1, int a2)
{
cout << "<int, int>特化版本" << endl;
}
template<>
void func<double, int>(double a1, int a2)
{
cout << "<double, int>特化版本" << endl;
}
void test2()
{
func<int, int>(1, 2);
func<double, int>(1.1, 2);
//也能让编译器自己推导类型
func(1, 2);
func(1.1, 2);
func('a', 'b');
}
--特殊的偏特化
//相当于指定类型的范围,只要都是指针/引用,走特化版本
template<class T1, class T2>
struct Test<T1*, T2*>
{
Test()
{
cout << "<指针, 指针>" << endl;
}
};
template<class T1, class T2>
struct Test<T1&, T2&>
{
Test()
{
cout << "<引用, 引用>" << endl;
}
};
void test3()
{
Test<int*, char*> t1;
Test<int&, char&> t2;
Test<int*, char&> t3;
}
模板参数是引用或指针的例子:
在实现迭代器时,Iterator会有3个模板参数
<class T, class Ref, class Ptr>
T:数据类型
Ref:用来接收 T& 或 const T&,作为operator*()返回值区分普通迭代器和const迭代器
Ptr:与Ref同理
模板的分离编译
--普通类与模板类分离编译比较
非模板类
在头文件中定义的普通类,内部的函数:
小函数放在类内作为内联函数;
大函数声明放在类内,定义放在另外.cpp文件(声明类域即可)
模板类
不支持函数声明和定义放在不同的文件中.
会出现链接错误,找不到分离编译的函数地址
--错误演示
为什么 模板类内部函数的声明和定义 不能放在不同的两个文件呢?
例:
vector.hpp
template<class T>
class vector
{
//声明和定义在不同文件的函数
void reserve(size_t n);
void push_back(const T& x);
};
vector.cpp:有它们的具体实现
#include "vector.hpp"
template<class T>
void lyh::vector<T>::reserve(size_t n)
{
if (n > capacity())
{
//开空间,拷贝数据
T* newBlock = new T[n];
for (size_t i = 0; i < size(); ++i)
{
newBlock[i] = _start[i];
}
//memcpy(newBlock, _start, sizeof(T) * size());
//记录当前size()的值,因为它内部是_finish - _start算出来的,更新成员变量时,不能使用size()
size_t st = size();
//释放旧空间
delete[] _start;
//更新成员变量
_start = newBlock;
_finish = newBlock + st;
_end_of_storage = newBlock + n;
}
}
template<class T>
void lyh::vector<T>::push_back(const T& x)
{
//1 检查扩容
if (_finish == _end_of_storage)
reserve(capacity() == 0 ? 5 : 2 * capacity());
//2 插入数据
*_finish = x;
++_finish;
}
test.cpp中
void test()
{
lyh::vector<int> v;
v.push_back(1);
}
--具体原因
在预处理阶段,.hpp头文件的内容将会在两个.cpp文件中展开.
( 1 ) 在链接之前,各个.cpp文件时分别进行编译汇编,不会有任何交互
( 2 ) 在test.cpp中,有指定vector,进行显式实例化,
这些没有分离的成员函数也会实例化出一份具体的函数,有函数地址
声明和定义在不同文件的函数,只有声明,那只能在链接阶段去其它文件找函数地址
( 3 ) vector.cpp中,里面全都是模板,无法进行实例化,
所以符号表里没有这些函数的地址.
综上:找不到对应的函数,会发生链接错误
--解决方案
( 1 ) 模板类内部的 函数声明 和 定义 要放在同一文件中.
分离也必须在同一文件中分离.
( 2 ) 在.cpp中手动显式实例化
例:在vector.cpp的结尾加上:
template
vector<int>;
让编译器在该文件中手动实例化出一份vector类型
但可能还需要实例化出存储double、char、以及各种自定义类型的vector,不建议使用这种方案.