目录
函数模板
功能:
函数模板代表了一个函数家族,在使用时被参数化,根据实参的类型生成特定类型的版本
也就是说,我们可以通过一个函数模板,让编译器生成一整个同类型的函数家族。
语法:
template <typename T1, typename T2 ......>
template <class T1, class T2 ......>
示例:
template <class T>
T Add(T x, T y)
{
return x + y;
}
编译器会根据调用函数时传入的参数,自动判断类型。
模板只是一个蓝图,本身不是函数,当我们传入指定类型参数,其就会生成相应的函数。
显式实例化
语法:
函数名 <类型> (参数);
比如:
int a = 5;
double b = 3.0;
Add<int> (a, b);
Add<double> (a, b);
模板参数缺省
在设置模板参数时,可以设置缺省值,在显式实例化时,对于没有指明的模板参数,会被推演为缺省值。
给模板参数缺省值试试:
template <class T1, class T2 = double>
void func(T1 a)
{
T2 b = 5;
cout << a / b;
}
参数匹配规则
模板本身不是一个函数,所以同名的函数和模板是可以共存的。
template <class T>
T func(T x, T y)
{
return x + y;
}
int func(int x, int y)
{
return x + y;
}
调用函数时,如果函数有现成的,完全匹配的函数,那么不会调用模板。
调用函数时,如果可以通过模板产生更加匹配的函数,那么会调用模板进行推演
类模板
类模板的特性与函数模板几乎一致,此处不额外讲解了,只讲解类模板的特殊的地方。
语法:
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
我们的模板参数为T,由于类没有传参的概念,不能通过参数来推演类型,所以一般而言类的模板都是要显式实例化的。
比如:
stack<int> s1;
stack<double> s2;
类成员的声明定义分离
当我们希望把一些类中的成员定义在类的外部时,那就需要声明和定义分离。
假设我们希望分离析构函数~stack
。
对于一般的类,我们会这样分离:
class stack
{
public:
stack(size_t capacity = 10)
:_pData(new int[capacity])
,_size(0)
,_capacity(capacity)
{}
~stack();//声明
private:
int* _pData;
size_t _size;
size_t _capacity;
};
stack::~stack()
{
//函数体
}
所以我们的类模板也要类型::函数名
来限定作用域。类模板的类型刚刚介绍过,就是stack<T>
,所以函数的声明应该这样写:
stack<T>::~stack()
{
//函数体
}
对于类模板,当在类外定义函数时,要添加模板参数列表。
也就是说要这样:
template <class T>
stack<T>::~stack()
{
//函数体
}
非类型模板参数
我们的模板参数也可以不是一个类型,而是一个数值。
对于指定类型的参数,我们称为类型形参,比如int,double。
对于一个数值的参数,我们称为非类型形参。
template <class T, int N>
class Array
{
public:
private:
T _arr[N];
};
非类型模板参数必须是整型,bool,char类型的常量
模板特化
通常情况下,使用模板可以实现一些与类型无关的代码,但是对于一些特殊的类型,有可能会得到错误的结果。
template<class T>
bool Less(T x, T y)
{
return x < y;
}
int main()
{
int* p1 = 5;
int* p2 = 10;
cout << Less(p1, p2) << endl;
return 0;
}
这个函数模板中,我们用Less
来比大小,我们此时传入了两个指针p1
,p2
,原本的意图是通过指针来比较数字5和10的大小。但是当传入后,我们比较的是p1 < p2
,也就是对两个指针比大小了,这不符合我们的预期。也就是说在面对指针的时候,我们需要特殊处理,这就需要模板特化了。
模板特化的功能就是:
在原模版的基础上,针对特殊类型进行特殊化的实现方式
函数模板特化
我们先看到一个函数模板特化,再讲解语法:
//基础模板
template<class T>
bool Less(T x, T y)
{
return x < y;
}
//模板特化
template<>
bool Less<int*>(int* x, int* y)
{
return *x < *y;
}
第一段代码是一般的函数模板,而第二段是对int进行了特化的版本,当我们传入参数类型为int时,就会调用这个特化版本,执行*x < *y
,先解引用再比较。
首先,我们将T
特化为了int*
,所以T
不再是一个需要推演的参数了,此时将T
从模板参数列表中抽离出来,改为int*
放到函数名Less
后面,用尖括号括起来,然后把函数参数中所有的T改为特化后的int*
。
模板特化要满足以下语法:
- 必须存在一个基础模板
- 对于特化版本,template后面的<>内部不写被特化的模板参数
- 对于特化版本,在函数名后跟上<>,内部指定特化类型
- 将特化前的模板参数改为特化后的具体类型。
类模板特化
模板特化要满足以下语法:
- 必须存在一个基础模板
- 对于特化版本,template后面的<>内部不写被特化的模板参数
- 对于特化版本,在函数名后跟上<>,内部指定特化类型
- 将特化前的模板参数改为特化后的具体类型
- 类模板特化分为全特化和偏特化。
全特化
全特化是指将模板参数的所有参数都确定下来
//基础模板
template<class T1, class T2>
class Data
{
public:
Data() {cout<<"Data<T1, T2>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
//模板特化
template<>
class Data<int, char>
{
public:
Data() {cout<<"Data<int, char>" <<endl;}
private:
int _d1;
char _d2;
};
偏特化
偏特化是指并没有把模板参数确定下来,但是对满足特定条件的模板参数,执行特化版本
偏特化分为部分特化和限制特化:
部分特化
部分特化是只将一部分参数特化
//基础模板
template<class T1, class T2>
class Data
{
public:
Data() {cout<<"Data<T1, T2>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
//模板特化
template<class T1>
class Data<T1, char>
{
public:
Data() {cout<<"Data<T1, char>" <<endl;}
private:
T1 _d1;
char _d2;
};
限制特化
限制特化是对参数进行条件限制,但是没有把参数类型确定下来
//基础模板
template<class T1, class T2>
class Data
{
public:
Data() {cout<<"Data<T1, T2>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
//模板特化
template<class T1, class T2>
class Data<T1*, T2*>
{
public:
Data() {cout<<"Data<T1*, T2*>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
此处<T1*, T2*>
是限定:当T1
,T2
为指针是,调用此模板特化。
也就是说,这个过程中,T1
,T2
的类型是不确定的,任然需要推演,所以第一行的模板参数列表保留了T1
,T2
。
而这样对模板参数进行限制,就是限制特化了。
模板分离编译
解决方法
1. 将声明和定义放到一个文件 "xxx.hpp" 里面或者 xxx.h 其实也是可以的 。推荐使用这种。
2. 模板定义的位置显式实例化 。这种方法不实用,不推荐使用。
模板总结
【优点】
1. 模板复用了代码,节省资源,更快的迭代开发, C++ 的标准模板库 (STL) 因此而产生
2. 增强了代码的灵活性
【缺陷】
1. 模板会导致代码膨胀问题,也会导致编译时间变长
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误