非类型模板参数
我们平时使用的模板都是和类型有关的,但是也有不是类型也能当做模板来使用,而是在tmplate<>
中定义一个变量并给定初始值,这种模板的参数称为非类型模板参数
例如在我们array
静态数组中,底层的实现就使用到了非类型模板参数
使用示例:
void test()
{
//定义10个int类型的静态数组
//这是在栈上申请的,第二个值不宜太大
//array<class T, size_t N>
array<int, 10> arr;
}
使用注意事项:
- 不允许使用浮点数、类对象以及字符串作非类型模板参数
- 非类型的模板参数必须在编译期就能确认结果(传值)
模板特化
模板的特化分为全特化和偏特化
全特化:所有的模板类型都是具体的类型
偏特化:即存在具体类型参数,又存在非模板类型参数
全特化
函数模板特化
平时我们用的模板都是非特化模板,例如我们写一个isEqual
判等函数。
template<class T>
bool isEqual(T a, T b)
{
return a == b;
}
使用示例:
但是我们发现它们都是同类型的,如果不是同类型就无法实现不同类型之间的判等功能;但是我们想通过一个函数名就能解决这些不同类型之间的问题。
例如我们想判断两字字符数组中的内容是否相同
void test()
{
char ptr1[] = "123";
char ptr2[] = "123";
int ret = isEqual(ptr1, ptr2);
cout << ret << endl;
}
输出结果是:false;原因是当数组当做实参来传参时,就会退化为指针,此时再利用之前的isEqaul函数比较的是他们之间的地址而非内容。但是我们又想通过这种个函数来判断他们内容是否相等,这时候就可以通到特化模板
注意:前提必须存在一个通用的模板
//特化模板
template<> //参数不写
//函数名后加<确定的类型>(具体类型参数)
bool isEqual<char*>(char* a, char* b)
{
return strcmp(a, b) == 0;
}
这时候就是相等的了
其实这里我们并非一定要使用特化模板,可以直接写一个普通函数,类型都为char*,所以特化模板用的比较少。
类模板特化
类模板特化的应用场景:类型萃取
我们看以下代码:
//通用类模板
template<class T1, class T2>
class D
{
public:
D(const T1& d1, const T2& d2)
:_d1(d1)
, _d2(d2)
{
cout << "D(T1, T2)" << endl;
}
T1 _d1;
T2 _d2;
};
//类模板特化
template<>
class D<int, char>
{
public:
D(const int& d1, const char& d2)
:_d1(d1)
, _d2(d2)
{
cout << "D(int, char)" << endl;
}
int _d1;
char _d2;
};
创建对象时会因为通过具体传入的类型模板而调用不同类型的模板类
偏特化
拿上面代码做比较
//通用类模板
template<class T1, class T2>
class D
{
public:
D(const T1& d1, const T2& d2)
:_d1(d1)
, _d2(d2)
{
cout << "D(T1, T2)" << endl;
}
T1 _d1;
T2 _d2;
};
//类模板全特化
template<>
class D<int, char>
{
public:
D(const int& d1, const char& d2)
:_d1(d1)
, _d2(d2)
{
cout << "D(int, char)" << endl;
}
int _d1;
char _d2;
};
//类模板偏特化
template<class T1>
class D<T1, double>
{
public:
D(const T1& d1, const double& d2)
:_d1(d1)
, _d2(d2)
{
cout << "D(T1, double)" << endl;
}
T1 _d1;
double _d2;
};
运行结果:
偏特化模板作用:给模板参数做进一步的限制
改为指针类型
//限制作用
template<class T1, class T2>
class D<T1*, T2*>
{
public:
D(const T1& d1, const T2& d2)
:_d1(d1)
, _d2(d2)
{
cout << "D(T1*, T2*)" << endl;
}
T1 _d1;
T2 _d2;
};
模板的分离编译
我们通常习惯在.h文件中声明一个函数,在define.cpp文件中写出该函数的定义,在main.cpp文件中链接函数使用它们。但是要注意,使用了模板必须将函数或者类的声明和定义都放在一个文件中
fun.h文件
//函数模板声明
template<class T>
T fun(const T& a);
fun.cpp
#include <iostream>
#include "fun.h"
using namespace std;
template<class T>
T fun(const T& a)
{
cout << a << endl;
return a;
}
main.cpp
#include <iostream>
#include "fun.h"
using namespace std;
int main()
{
fun(1);
return 0;
}
运行main.cpp
这是一个链接错误
在一个程序要运行起来要经过四个过程预处理
->编译
->汇编
->链接
。这里链接是将所有用到的函数地址符号等信息链接到同一个文件中整合成可执行文件。
错误原因分析:如果fun函数时一个普通的函数,在main.cpp中用到了该函数,会将该函数的地址信息符号加载到该文件中。但是该函数是一个模板函数,是在编译阶段中就要编译,但是在编译阶段中并没有使用的情况,所以此时就是一个空的符号信息。链接是就找不到该函数的符号信息,就会报链接错误。
解决办法1:声明与定义放在头文件中
fun.h文件
#include <iostream>
using namespace std;
template<class T>
T fun(const T& a)
{
cout << a << endl;
return a;
}
main.cpp文件
#include <iostream>
#include "fun.h"
using namespace std;
int main()
{
fun(1);
return 0;
}
运行结果:
解决办法2:在定义的文件中使用该函数,让该函数被实例化出来(有符号信息,链接时就找得到)
fun.h文件
//函数模板声明
template<class T>
T fun(const T& a);
fun.cpp
#include <iostream>
#include "fun.h"
using namespace std;
template<class T>
T fun(const T& a)
{
cout << a << endl;
return a;
}
void test()
{
fun(10);
}
main.cpp
#include <iostream>
#include "fun.h"
using namespace std;
int main()
{
fun(1);
return 0;
}
运行结果: