🌟 各位看官好,我是egoist2023!
🌍 种一棵树最好是十年前,其次是现在!
🚀 今天来学习非类型模板及特化的相关知识。
👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦!
目录
非类型模版参数
在 模版【上】章节中模版参数当作类型来处理,实际上模版参数还有非类型模版参数
#define N 10
template<class T>
class myvector
{
public:
//...
private:
T _arr[N];
int _capacity;
};
int main()
{
myvector<int> v1;
myvector<double> v2;
return 0;
}
在上面这段程序当中,N的大小是确定的,即如果要插入100个数据时,N是10是不够的,需要手动将N的大小进行修改;当要插入10个数据时,N是100又太多了,导致很多空间被浪费。因此槽点很多,如果使用非类型模版参数可以改善这种问题。
template<class T,size_t N>
class myvector
{
public:
//...
private:
T _arr[N];
int _capacity;
};
此时N的大小我们可以自行决定,此时一个double类型的vector要开100个空间
myvector<double, 100> v;
int类型的vector要开10个空间
myvector<int, 10> v;
实际上,在C++库中的array(数组)也是这样处理的,那它又和int array[N]有什么区别呢?
C语言中的int array对越界问题检查实际是不严格的。
C++中array对越界问题的检查。
需要注意的是
- 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
- 非类型的模板参数必须在编译期就能确认结果。
特化
template<class T> bool Less(T left, T right) { return left < right; } int main() { cout << Less(1, 2) << endl; Date d1(2025, 3, 1); Date d2(2025, 3, 2); cout << Less(d1, d2) << endl; return 0; }
结合之前Date类实现,如上一段程序中,可以对日期类进行正确比较。但是也有一些情景是需要特殊处理的。
在上面这段程序当中,明显d3>d4的,运行结果为1才对。但运行几次后发现有两个结果,为什么会出现0这个现象呢?
此时我们进行调试观察d3和d4。
可以发现d4的内存地址是大于d3的内存地址,这也解决了我们的疑惑:这里的比较是指针地址的比较,因此需要引入特化或提供函数支持Date比较 。
函数模版特化
//函数模版特化
template<>
bool Less<Date*>(Date* left, Date* right)
{
return *left < *right;
}
普通函数同样可以解决问题,且这种方式简单明了,难道不是更香吗?
bool Less(Date* left, Date* right)
{
return *left < *right;
}
我们再来看看传右值的情况(右值指的是匿名对象、常量等)
template<class T>
bool Less(const T& left, const T& right) //传右值-->左值引用右值要加const
{
return left < right;
}
//特化
template<>
bool Less<Date*>(Date* const & left, Date* const & right) //const修饰本身
{
return *left < *right;
}
int main()
{
cout << Less(new Date(2025, 3, 1), new Date(2025, 2, 28)) << endl;
return 0;
}
再针对右值的情况下,左值引用要引用右值就必须+const修饰,那在特化的时候,也需要进行对应修改。可以看到,模版函数特化在上面这段程序中显得特别坨,再来看看普通函数的实现。
bool Less(Date* const & left, Date* const & right) //const修饰本身
{
return *left < *right;
}
因此,为了提高代码的可读性,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。这样看特化好像无用武之地,别着急我们继续往下看。
类模版特化
全特化
template<class T1,class T2>
class Date
{
public:
Date()
{
cout << "Date<T1,T2>" << endl;
}
};
template<>
class Date<int*, int*>
{
public:
Date()
{
cout << "Date<int*,int*>" << endl;
}
};
template<>
class Date<int, int>
{
public:
Date()
{
cout << "Date<int, int>" << endl;
}
};
偏特化
template<class T>
class Date<T, char>
{
public:
Date()
{
cout << "Date<T,char>" << endl;
}
};
参数更进一步的限制
template<class T1,class T2>
class Date<T1*, T2*>
{
public:
Date()
{
cout << "Date<T1*,T2*>" << endl;
}
};
template<class T1, class T2>
class Date<T1&, T2*>
{
public:
Date()
{
cout << "Date<T1&,T2*>" << endl;
}
};
类模版特化的场景
针对日期进行排序
// 对Less类模板按照指针方式特化
template<>
struct Less<Date*>
{
bool operator()(Date* x, Date* y) const
{
return *x < *y;
}
};
模版分离编译
为什么在 模版【上】章节说到不建议模版声明与定义分离呢?这里做下解释。
我们知道C/C++程序运行一般要经历以下步骤:
- 预处理 --> 展开头文件、宏替换、条件编译、去掉注释等;
- 编译 --> 检查语法、生成汇编代码;
- 汇编 --> 将汇编代码转成二进制机器码;
- 链接 --> 合成可执行程序,链接函数地址等;
运行时链接出现了报错,我们来具体分析下原因在哪。
解决方案
- 显示实例化(但是不建议如果有多个类型)
func2(1);
func2(200.1);
- 不做声明和定义分离(避开找地址,在声明处直接有地址了)
总结
优点远大于缺点
【优点】1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生2. 增强了代码的灵活性【缺陷】1. 模板会导致代码膨胀问题,也会导致编译时间变长2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误