【C++】非类型模板参数、模板特化、模板的分离编译、模板总结
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
文章目录
一、非类型模板参数
模板参数分类类型形参与非类型形参。
类型形参出现在模板参数列表中跟在class或者typename之类的参数类型名称。
#define N 10;
//静态数组
template <class T>
class Array
{
private:
T _a[N];
}
如果是这样的话我们无法去灵活控制大小
int main()
{
Array<int> a1;
Array<int> a2;
return 0;
}
这都是固定的了写死的了所以这时候我们可以使用非类型模板参数
非类型形参就是用一个常量作为类(函数)模板的一个参数在类(函数)模板中可将该参数当成常量来使用。注意是常量可以给缺省
//非类型模板参数——常量
template<class T,size_t N>
class Array
{
private:
T _a[N];
}
int main()
{
Array<int,10> a1;
Array<double,100>a2;
return 0;
}
注意
非类型模板参数只支持整型浮点数、类对象以及字符串是不允许作为非类型模板参数的
非类型的模板参数必须在编译期就能确认结果
实际上库里面的array也是非类型模板
库里面的array与C语言的数组相比
int main()
{
int a1[10];
array<int,10> a2;
array<double,10> a3;
return 0;
}
区别在于array可以对越界进行检查C语言数组对于越界读是不检查的而对于越界写是抽查的(不同平台不一样)。而array可以assert检查是否越界。
二、模板特化
1.函数模板特化
通常情况下使用模板可以实现一些与类型无关的代码但对于一些特殊类型的可能会得到一些错误的结果
我们来以日期类为例子
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(2023, 1, 6);
Date d2(2023, 1, 9);
cout << Less(d1, d2) << endl;
Date* p1 = &d1;
Date* p2 = &d2;
cout << Less(p1, p2) << endl;
return 0;
}
所以我们要去对Date*进行特殊化处理——Date*
函数模板的特化步骤
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同如果不同编译器可能会报一些奇怪的错误
//针对某些类型进行特殊处理——Date*
template<>
bool Less<Date*>(Date* left, Date* right)
{
return *left < *right;
}
函数模板也可以不写成模板直接写成函数也是可以的因为函数模板支持重载
2.类模板特化
1.全特化
全特化即是将模板参数列表中所有的参数都确定化
类模板的全特化将模板参数列表中的所有参数我们都将其写出来
如果此时的数据类型是我们自己定义的比如我们之前所说的Date*之时比较的是地址所以我们之前是通过自己写一个仿函数来实现比较大小的代码如下
struct PDateLess
{
bool operator()(const Date* d1, const Date* d2)
{
return *d1 < *d2;
}
};
struct PDateGreater
{
bool operator()(const Date* d1, const Date* d2)
{
return *d1>*d2;
}
};
void TestPriorityQueue()
{
//大堆
priority_queue <Date*, vector<Date*>, PDateLess> q3;
q3.push(new Date(2018, 10, 29));
q3.push(new Date(2018, 10, 28));
q3.push(new Date(2018, 10, 30));
cout << *q3.top() << endl;
//小堆
priority_queue<Date*, vector<Date*>, PDateGreater> q4;
q4.push(new Date(2018, 10, 29));
q4.push(new Date(2018, 10, 28));
q4.push(new Date(2018, 10, 30));
cout << *q4.top() << endl;
}
现在我们如果不写仿函数这时候就可以通过针对Date*实现特化了
template <class T>
class Greater
{
public:
bool operator()(const T& x, const T& y) const
{
return x > y;
}
};
//特化
template<>
class Greater<Date*>
{
public:
bool operator()(Date* const& d1, Date* const& d2) const
{
return *d1 > *d2;
}
};
int main()
{
hwc::priority_queue<Date*, vector<Date*>, Greater<Date*>> q4;
q4.push(new Date(2018, 10, 29));
q4.push(new Date(2018, 10, 28));
q4.push(new Date(2018, 10, 30));
cout << *q4.top() << endl;
return 0;
}
2.偏特化
偏特化任何针对模版参数进一步进行条件限制设计的特化版本。部分特化将模板参数类表中的一部分参数特化
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;
}
};
int main()
{
//模板
Data<int,int>d;
//偏特化
Data<double,char> d1;
Data<char,char> d2;
return 0;
}
偏特化可以对参数进一步的限制只要是指针不管是什么类型的指针针对指针也可以针对引用
//参数类型进一步限制
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()
{
return 0;
}
int main()
{
//指针
Data<char*, char*> d5;
Data<double*, int*> d6;
//引用
Data<double&,int&> d7;
return 0;
}
特化的本质体现的是编译器的参数匹配原则
三、模板的分离编译
模板的分离编译我们之前就有说过这里重新说一遍
分离编译一个程序项目由若干个源文件共同实现而每个源文件单独编译生成目标文件最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
而对于模板链接之前并不会交互分离编译就会导致用的地方.cpp没有实例化没有实例化就会导致链接不上。
比如a.h,a.cpp,test.cpp这三个文件
编译链接过程预处理——>编译——>汇编——>链接
预处理去注释宏替换头文件展开条件编译a.i,test.i
编译生成汇编代码(a.s,test.s)、符号汇总
汇编把汇编变成二进制目标文件(a.o,test.o)形成符号表
链接符号表的合并与重定位将多个obj文件合并成一个形成可执行程序
解决方案
- 将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h其实也是可以的。推荐使用这种。此时在编译阶段中就有了模板的实例化。
- 模板定义的位置显式实例化。这种方法不实用不推荐使用 。如果实例化的类型少那还是可行的如果要针对的类型很多那就太麻烦了
四、模板总结
优点 模板复用了代码节省资源更快的迭代开发C++的标准模板库(STL)因此而产生。增强了代码的灵活性。
缺点模板会导致代码膨胀问题也会导致编译时间变长。出现模板编译错误时错误信息非常凌乱不易定位错误 。