目录
前言
在前面的文章中,我们初步的介绍了模板是什么,以及模板的用法;如果你还并不了解,下面贴出了模板初阶的一些内容;本文我们将介绍模板另外的一些用法;当然模板的用法有很多,并不是一两篇文章可以介绍完的,有一些模板语法的使用需要结合一些具体的场景,后面会逐步一一介绍;
一、非类型模板参数
1、非类型模板参数的引入
前面我们为了解决不同类型使用同一个类的问题,我们引出了类型模板参数的概念,除了类型模板参数,其实还有非类型模板参数;例如下面一段代码;
const int N = 10;
template<class T>
class Array
{
public:
private:
T array[N];
};
int main()
{
Array<int> arr1; // 大小为10
return 0;
}
我们完美的用模板实现了不同类型的数据想用同一个类的问题;其中大小我们可以用定义的常量来控制;可是如果我们想让两个对象的大小不同呢?那不又回到了当初我们想实现一个能构建不同类型的对象的问题上来了;因此,C++引出了非类型模板参数的用法;
2、非类型模板参数的使用
我们将上述代码进行更改,使用非类型模板参数;如下所示;
template<class T, int N = 10>
class Array
{
public:
private:
T array[N];
};
int main()
{
Array<int> arr1; // 大小为默认缺省值10
Array<int, 10> arr2; // 大小为传入的10
Array<double, 20> arr3; // 大小为传入的20
return 0;
}
3、非类型模板参数的注意事项
在使用非类型模板参数时,我们需要注意以下几点;
1、非类型模板参数只能是整型家族的类型
2、非类型的模板参数必须在编译期就能确认结果
二、模板的特化
1、模板特化的引入
在一些特殊情况时,对某些类型模板的处理方式并不符合我们的预期结果;这时,我们可以对这个模板实现一种特化的版本;如下所示;
class Date
{
public:
Date(int year = 1970, 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 x, T y)
{
return x < y;
}
int main()
{
Date d1(2023, 7, 9);
Date d2(2023, 7, 10);
cout << Less(d1, d2) << endl;
// 明显与我们预估的比较结果不同
Date* p1 = &d1;
Date* p2 = &d2;
cout << Less(p1, p2) << endl;
return 0;
}
2、函数模板特化
函数模板特化步骤:
1、关键字template后面接一对尖括号
2、函数名后接一对尖括号,尖括号里指定需要被特化的类型
// 比较函数模板
template<class T>
bool Less(T x, T y)
{
return x < y;
}
// 函数模板特化
template<>
bool Less<Date*>(Date* x, Date* y)
{
return *x < *y;
}
注意:
1、函数模板特化必须有原模板,特化模板不能单独存在,原模板与特化模板同时存在时,调用优先匹配函数模板特化;
2、以上函数模板的特化为全特化,函数模板没有半特化(全特化、半特化概念后面提)
3、类模板特化
类模板特化分为全特化与半特化,下面分别介绍全特化与半特化;
(1)全特化
全特化即是将模板参数列表中所有的参数都确定化;如下所示
特化步骤:
1、将关键字template后尖括号中的模板参数名与class/typename去掉;
2、在类名后加加括号,并将替换类型加上
3、更改类内特化类型
// 原类模板
template<class T1, class T2>
class A
{
public:
A(T1 a, T2 b)
:_a(a)
,_b(b)
{
cout << "class A" << endl;
}
private:
T1 _a;
T2 _b;
};
// 全特化
template<>
class A<int ,char>
{
public:
A(int a, char b)
:_a(a)
, _b(b)
{
cout << "class A<int ,char>" << endl;
}
private:
int _a;
char _b;
};
(2)偏特化
偏特化指的是任何针对模版参数进一步进行条件限制设计的特化版本。偏特化分为两种,以下分别介绍;
a、部分特化(将部分参数特化)
// 部分特化
template<class T2>
class A<int, T2>
{
public:
A(int a, T2 b)
:_a(a)
, _b(b)
{
cout << "class A<int, T2>" << endl;
}
private:
int _a;
T2 _b;
};
b、参数进一步限制(指定为引用/指针)
// 参数进一步限制
template<class T1, class T2>
class A<T1*, T2*>
{
public:
A(T1* a, T2* b)
:_a(a)
, _b(b)
{
cout << "class A<T1*, T2*>" << endl;
}
private:
T1* _a;
T2* _b;
};
template<class T1, class T2>
class A<T1&, T2&>
{
public:
A(T1& a, T2&b)
:_a(a)
, _b(b)
{
cout << "class A<T1&, T2&>" << endl;
}
private:
T1& _a;
T2& _b;
};
我们可通过以下各种类型调用,观察调用特点;
int main()
{
int n1 = 10;
int n2 = 20;
int* p1 = &n1;
int* p2 = &n2;
int& ra1 = n1;
int& ra2 = n2;
A<double, double> a1(1, 2);
A<int, double> a2(n1, n2);
A<int*, int*> a3(p1, p2);
A<int&, int&> a4(ra1, ra2);
return 0;
}
我们发现无论哪种模板调用,总会调用最接近的模板,偏特化>全特化>原模板;
三、模板的分离编译
模板是否可以分离编译呢?在我们以前的C语言代码中,我们常常将声明与定义分离在两个不同的文件中,声明在.h文件中,定义在.c文件中,而我们前面在编译C++代码时,却很少看到声明与定义分离编译;要搞清这个问题,首先要清楚文件编译的过程,如果不清楚文件编译过程的可点击下方传送门,大体了解编译一个项目各个文件的过程;
1、模板分离编译的报错
假如以下各个文件中有以下代码;
//-----------Cal.h文件-----------//
// 带模板函数
template<class T>
T add(const T x, const T y);
// 不带模板函数
void func();
//-----------Cal.cpp文件-----------//
#include "Cal.h"
template<class T>
T add(const T x, const T y)
{
return x + y;
}
void func()
{
1 + 2;
}
//-----------Test.cpp文件-----------//
#include "Cal.h"
int main()
{
int x = 3;
int y = 6;
cout << add(x, y) << endl;
func();
return 0;
}
由于模板在编译阶段无法实例化,也就无法生成地址,因此在后面链接时,发现add函数仍然没有地址;所以会发生连接错误;如下图;
2、解决方案
解决方案有如下两种;
(1)在模板定义的位置进行特化
//-----------Cal.cpp文件-----------//
#include "Cal.h"
template<class T>
T add(const T x, const T y)
{
return x + y;
}
void func()
{
1 + 2;
}
// 显示特化
template<>
int add<int>(const int x, const int y)
{
return x + y;
}
这种方案的缺点也很明显,当我们还需要别的类型时,我们需要在添加一段别的类型的特丽华;(不推荐)
(2)将模板的定义与声明放在同一个文件中(推荐)
这种方案即一开始我们实现string、vector等使用的方案;此处就不过多介绍;