1.模板概念
扯在前面:
现在过中秋时一般都是从外边买月饼回来吃,但我在小的时候家里经常会自己动手来做中秋所需的月饼,我也在中秋节前见过家里的大人亲手做月饼的过程,不过写这篇博客自然不是想介绍做月饼的方式,而且很多的步骤我也没有太多的印象了。但其中有很重要的一步我却仍旧记忆犹新,那便是为了使月饼真正成形,都必须经过类似下面图片所示的一种月饼模具,模具也许只有一个,却可以因它产生成百上千甚至更多数量的月饼,而且只要月饼的馅不同,那便可以说是不同的月饼。不论做月饼的配料有什么不同,都可以经过这同样的模具,从而产生不同口味的月饼。
上边扯了那么多,其实也并不完全是废话,只是想借之引出这篇博客所要谈的重头戏----模板。
理解模板:在C++中,对于模板的理解,其实完全可以与上述的模具相对应,给这样的模具之中填充不同的配料(类型),来产生不同口味的月饼(生成具体类型的代码),使原本可能比较复杂的工作变得简单,这便完成了模板所想要完成的内容。
模板是泛型编程的基础。
泛型编程:是一种代码编写的方式,通过使用泛型编程,我们可以编写出独立于任何特定类型(与类型无关)的代码。
模板一般分为函数模板和类模板:
2.函数模板
2.1函数模板概念
所谓函数模板,实际上就是建立一个通用的函数,这个函数的函数类型与参数类型不具体指定,而是用一个虚拟的类型来代表,这个通用的函数就称为函数模板。这个函数模板,在使用时被参数化,根据具体类型的实参,才会产生对应类型的函数。
2.2定义函数模板的一般形式
template <typename T> 或 template <class T>
通用函数定义
类型参数可以不止一个,根据需要确定参数,如
template <class T1, class T2>
template的含义为“模板”,尖括号中先写关键字typename或class,表示类型名的意思,后面跟一个类型参数T,这个T只是一个虚拟的类型名,并未指定是哪一种具体的类型。
2.3函数模板的原理
函数模板并不是真正意义的函数,只是一个蓝图,对于不同类型的实参,编译器会去完成类型的推演,从而产生了具体的函数。
观察下列代码及其函数调用部分的汇编代码:
#include <iostream>
using namespace std;
template <class T>
T Add(T x, T y)
{
T ret = x + y;
return ret;
}
int main()
{
int i1 = 10;
int i2 = 20;
Add(i1, i2);
double d1 = 10.0;
double d2 = 20.0;
Add(d1, d2);
char c1 = 'a';
char c2 = 'b';
Add(c1, c2);
return 0;
}
从上述汇编代码即可看出,在编译阶段,编译器会根据函数模板以不同的实参类型,类推演生成具体类型的函数以供调用。 如,当int类型的实参使用函数模板时,编译器通过对实参的类型进行推演,从而确定T为int类型,然后产生处理int类型的函数。而double类型和char类型也同样如此。
2.4函数模板的实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化。函数模板的实例化分为隐式实例化和显式实例化。
隐式实例化:让编译器根据实参的类型自己推演模板函数参数的实际类型。
上边原理部分所演示的代码就是采用隐式实例化的方式。
但要注意,就上述代码而言,如果是隐式实例化,对于同一次调用,不同的实参的类型必须一致,否则编译器无法成功推演出T的类型纠结是哪种类型,便会报错。 如以下调用:
Add(i1, d2); //其他代码在上边
对于以上调用,编译器无法确定T的类型到底是int还是double,所以就会报错,因此一定要杜绝这样的写法。
显式实例化:在函数名后的<>中指定模板参数的实际类型。
Add<int>(i1, d2); //会将d2隐式类型转换为int
对于显式实例化,如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。
3.类模板
理解了函数模板之后再来认识类模板就十分简单了,概念及原理都大同小异。
3.1定义类模板的一般形式
template <class T1, class T2, ..., class Tn> //一个或多个参数
class 类模板名
{
// 类内成员定义
};
如定义一个顺序表的类模板:
template <class T>
class SeqList //这里的SeqList并不是具体的类,而是类模板
{
public:
//构造函数
SeqList(size_t capacity = 10)
: _array(new T[capacity])
, _capacity(capacity)
, _size(0)
{}
//下方俩个成员函数仅为举例说明,未具体实现
SeqList(const SeqList<T>& s); //拷贝构造函数
SeqList<T>& operator=(const SeqList<T>& s); //赋值运算符的重载
//析构函数
~SeqList()
{
if (_array)
{
delete[] _array;
_capacity = 0;
_size = 0;
}
}
/*未具体实现*/
void push_back(const T& data);
void pop_back();
//...等等
//下标运算符的重载
T& operator[](size_t index)
{
assert(index < _size);
return _array[index];
}
const T& operator[](size_t index)const
{
assert(index < _size);
return _array[index];
}
private:
T* _array;
size_t _capacity;
size_t _size;
};
3.2类模板的实例化
需要注意的是,类模板的实例化与函数模板的实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中。类模板名字不是真正的类,而实例化的结果才是真正的类。如对上述顺序表的类模板进行实例化:
// SeqList是类名,SeqListr<int>才是类型
SeqList<int> s1;
SeqList<char> s2;
再次观察其汇编代码,进一步验证上述结果。