模板进阶和array
模板进阶
- 非类型模板参数
- 容器里面 的非类型模板参数
- 函数模板的特化
- 类模板的特化
- 全特化
- 偏特化(半特化)
- 特化类模板来实现排序
- 为什么模板的声明和定义不能分离到两个文件
非类型模板参数
#include<iostream>
////非类型模板参数
//实现一个静态栈
template<class T,size_t N=10>//给一个非类型模板参数可以当成常量使用在类里面
class stack {T a[N];//可以直接当常量使用.int capacity;
};
模板里面可以直接定义整型,int char但是double,float不行,这种c++20后面才支持,自定义类型不支持,但是指针行

c++20之前支持一下:
- 整型(枚举)
- 指针类型(函数指针,对象指针)
- 引用(对象引用,函数引用)
- 成员指针
这里面可以没有T
template<size_t b>
class s {};
容器里面 的非类型模板参数

array是一个静态数组,一开始就开好了空间。
它也支持迭代器,[],size.等很多接口,但是不支持头插尾插,因为这个已经开好了空间,不能扩容了。
?为什么要设置这个array了,它和int a[];区别是什么呢;
- 普通数组在写的时候,越界会抽取检查,读的时候并不会。
- array数组不管读和写入都会严格检查,也不是抽取检查
std::array<int, 10> d1;int a[10];a[11] = 12;//会报越界错误std::cout<<a[11];//不会发生错误;
std::array<int, 10> d1;int a[10];//a[11] = 12;//a[15] = 3;不会报错//std::cout << a[11];std::cout << d1[11];//会报错
3.STL容器里面不能直接用int a[]作为元素,因为STL里面的元素要克构造,可赋值的,这也是array的好处

4.在函数传参的时候,int a[]只能传指针,如果在其他函数输出,还不能使用迭代器,还需要传数组的长度。而array就可以直接传过去,输出使用迭代器更方便。
函数模板的特化
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 std::ostream& operator<<(std::ostream& _cout, const Date& d);
private:int _year;int _month;int _day;
};
std::ostream& operator<<(std::ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}template<class T>
bool less(T& x, T& y) {return x < y;
}int main(){Date d1(2025, 1, 1);Date d2(2025, 1, 3);std::cout << less(d1, d2) << std::endl;
}
对于这种对象,完全没有问题函数的模板,但是如果是对象的指针就会出现问题.
Date* d1 = new Date(2025, 1, 1);Date*d2=new Date(2025, 1, 3);std::cout << less(d1, d2) << std::endl;
对于这种Date*函数普通模板就会出现问题,它们比较是地址,但new 的地址是随机的。会出现问题
出现随机结果。
这时候我们可以利用函数模板特化,写出一个Date*的函数模板,这样就不会出现错误
template<>
bool less<Date*>(Date* & x, Date* & y) {return *x < *y;
}
还有就是我们一般传不改变的新参,都加上const.但是函数模板特化有个特点就是函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇
怪的错误。
即使加上了const,为什么还会报错,因为const在前面修饰的是指针指向的内容,修饰的Date,//而上面的函数模板修饰的引用本身,所以我们应该在*后面加const,修饰本身。
解决:
template<>
//bool less<Date*>(const Date* & x, const Date* & y)
bool less<Date*>( Date* const& x, Date* const& y) {//即使加上了const,为什么还会报错,因为const在*前面修饰的是指针指向的内容,修饰的Date*return *x < *y;//而上面的函数模板修饰的引用本身,所以我们应该在*后面加const,修饰本身。
}
总结:
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇
怪的错误。
类模板的特化
全特化
就是把模板参数里面的所有参数都确定化。在类名后面加括号。写函数模板特化和类模板特化,前面必须有普通的模板,必须有主模板声明
//类模板
template<class T1,class T2>
class stack {void fun() {std::cout << "普通模板" << std::endl;}};template<>
class stack<int,int>{//在类名后面加括号。写函数模板特化和类模板特化,前面必须有普通的模板,必须有主模板声明void fun() {std::cout << "int模板" << std::endl;}};
不管什么类型都可以确定化,自定义也可以,指针,引用
template<>
/*class stack<int,int>*/
/*class stack<int, double>*/
class stack<Date*, Date*> {//在类名后面加括号。写函数模板特化和类模板特化,前面必须有普通的模板,必须有主模板声明void fun() {std::cout << "int模板" << std::endl;}};
偏特化(半特化)
偏特化不仅仅只是对像缺省参数一样对其中的模板参数进行特化,它还可以限制全部或者个别参数必须为指针,引用,但是指针和引用的类型不限制
//同样偏特化前面也必须有主模板的声明
template<class T>
class stack<T, int> {void sun() {std::cout << "stack<T, int>半特化" << std::endl;}};template<class T1,class T2>
class stack<T1*,T2*> {///这就是为什么叫偏特化,可以进一步限制,传的只能是指针,但是什么指针都可以。void sun() {std::cout << "stack<T1*,T2*>半特化" << std::endl;}};template<class T1, class T2>
class stack<T1&, T2&> {///这就是为什么叫偏特化,可以进一步限制,传的只能是指针,但是什么指针都可以。void sun() {std::cout << "stack<T1&,T2&>半特化" << std::endl;}};template<class T2>
class stack<int, T2&> {///这就是为什么叫偏特化,可以进一步限制,传的只能是指针,但是什么指针都可以。void sun() {std::cout << "stack<int,T2&>半特化" << std::endl;}};
特点类名后面的模板参数必须和主模板声明一样多。不能只写一个就不叫模板特化
特化类模板来实现排序
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 std::ostream& operator<<(std::ostream& _cout, const Date& d);
private:int _year;int _month;int _day;
};
std::ostream& operator<<(std::ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}template<class T>
class less {
public:bool operator()(const T& x, const T& y) {return x < y;}};//利用类模板的特化std::vector<Date*> d1;Date* s1 = new Date(2025, 1, 3);Date* s2 = new Date(2025, 1, 1);Date* s3 = new Date(2025, 1, 2);Date* s4 = new Date(2025, 1, 6);d1.push_back(s1);d1.push_back(s2);d1.push_back(s3);d1.push_back(s4);std::sort(d1.begin(), d1.end(),less<Date*>());//开始我写成这个less(Date*),语法混乱了for (auto& e : d1) {std::cout << e << " ";}std::cout << std::endl;
这个还是排的指针,并不能排日期的,所以我们可以特化less类模板,来实现
template<>
class less<Date*>{
public:bool operator()(const Date*& x, const Date* & y) {return x < y;}};

这里为什么会报错,因为Date*,const修饰的是Date*,要进行类型转换,生成一个临时的变量,引用不能存储临时变量
解决方案有多种
- 在*后面加const,因为类模板特化类里面可以不一样
template<>
class less<Date*>{
public:bool operator()(const Date* const & x, const Date* const & y) {return *x < *y;}};
2.接受一样的类型,不进行转换
template<>
class less<Date*> {
public:bool operator()(Date* x, Date* y) {return *x < *y;}};
3.使用半特化,指定指针
template<class T>
class less<T*> {
public:bool operator()(T* x, T* y) {return *x < *y;}};
为什么模板的声明和定义不能分离到两个文件




这里会发生链接错误,add找不到定义,为什么找不到定义了
首先编译器要进行 预处理:
a.h a.cpp a.main
预处理:头文件展开/条件编译什么if都编译为指令/宏替换/去掉注释
a.i main.i
编译:检查语法和生成汇编代码
a.s main.s
汇编:把汇编码转换成二进制机械码。
链接:合成可执行文件,链接其他文件定义的函数和变量等

解决方法:
1.在定义文件中,显示实列化
template<class T>
T add(T& x, T& y) {return x + y;
}
template//显示实列化
int add(int& x, int& y);
但是这样很麻烦,如果要传double,又要实列化一个double
2.还是声明和定义都放在一个文件里面头文件.




