在前面的文章中我写了关于模板的一些简单的认识,现在我们来再次认识模板
文章目录
- 1.非类型模板参数
- 2.模板特化
- 1). 模板特化的写法
- 2). 类模板特化
- 3). 函数模板特化
- 4). 模板全/偏特化
- 3.模板分离编译
1.非类型模板参数
在模板中还有一种是非类型的模板参数。我们代码展示:
#define N 10
template<class T>
class stack {
private:
T* _arr[N];
size_t size;
size_t capacity;
};
int main()
{
stack<int> st1;
return 0;
}
这是我们以前编写静态容器的方式,通过宏定义来实现,但是有一个缺陷,那就是我们假如还要建立一个大小为100的静态栈呢?把N改成一百吗?我那我们有想要一个大小为10的静态栈和大小为10000的静态栈呢?这时候就会非常浪费空间。所以这时候就有了非类型模板参数的出现。
template <class T, size_t N>
class stack {
private:
T* _arr[N];
size_t size;
size_t capacity;
};
int main()
{
stack<int, 10> st1;
stack<int, 100> st2;
return 0;
}
这样就可以解决上面的问题了。非类型模板参数目前只支持整形(char, short,int)
可以看到STL中有一个容器静态数组也用了非类型模板参数。
2.模板特化
关于模板我们有函数模板,类模板,模板中也有一种新的知识,叫做模板特化。
1). 模板特化的写法
1. 必须要先有一个基础的函数/类模板
2. 关键字template后面接一对空的尖括号<>
3. 函数/类名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。(针对函数模板)
2). 类模板特化
话不多说,上代码:
#include <iostream>
using namespace std;
namespace xxx{
template<class T>
class stack
{
public:
stack()
{
cout << "stack():template<class T>" << endl;
}
};
template<>
class stack<int>
{
public:
stack()
{
cout << "stack():template<>" << endl;
}
};
}
int main()
{
xxx::stack<double> s1;
xxx::stack<int> s2;
return 0;
}
我们可以看到当两种实例化的对象他们所调用的构造函数是不同的,原因是当对象实例化的时候,他们会去找“最符合”的类来实例化自己。
下面那个类就是模板的特化。特化是为了处理一些类型错误的场景。下面举个例子:
class A
{
public:
A(int aa, int a)
:_aa(aa)
, _a(a)
{}
bool operator>(const A& y) const
{
if (_aa > y._aa) return true;
else if (_aa == y._aa)
{
if (_a > y._a) return true;
else return false;
}
else return false;
}
private:
int _aa = 0;
int _a = 0;
};
template <class T>
class Greater {
public:
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
template <>
class Greater<A*> {
public:
bool operator()(const A* x, const A* y)
{
return *x > *y;
}
};
int main()
{
A a1(100, 10);
A a2(120, 10);
cout << Greater<A>()(a1, a2) << endl;
A* pa1 = new A(100, 1);
A* pa2 = new A(122, 1);
cout << Greater<A*>()(pa1, pa2) << endl;
return 0;
}
我们定义了这么一个类,他里面有两个int成员,比较大小的方法经过了重载。现在哦我们用一个仿函数,来比较他们的大小。
我们发现,我们相比较的是对象的内容,但是第二组比较显然是比较了他俩的地址大小(由于申请内存时申请的地方不确定,所以大小关系也就不确定),所以为了不出现这样的情况,我们对仿函数特化一下:
class A
{
public:
A(int aa, int a)
:_aa(aa)
, _a(a)
{}
bool operator>(const A& y) const
{
if (_aa > y._aa) return true;
else if (_aa == y._aa)
{
if (_a > y._a) return true;
else return false;
}
else return false;
}
private:
int _aa = 0;
int _a = 0;
};
template <class T>
class Greater {
public:
bool operator()(T& x, T& y)
{
return x > y;
}
};
template <>
class Greater<A*> {
public:
bool operator()(A* x, A* y)
{
return *x > *y;
}
};
int main()
{
A a1(100, 10);
A a2(120, 10);
cout << Greater<A>()(a1, a2) << endl;
A* pa1 = new A(100, 1);
A* pa2 = new A(122, 1);
cout << Greater<A*>()(pa1, pa2) << endl;
return 0;
}
可以发现这样就解决了。
3). 函数模板特化
同理照搬即可,只是写法上与类模板有点不一样
class A
{
public:
A(int aa, int a)
:_aa(aa)
, _a(a)
{}
bool operator>(const A& y) const
{
if (_aa > y._aa) return true;
else if (_aa == y._aa)
{
if (_a > y._a) return true;
else return false;
}
else return false;
}
private:
int _aa = 0;
int _a = 0;
};
template <class T>
bool Greater(const T& x, const T& y)
{
return x > y;
}
template <>
bool Greater<A*>(const A* x, const A* y)
{
return *x > *y;
}
int main()
{
A a1(100, 10);
A a2(120, 10);
cout << Greater(a1, a2) << endl;
A* pa1 = new A(100, 1);
A* pa2 = new A(122, 1);
cout << Greater(pa1, pa2) << endl;
return 0;
}
如果把上述代码,运行的话,会发现有语法错误,错误出现在仿函数的特化中。只需要这么写就可以了:
template <class T>
bool Greater(const T& x, const T& y)
{
return x > y;
}
template <>
bool Greater<A*>(A* const & x, A* const & y)
{
return *x > *y;
}
但是这么写有点恶心,但是我们换一种写法:
template <class T>
bool Greater(const T& x, const T& y)
{
return x > y;
}
//template <>
//bool Greater<A*>(A* const & x, A* const & y)
//{
// return *x > *y;
//}
bool Greater(const A* x, const A* y)
{
return *x > *y;
}
下面这个函数,当函数模板实例化出模板函数的时候会与那个函数构成重载。不需要模板特化就可以写的简单易懂,也能实现要求。所以类模板推荐使用特化,函数模板不推荐。
4). 模板全/偏特化
这个很好理解,结合开始对模板特化的理解,类模板实例化的成模板类的时候会找“最匹配的”类来实例化:
template<class T1, class T2>
class A
{
public:
A()
{
cout << "A:template<class T1, class T2>" << endl;
}
};
//偏特化
template<class T1>
class A<T1, int>
{
public:
A()
{
cout << "A:template<class T1>" << endl;
}
};
//全特化
template<>
class A<int, int>
{
public:
A()
{
cout << "A:template<>" << endl;
}
};
int main()
{
A<double, double> a1;
A<double, int> a2;
A<int, int> a3;
return 0;
}
3.模板分离编译
我们先写三个文件:
test.cpp
#include "func.h"
int main()
{
printf<int>();
return 0;
}
func.cpp
#include "func.h"
template<class T>
void printf()
{
cout << "void printf()" << endl;
}
func.h
#pragma once
#include <iostream>
using namespace std;
template<class T>
void printf();
我们写完后,发现写的没啥问题,但是一运行就出错了。出的是一个链接错误。这其中的原因是:
在形成可执行程序的过程中。首先要预处理,编译,汇编,链接过程最后才能形成可执行程序。
在编译过程检查语法的时候,test.cpp文件里因为有了print函数的声明,所以没有报错,而func.cpp中的print函数它是一个模板函数,模板是需要成为可执行程序后运行起来需要他时才会生成模板函数或模板类,所以编译器就没有实现这个函数。自然在链接过程就没有对应的函数地址来生成符号表。所以会报链接错误,所以我们应该实例化实现才可以:
func.cpp:
template<>
void printf<int>()
{
cout << "void printf()" << endl;
}
类模板也会出现这样的情况。
test.cpp
#include "func.h"
int main()
{
A<int> a;
return 0;
}
func.cpp
#include "func.h"
template<class T>
A<T>::A()
{
cout << "A()" << endl;
}
func.h
#pragma once
#include <iostream>
using namespace std;
template<class T>
class A {
public:
A();
};
这样也会出现链接错误,也是得显式写明类型。
func.cpp
#include "func.h"
template<>
A<int>::A()
{
cout << "A()" << endl;
}
所以,还是建议写模板的时候,不要声明定义分离了。