重生之C++学习:模板初阶_成分不含牛奶的博客-CSDN博客
前面我们已经开始了模版的学习,但是学习的内容比较容易,和单一,现在我们继续加强模版的学习吧
目录
非类型模版参数
将非类型模版参数之前,我们先来讲一下什么是类型模版参数,其实就是class后的参数名,
下面是一个传统的场景,定义一个静态变量
#define N 10000
template<class T>
class Stack {
private:
T _a[N]; // 我们假设有个静态的栈
};
void test_template() {
// 在这个场景下无法实现,所以cpp引入了一个非类型模版参数
Stack<int> st1; // 假设需要10个空间
Stack<double> st2; // 假设需要100个空间
}
当我们引入非类型模版参数 N,就可以控制栈的类型和大小了(好像也可以传缺省值)
template<class T,size_t N = 10> // class T 为类型模版参数
class Stack {
private:
T _a[N];
};
void test_template() {
// 通过非类型模版参数 N 实现了静态栈开不同的空间,不用写死
Stack<int,20> st1; // 假设需要20个空间
Stack<double, 100> st2; // 假设需要100个空间
}
模版特化
模板特化是指在泛型编程中,为了提高程序的效率和减少代码的冗余,对某些特定类型的数据进行特殊处理的一种技术。通过模板特化,可以为某些特定类型的数据提供更高效的算法实现,从而提高程序的性能。特化也分为半特化和全特化
类模版特化
在代码中,如果对应的是int,double这个特别说明的类型,进行特化(有点类似找到最匹配的)
// 类模版特化
template<class T1, class T2>
class Data {
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _data1;
T2 _data2;
};
// 下面是上面类模版的特化
template<> // 语法规定
class Data<int, double> { // 这个也叫全特化
public:
Data() { cout << "Data<int, double>" << endl; }
};
// 偏特化
template<class T1>
class Data<T1, double> {
public:
Data() { cout << "Data<T1, double>" << endl; }
};
函数模版特化
我们通过日期类,来进行函数模版特化的学习,函数模版特化跟类模版特化几乎一致
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);
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 << "如果小于返回1: ";
return left < right;
}
// 函数特化后
template<>
bool Less<Date*>(Date* left, Date* right) {
cout << "特化后小于返回1: ";
return *left < *right;
}
void test_template2() {
Date* d1 = new Date(2023, 10, 8);
Date* d2 = new Date(2023, 10, 9);
cout << Less(d1, d2) << endl;
// 未特化前为指针地址的比较,指针开辟的地址前后随机大小
// 特化后通过解引用,直接对着d1,d2对应的日期进行比较
}
但是回顾我们函数重载的学习,我们也可以直接利用函数重载来实现和函数特化一样的功能
并且函数特化有时候也会出现一些奇奇怪怪的问题 ,比如实际开发时我们一般传入的是变量的引用来减少拷贝带来的效率影响,那么就需要传入const类型,但是特化时
template<class T>
bool Less(const T& left, const T& right){
return left < right;
}
// 特化一下
template<>
bool Less<Date*>(Date* const & left, Date* const & right){
return *left < *right;
}
会报错,而将代码的const的位置 由修饰left对应的数据 改为 修饰这个left指针本身
问题就神奇般的解决了,不过看起来就有点奇怪,而且也无法实现const作用于指针下的数据,也就是我们能在这个特化的Less里面修改*left 和 *right
综合这些问题我们最后还是主要用函数重载
因此:类模版可以用特化,函数模版经理不要特化
模版的声明与定义
模板的声明和定义都需要放在头文件中。模板的声明包括模板的参数列表和函数返回类型,而模板的定义则包括函数体。在编译器处理模板实例化时,需要同时看到模板的声明和定义,因为只有实例化时才能确定模板参数的具体类型,从而生成相应的函数定义。如果模板的声明和定义分别放在不同的文件中,编译器在处理模板实例化时就无法找到相应的函数定义,从而导致链接错误。因此,为了避免这种错误,通常将模板的声明和定义都放在头文件中。
如果需要声明和定义分开,则需要使用“显示实例化”,就是需要再定义处,给模版确定类型,如设置为int,设置为double,设置为char,类似于学习函数重载前设置若干个类型的函数接口,这样就导致有点冗余了,所以声明和定义一般就放在同一个地方