C++学习记录——십유 模板进阶
1、非类型模板参数
模板参数分为类型参数和非类型参数。
如果要定义一个静态数组
#define N 10
template<class T>
class Array
{
private:T _a[N];
};Array<int> a1;Array<double> a2;
这就是类型模板参数,实例化那里定义了对象参数,模板那里定义类类型参数。但是这样只能固定大小,不能用同一个类定义不同大小的数组。非类型模板参数定义的是常量。
//#define N 10
template<class T, size_t N>
class Array
{
private:T _a[N];
};int main()
{Array<int, 10> a1;Array<double, 100> a2;return 0;
}
非类型模板参数也可以用缺省值,但只能是整型常量。布尔类型也可以。
2、类模板特化
先弄一个简单的日期类
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()
{//Array<int, 10> a1;//Array<double, 100> a2;//func1(a1);//func2(a2);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;
}
结果确实是1 1 0,但是现在我不想这样比较。虽然指针可以调用Less比较,但是还想要单独写出来
template<class T>
bool Less(T left, T right)
{return left < right;
}template<class T>
bool Less<Date*>(Date* left, Date* right)
{return *left < *right;
}
这就是特化,对于某些类型特殊化处理。模板可以泛型变成,每个类型都可以使用成员函数,但是也可以对某个类型单独拿出来。
特化必须要现有一个基础的函数模板,函数形参要和模板函数的基础参数类型相同。
模板特化是在原生类型不符合需求时单独写一个函数。类模板是在类名后加<类型>。特化对于类模板特化更有意义。
偏特化
template<class T>
struct Less
{bool operator()(const T& l, const T& r){return l < r;}
};Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout << Less<Date>()(d1, d2) << endl;Date* p1 = &d1;Date* p2 = &d2;cout << Less<Date*>()(p1, p2) << endl;int* p3 = new int(1);int* p4 = new int(2);cout << Less<int*>()(p3, p4) << endl;
也可以这样
template<class T>
struct Less<T*>
{bool operator()(const T* l, const T* r){return *l < *r;}
};
偏特化并不是在使用一个具体的类型,而是用模板,有些泛型编程的意思。偏特化可以将模板参数类表中的一部分参数特化,也可以针对模板参数进一步地条件限制。
3、模板分离编译
在Func.h文件里这样写
template<class T>
T Add(const T& Left, const T& right);void func();
Func.cpp文件里这样写
template<class T>
T Add(const T& Left, const T& right)
{return left + right;
}void func()
{cout << "func" << endl;
}
Test.cpp里调用Add和Func函数
#include "Func.h"int main()
{Add(1, 2);func();
}
调用Add会出现链接错误,而func则没事。常见的声明和定义分离。类模板不能这样,普通函数可以。
现在有三个文件func.h func.cpp test.cpp,编译器先对它们进行预处理,展开头文件,变成func.i,以及test.i;编译阶段,要开始检查语法,生成汇编代码,func.s test.s;汇编阶段,将汇编代码转换成二进制机器码, func.o test.o;链接阶段,合并生成可执行文件a.out。
在编译阶段,编译器只会生成func的汇编代码,而没有Add。func.o中有func的地址,而Add没有实例化,所有没法确定T。
在函数调用处,都会有call的指令。到了编译阶段,两个函数都有声明,定义,链接阶段会去找它们来合并起来,这时候会有一个符号表,用来映射名字和地址,func此时找到了地址,但是Add找不到它的地址,因为不确定T,Add也不知道自己要实例化成什么类型。
为了解决这个问题,我们可以显式实例化。
template
int Add<int>(const int& left, const int& right);
但是显式实例化一次只能写一个类型。常用的办法就是不分离,都放在.h文件里,因为头文件会展开。声明和定义放在一起,直接就实例化了,编译时就有地址,链接时不需要去找。
4、模板总结
模板复用了代码,节省资源,更快地迭代开发,C++标准模板库(STL)因此而诞生
增强了代码的灵活性
模板会导致代码膨胀问题,也会导致编译时间变长
出现模板编译错误时,错误信息非常凌乱,不易定位错误
结束。