1. 非类型模板参数
定义一个数组类
c
#include<iostream>
using namespace std;
#define N 1000
namespace zjw
{
// 定义一个模板类型的静态数组
template<class T>
class array
{
public:
T& operator[](size_t index) { return _array[index]; }
const T& operator[](size_t index)const { return _array[index]; }
size_t size()const { return _size; }
bool empty()const { return 0 == _size; }
private:
T _array[N];
size_t _size=N;
};
}
我们需要两个对象,分别数组大小为10,和1000的话,只用上面一个类是不够的,两个类给不同的N值.
这里我们可以用非类型模板参数来解决这个问题,只需要一个类。
在这之前有一个知识点
c
#include<iostream>
using namespace std;
#include<iostream>
using namespace std;
#define N 1000
namespace zjw
{
// 定义一个模板类型的静态数组
template<class T>
class array
{
public:
T& operator[](size_t index) { return _array[index]; }
const T& operator[](size_t index)const {
size(1);//这里故意写错
return _array[index]; }
size_t size()const { return _size; }
bool empty()const { return 0 == _size; }
private:
T _array[N];
size_t _size = N;
};
}
int main()
{
zjw::array<int>st1;
cout << st1.size() << endl;
}
size(1);//这里故意写错,但是在实例化st1,它只对要用的函数实例化,并不会检查其他类成员函数内部有没有错误。在operator[]中,size(1);//这里故意写错,就不会报错。而编译器只会对大体的结构进行检查,比如说哪个类成员函数少一个分号。
模板参数分类类型形参与非类型形参。
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
c
#include<iostream>
using namespace std;
namespace zjw
{
// 定义一个模板类型的静态数组
template<class T, size_t N = 10>
class array
{
public:
T& operator[](size_t index) { return _array[index]; }
const T& operator[](size_t index)const { return _array[index]; }
size_t size()const { return _size; }
bool empty()const { return 0 == _size; }
private:
T _array[N];
size_t _size=N;
};
}
浮点数、类对象以及字符串是不允许作为非类型模板参数的。c++20可以
非类型的模板参数必须在编译期就能确认结果。
c
#include<iostream>
using namespace std;
namespace zjw
{
template<string str>
class A
{
};
}
int main()
{
zjw::A<"11111">st3;
}
2. 类模板的特化
上篇文章我们使用仿函数来给日期类比较大小,现在我们可以使用函数模板来给日期类比较大小
先定义一个日期类
c
class Date
{
public:
friend ostream& operator<<(ostream& _cout, const Date& d);
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);
}
private:
int _year;
int _month;
int _day;
};
然后实现函数模板比较大小
c
函数模板
template<class T>
bool Less(T left, T right)
{
return left < right;
}
主函数
c
int main()
{
Date d1(2024, 2, 10);
Date d2(2024, 3, 10);
cout << Less(d1, d2) << endl;
}
此时d1<d2返回1.
实例化过程如下:
如果我们要给日期类指针进行比较大小
这里我们可以用特化
函数模板的特化步骤:
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
c
class Date
{
public:
friend ostream& operator<<(ostream& _cout, const Date& d);
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);
}
private:
int _year;
int _month;
int _day;
};
template<class T>
bool Less(T left, T right)
{
return left < right;
}
// 特化,针对某些特殊类型可以进行特殊处理
template<>
bool Less<Date*>(Date* left, Date* right)
{
return *left < *right;
}
int main()
{
Date d1(2024, 2, 10);
Date d2(2024, 3, 10);
Date* p1 = new Date(2022, 7, 7);
Date* p2 = new Date(2022, 7, 8);
cout << Less(p1,p2) << endl;
}
或者这样类似函数重载,匹配最符合的,有现成的使用现成的
c
template<class T>
bool Less(T left, T right)
{
return left < right;
}
bool Less(Date* left, Date* right)
{
return *left < *right;
}
特化,针对某些特殊类型可以进行特殊处理
类模板的特化
全特化:全特化即是将模板参数列表中所有的参数都确定化
c
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
// 全特化
template<>
class Data<int, char>
{
public:
Data() { cout << "Data<int, char>" << endl; }
};
int main()
{
Data<int, char> d2;
}
半特化->部分特化
将模板参数类表中的一部分参数特化。
c
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, char>
{
public:
Data() { cout << "Data<T1, char>" << endl; }
};
半特化->参数更进一步的限制
偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版
本。
c
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
// 半特化/偏特化,不一定是特化部分参数,可能是对参数的进一步限制
template<class T1, class T2>
class Data<T1*, T2*>
{
public:
Data() { cout << "Data<T1*, T2*>" << endl; }
};
c
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
template<class T1, class T2>
class Data<T1&, T2*>
{
public:
Data() { cout << "Data<T1&, T2*>" << endl; }
};
5. 模板的分离编译
首先声明定义不分离:不会出现问题
声明定义分离:
链接出现问题,原因是在源.cpp中调用st1.size()会call这个函数的地址,在编译时候,去array.h中发现他只有声明,编译器会以为函数地址会在其他cpp中,等到链接的时候在call size函数地址,但是等到链接时,在cpp之间是分离的,,所以源.cpp中st1实例化的参数<int,10>不会给array.cpp中<T,N>;模板没有实例化,所以array.cpp中的size()函数没有地址,出现了链接错误。
解决方法:显示实例化
反汇编观察size()函数地址,call size()函数地址说明要调用该函数