@TOC
1.非类型模板参数
模板参数分为 类型形参 和 非类型形参 类型形参即出现在模板参数列表中, 跟在class或者typename之类的参数类型名称 非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将参数当成常量使用
#include<iostream>
using namespace std;
#define N 10
template <class T>//类型模板参数
class Array
{
public:
private:
T _a[N];
};
int main()
{
Array<int> a;//10
Array<double>b;//100
return 0;
}
使用类型模板参数,虽然看似可以解决问题,但若a要10个int的数组,b要100个double的数组,宏就没办法解决了
所以为了解决这个问题,引入非类型模板参数,定义的是常量,一般是整形常量
#include<iostream>
using namespace std;
template <class T,size_t N>//非类型模板参数
class Array
{
public:
private:
T _a[N];
};
int main()
{
Array<int,10> a;
Array<double,20>b;
return 0;
}
同样非类型模板参数还可以给缺省值 ,由于是整型常量,所以缺省值也是常量
同样在函数内部,无法对常量值进行修改
#include<iostream>
using namespace std;
template<class T,size_t N=20>
void func(const T& a)
{
N = 20;//左操作数必须为左值
}
int main()
{
func(1);
}
必须为整形常量 整形包括 int 、char、 long 、long long 、short
若为double类型就会报错
2. 模板特化
使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理
函数模板特化
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
bool operator<(const Date& d)const
{
return (_year < d._year) ||
(_year == d._year && _month < d._month) ||
(_year == d._year && _month == d._month && _day < d._day);
}
bool operator>(const Date& d)const
{
return (_year > d._year) ||
(_year == d._year && _month > d._month) ||
(_year == d._year && _month == d._month && _day > d._day);
}
friend ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
template<class T>
bool Less(T left, T right)
{
return left < right;
}
int main()
{
cout << Less(1, 2) << endl; // 可以比较,结果正确
Date d1(2022, 7, 7);
Date d2(2022, 7, 8);
cout << Less(d1, d2) << endl; // 可以比较,结果正确
Date* p1 = &d1;
Date* p2 = &d2;
cout << Less(p1, p2) << endl; // 可以比较,结果错误
return 0;
}
若p1与p2都为Date*,则调用Less ,会变成 left与right指针的比较 ,不符合预期,我们想要的是日期与日期之间的比较
所以为了防止这种 特殊情况的发生,所以使用 模板特化 (对某些类型进行特殊化处理)
此时就可以正常比较两个日期
函数模板特化意义不大,可以直接使用函数重载的方式来实现的
类模板特化
偏特化
偏特化——进一步的限制 必须在原模版的基础上才可以使用
针对的是指针这个泛类
全特化
必须在原模版的基础上才可以使用
只针对其中一个,如指针中的日期类
半特化
对部分参数特化
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
//半特化
template <class T1>
class Data<T1, int>//只将第一个模板参数特化
{
public:
Data() { cout << "Data<T1, int>" << endl; }
private:
T1 _d1;
int _d2;
};
void TestVector()
{
Data<int, int> d1;//Data<T1, int>
Data<int*, int> d3;
//Data<T1, int>
Data<double, int> d4;
//Data<T1, int>
}
int main()
{
TestVector();
return 0;
}
此时虽然有两个参数,但是只特化了一个 ,无论传的是 int、int*、double 都是走到半特化
参数的进一步限制
两个参数特化为引用类型
两个参数特化为指针类型
3. 模板的分离编译
模板并不支持分离编译 即声明在一个文件,定义在一个文件
此时调用模板add 就会报错,因为模板的声明和定义不在同一文件中
调用普通函数func,即可正常运行
模板会发生链接错误
func.h func.cpp test.cpp预处理:头文件展开/ 注释的消除/ 条件编译/ 宏的替换
编译: 检查语法,生成汇编代码func.s test.s汇编:汇编代码转换为二进制机器码func.o test.o 链接:合并生成可执行文件 a.out
由预处理阶段到 编译阶段时,通过func.i生成func.s ,生成汇编指令 ,但是只能生成func的,不能生成add的
函数被编译后生成指令,才可以找到第一句指令的地址 即函数地址
func会被编译成一堆指令,所以在func.o有func函数的地址,但是没有add的地址,因为add没有实例化 ,没办法确定T
就像是你差一点就可以交首付了,你打电话给你哥们借钱,你哥们说没问题,就像声明一样,这是一种承诺,所以在编译阶段 add就可以过了 即call add( ? )
?代表现在没有地址
等到链接阶段才能拿到地址 ,而现在只是说得到承诺,到时候可以去拿到地址啦
但是 当你要交首付的时候,你哥们突然说借不了钱了,那这个房子首付也就交不上了
就好比 链接时 找不到add的函数地址了,所以会链接错误
链接之前不会交互,各走各的,不知道不能实例化,因为没办法确定T
解决链接错误的问题
显示实例化
虽然设置成double可以解决double的问题,但是传int 等又会报错,所以还是有很大的缺陷 局限性,实际中一般不使用
将声明和定义放在一个文件中
此时在预处理阶段将头文件展开,由于声明和定义都在一起,所以此时add函数 call时有地址存在,不需要向func一样,链接时去寻找函数地址了声明和定义放在一起,直接就可以实例化,编译时就有地址,不需要链接
4. 模板总结
缺陷
1. 模板会导致代码膨胀问题,也会导致编译时间变长模板会进行实例化,所以运行时间变长,因为是一个模板,若传过来int 、char、double 类型都要实现,所以代码会膨胀
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误通常为一报错就一大堆错误,一般只需要找到第一个错误并解决它,就可以了