前言
本篇博客讲解c++中的模板的一些其他知识
💓 个人主页:普通young man-CSDN博客
⏩ 文章专栏:C++_普通young man的博客-CSDN博客
⏩ 本人giee: 普通小青年 (pu-tong-young-man) - Gitee.com
若有问题 评论区见📝
🎉欢迎大家点赞👍收藏⭐文章
目录
概述
C++模板是一种强大的功能,它允许开发者编写泛型代码,从而提高代码的重用性和灵活性。本文将探讨非类型模板参数的概念及其限制,以及模板特化的方法,包括函数模板特化和类模板特化。
1. 非类型模板参数
非类型模板参数是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。非类型模板参数必须在编译期就能确认结果,并且有一些限制,例如:
- 浮点数、类对象以及字符串不允许作为非类型模板参数。
- 非类型的模板参数必须在编译期就能确认结果。
template<class T,size_t size = 10>
int add(T a1) {
return a1 + size;
}
template<class T,size_t N = 10>
class MyClass
{
public:
MyClass(const T& val)
:arr[0] = val;
{}
private:
int arr[N];
};
void test1() {
//cout << add<>(10) << endl;
MyClass<int> s1(10);
}
int main() {
test1();
return 0;
}
2. 模板的特化
模板特化是在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化分为函数模板特化与类模板特化。
2.1 函数模板特化
函数模板特化是当函数模板在处理某些特定类型时可能无法得到预期结果的情况下使用的。特化过程包括以下几个步骤:
- 必须要先有一个基础的函数模板。
- 使用关键字
template
后面接一对空的尖括号<>
。- 在函数名后跟一对尖括号,尖括号中指定需要特化的类型。
- 函数形参表必须与模板函数的基础参数类型完全相同。
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;
};
class DateLess
{
public:
//bool operator()(Date* p1, Date* p2)
//{
// return *p1 < *p2;
//}
};
//模板特化
template<class T>
bool lessfunc(const T& left, const T& right) {
return left < right;
}
//特化--不推荐
//template<>
//bool lessfunc<Date*>(Date* const& left, Date* const& right)
//{
// return *left < *right;
//}
//
//template<>
//bool lessfunc<const Date*>(const Date* const& left,const Date* const& right)
//{
// return *left < *right;
//}
//推荐直接写成函数
bool lessfunc(Date* p1, Date* p2) {
return *p1 < *p2;
}
bool lessfunc(const Date* p1,const Date* p2) {
return *p1 < *p2;
}
void test2() {
//cout << lessfunc(1, 2) << endl;
//int a = 100, b = 20;
//int* pa = &a;
//int* pb = &b;
//cout << lessfunc(pa,pb) << endl;//比较错误
Date d1(2025, 7, 20);
Date d2(2022, 7, 8);
cout << lessfunc(d1, d2) << endl;
Date* p1 = &d1;
Date* p2 = &d2;
cout << lessfunc(p1, p2) << endl;//比较错误
}
int main() {
test2();
return 0;
}
这里为什么不推荐写成函数特化,就是因为易读性不好,写函数的话直接一点,简单易操作
- 代码冗余:特化版本的函数可能需要重复原始模板函数的部分逻辑,这可能导致代码冗余。
- 理解难度:特化版本的存在可能会使代码的逻辑变得模糊不清,尤其是当多个特化版本同时存在时,读者需要花费更多的时间去理解哪个版本会被调用。
- 调试困难:当出现问题时,调试特化版本可能比调试普通函数更加困难,因为需要考虑特化版本与模板之间的交互。
- 过度特化:过度使用特化可能导致代码膨胀,并且增加维护成本。
2.2 类模板特化
类模板特化分为全特化和偏特化。
2.2.1 全特化
全特化是将模板参数列表中所有的参数都确定化。
//全特化
template<>
class Data<int,int>
{
public:
Data() { cout << "data<int,int>" << endl; }
};
2.2.2 偏特化
偏特化是指对模板参数进行进一步的条件限制设计出的特化版本。偏特化有两种表现形式:部分特化和参数更进一步的限制。
//偏特化/半特化//全特化
template<class T1>
class Data<T1, int>
{
public:
Data() { cout << "data<T1,int>" << endl; }
};
参数更进一步的限制 是针对模板参数的类型进行更严格的限制。例如,将两个参数特化为指针类型:
template<>
class Data<int*, int*>
{
public:
Data() { cout << "data<int*,int*>" << endl; }
};
template<>
class Data<int&, int&>
{
public:
Data() { cout << "data<int&,int&>" << endl; }
};
类模板特化应用示例
我们可以通过一个例子来看一下如何使用类模板特化来解决特定的问题。假设我们有一个用于比较的类模板 Less
,它可以用于直接比较日期对象,但是当比较指针时则会比较指针的地址而非指针指向的内容。我们可以使用类模板特化来解决这个问题。
#include<algorithm>
#include<vector>
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>
class Less {
public:
//仿函数
bool operator()(const T& a1,const T& a2) {
return a1 > a2;
}
};
//特化
template<class Data>
class Less <Data*>{
public:
//仿函数
bool operator()(Data* a1, Data* a2) const{
return *a1 > *a2;
}
};
int main() {
Date d1(2024, 1, 1);
Date d2(2024, 11, 1);
Date d3(2024, 10, 1);
//vector<Date> s1;
//s1.push_back(d1);
//s1.push_back(d2);
//s1.push_back(d3);
//sort(s1.begin(), s1.end(), Less<Date>());
vector<Date*> s1;
s1.push_back(&d1);
s1.push_back(&d2);
s1.push_back(&d3);
sort(s1.begin(), s1.end(), Less<Date*>());
return 0;
}
其实就可以发现,特化一般是在一些特殊情况进行一个特殊处理
3. 模板分离编译
分离编译模式是指一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程。
在使用模板时,如果模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义,则需要特别注意编译过程。解决方法包括:
- 将声明和定义放到一个文件
"xxx.hpp"
或者"xxx.h"
中。这是推荐的做法。 - 显式实例化模板定义的位置。这种方法不实用,不推荐使用。
故事背景
假设你正在筹备一场盛大的聚会,你需要向不同的供应商订购所需物品和服务。为了组织好这场聚会,你决定采用一种高效的协调方式——分离编译模式。
制定采购清单:首先,你需要制定一份采购清单,列出所有需要的物品和服务。这份清单相当于模板的声明,它定义了你需要什么,但没有具体到哪家供应商。
发送询价单:接下来,你将向各个供应商发送询价单,这些询价单包含了具体的数量和要求。每份询价单相当于模板的具体定义,它们包含了特定的实现细节。
供应商报价:供应商会根据你的询价单进行报价。这个过程类似于编译阶段,供应商评估需求并给出价格。
签订合同:如果供应商的报价符合你的预算,你们将签订合同。合同相当于生成的目标文件,它明确了双方的责任和义务。
完成交易:最后,供应商会根据合同提供货物和服务,你则支付费用。这相当于链接阶段,将所有合同(目标文件)组合在一起形成一个完整的交易过程。
分离编译模式的应用
现在,让我们看看如何将这个故事映射到模板分离编译上:
声明和定义放在同一个文件:这就像你将采购清单和询价单合并到一起,直接交给供应商。这种方式简单明了,因为供应商不需要在不同的地方查找你的清单和询价单详情。同样地,在编程中,如果你将模板的声明和定义放在同一个文件(例如
xxx.hpp
)中,那么编译器可以在一个地方找到所有需要的信息,无需额外的链接步骤。显式实例化:这类似于你在采购清单中明确指出你需要从哪家供应商订购多少数量的商品,并且直接发送询价单。在编程中,这意味着你需要在源文件中显式地实例化模板,告诉编译器你需要哪些具体类型的实例。然而,这种方法在实际操作中并不常见,因为它增加了额外的工作量并且可能导致代码冗余。
这边举了一个例子来更好的理解为什么声明和定义放在两个文件会报错
解决方法
1.模板的声明和定义都在同一个文件(推荐)
2.显示实例化<这一步就是在链接的时候告诉编译器类型>(不推荐,容易代码膨胀)
这边的template不加<>是为了和特化区分(可以理解为文件在链接的时候告诉它这个文件里的模板类型)
总结
模板是C++语言的重要特性,它提供了代码重用的强大手段。然而,使用模板也需要注意其局限性,如非类型模板参数的限制、模板特化的正确使用等。通过合理利用模板特化,我们可以解决特定类型的问题,使得代码更加灵活和高效。