目录
非类型模板参数
C++11的静态数组容器-array
按需实例化
模板的特化
函数模板特化
类模板特化
全特化与偏特化
模板的分离编译
总结
非类型模板参数
基本概念:模板参数类型分为类类型模板参数和非类类型模板参数
- 类类型模板参数:跟在class 或 typename之后的形参
- 非类类类型模板参数:用一个常量作为类模板的参数
功能:编译时合理分配大小
#include <iostream>
using namespace std;namespace bit
{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;};
}int main()
{bit::array<int> a1;bit::array<int, 10> a2;bit::array<int, 100> a3;return 0;
}
注意事项:
1、浮点数、类类型的对象及字符串不允许作为非类类型模板参数
2、非类类型模板参数是在编译时传参,函数参数是在运行时传参
3、函数参数(T 对象1,T 对象2)| 模板参数<class 类型1,class 类型2>
C++11的静态数组容器-array
array文档:<array> - C++ Reference (cplusplus.com)
#include <iostream>
#include <assert.h>
#include <array>
using namespace std;int main()
{std::array<int, 10> a1;int a2[10];//越界读,检查不出来a2[10];//越界写,抽查,局限多,很多位置查不出来(x86环境下运行a2[15]不报错,a[10] = 1报错)a2[15] = 1;//任意读写越界都能检查出来a1[10];//报错return 0;
}
优点: 可以避免数组越界问题(但实际上这些内容vector就可以做到,array没啥用)
std::vector<int> v1(10,0);
v1[10]//v1[10]也可以检测出
缺点:会出现栈溢出问题
std::array<int,1000000> a3;//报错
std::vector<int> v2(1000000,0);//正确
按需实例化
include <iostream>
#include <array>
#include <vector>
#include <assert.h>
using namespace std;namespace bit
{template<class T, size_t N = 10>class array{public:T& operator[](size_t index){assert(index < N);size(1);//语法错误,但是不报错return _array[index];}const T& operator[](size_t index)const{assert(index < N);return _array[index];}size_t size()const{return _size;}bool empty()const{return 0 == _size;}private:T _array[N];size_t _size;};
}int main()
{bit::array<int> a1;cout<<a1.empty()<<endl;//不报错a1[1];//增加一个调用a1[1]时报错return 0;
}
运行上述代码后不会报错,即使size(1)是一个语法错误(size函数不需要传参),这是因为
- 在编译器遇到模板时,预处理阶段后不会直接编译,而是根据模板的“蓝图” + 传入模板的参数类型等内容,将模板进行实例化后才会进行编译阶段(调试时是已经经历了四个阶段的)
- 在实例化类模板时会进行按需实例化,即调用哪个成员函数就实例化哪个(调用empty就仅实例化该函数)
没有报错的本质就是没有调用operator[],所以operator[]中的错误不会被检查出来,只有调用才会细致检查语法错误
- 当然编译器还是会大体框架进行检查,比如少了}多个}之类的错误
模板的特化
基本概念:在原模板类的基础上,针对特殊类型所进行特殊化处理
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到
一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板:
#include <iostream>
using namespace std;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;
};ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}// 函数模板 -- 参数匹配
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, 6);cout << Less(d1, d2) << endl; // 可以比较,结果正确(会调用日期类对象的<重载)Date* p1 = &d1;Date* p2 = &d2;//(传入日期类对象的指针进行比较)cout << Less(p1, p2) << endl; // 可以比较,结果错误return 0;
}
p1指向的d1显然大于p2指向的d2对象,但是Less内部并没有比较p1和p2指向的对象内容,
而比较的是p1和p2指针的地址,因而无法达到预期而错误
函数模板特化
注意事项:
- 1、必须要现有一个基础的函数模板
- 2、template后接一个空的尖括号<>
- 3、函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 4、函数形参顺序必须要和模板函数的基础参数类型完全相同,若不同则编译器可能报错
#include <iostream>
using namespace std;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;
};ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}//函数模板
template<class T>
bool Less(T left, T right)
{cout << "bool Less(T left, T right)" << endl;return left < right;
}特化(但是该特化还是会出错)
//template<>
//bool Less(Date* left, Date* right)
//{
// cout << "bool Less(T* left, T* right)" << endl;
// return *left < *right;
//}//不会出错但是不符合特化定义
template<class T>
bool Less(T* left, T* right)
{cout << "bool Less(T* left, T* right)" << endl;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 = new Date(2022, 7, 7);Date* p2 = new Date(2022, 7, 8);cout << Less(p1, p2) << endl; // 可以比较,结果错误int* p3 = new int(3);int* p4 = new int(4);cout << Less(p3, p4) << endl; //可以比较,结果错误return 0;
}
~~依然是哪个类型更匹配就用哪个 ~~
结论:函数模板不建议特化do且函数模板特化的使用场景少
类模板特化
全特化与偏特化
全特化:将模板参数列表中的所有参数都确定化
偏/半特化:将模板参数列表中的部分参数确定化
- 偏特化又分为:“部分特化” 和“参数更进一步的限制”
#include <iostream>
using namespace std;//Data类模板
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;}
};//偏/半特化(部分特化)
template<class T1>
class Data<T1, char>
{
public:Data(){cout << "Data <T1,char>" << endl;}
};//偏/半特化(对参数进一步限制)
template<class T1,class T2>
class Data<T1*, T2*>
{
public:Data(){cout << "Data <T1*,T2*>" << endl;}
};//偏/半特化(对参数进一步限制)
template<class T1, class T2>
class Data<T1&, T2*>
{
public:Data(){cout << "Data <T1&,T2*>" << endl;}
};int main()
{Data<int, int> d1;Data<int, char> d2;Data<char, char> d3;Data<char*, char*> d4;Data<int*, char*> d5;Data<int*, string*> d6;Data<int&, string*> d7;return 0;
}
匹配机制:有现成的就用现成的,没有现成的就用那个最合适的
注意事项:如果传入的是Data*,特化时的T*就会变成Date*而不是Data**,T*是一个整体
模板的分离编译
基本概念:一个程序/项目由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式
假如有以下场景,模板声明和定义分离,在头文件中进行声明,在源文件中进行定义:
//a.h文件
tempate<class T>
T Add(const T& left,const T& right);//a.cpp文件
template<class T>
T Add(const T& left,const T& right)
{return left + right;
}//main.cpp
#include "a.h"
int main()
{Add(1,2);Add(1.0,2.0);return 0;
}
22、2:33处
注意事项:模板的声明和定义支持分离,但不支持分离在两个文件(STL库中的模板的定义和分离都是在.h文件中的)
总结
优点:
- 复用了代码,节省资源,更快的迭代开发,C++的标准模板库也因此而生
- 增强代码灵活性
缺点:
- 模板会导致代码膨胀问题,也会导致编译时间变长
- 出现模板编译错误时,错误信息十分混乱,不易定位
~over~