首先,我们通过一个问题来引入泛型编程的概念。
如果我们想要实现对任何类型都能够适用的swap函数,结合C++的语法,目前能想到的方法就只有利用函数重载的方式,把每一种类型的swap函数都写一遍。代码如下。
void swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
void swap(char& a, char& b)
{
char tmp = a;
a = b;
b = tmp;
}
void swap(double& a, double& b)
{
double tmp = a;
a = b;
b = tmp;
}
//......
显然,我们可以发现很多问题:
- 上述重载出来的几个swap函数只有参数类型不同,其余完全相同,造成了代码冗余
- 这些预设的swap函数只能支持内置类型,如果出现了一个自定义类型需要使用swap函数的时候,就需要我们重新写一个针对于此自定义类型swap函数。
- 代码的可维护行比较低,一个出错可能所有的重载均出错
那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?
答案是肯定的。在C++中,是存在这样一个模具,通过给这个模具中填充不同类型,来生成具体类型的代码。这个模具叫做==模板==。
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
概念:函数模板所代表的是执行某一种功能的一类的函数,但是他本身不是函数,只是一个蓝图。在使用时被编译器参数化,根据实参类型产生特定的函数版本。
使用格式:
//格式1 template<typename T1, typename T2, ......> 返回值类型 函数名(参数列表) {} //格式2 template<class T1, class T2, ......> 返回值类型 函数名(参数列表) {}
其中,template是定义模板的关键字,typename和class是定义模板参数的关键字
例如,本文开头提出的问题,使用模板改写之后的结果:
template<class T> void Swap(T& a, T& b) { T tmp = a; a = b; b = tmp; } void Test_Template() { int a = 10, b = 20; char c = 'a', d = 'b'; double e = 1.1, f = 2.2; Swap(a, b); Swap(c, d); Swap(e, f); }
可以看到,使用了一个模板,就可以做到同时支持不同类型参数的功能。
函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此
通过查看汇编代码,可以看到,最终Swap模板实例化出了Swap
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化
1. 隐式实例化:让编译器根据传入的实参自动推演参数的实际类型
template<class T>
T Add(const T a, const T b)
{
return a + b;
}
void Test1()
{
int a = 10, b = 20;
double e = 1.1, f = 2.2;
cout << Add(a, b) << endl;//会实例化出Add
cout << Add(e, f) << endl;//会实例化出Add
cout << Add(a, f) << endl;//此语句不能通过编译
}
上述代码中,前两此调用Add能够被编译器推导出来参数类型,但是第三个,在编译期间,通过实参a将T推演成int类型,通过f将T推演成double类型,但是模板参数列表中只有一个T,因此编译器无法确定此处T将被推演成int还是double而报错。
注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅
此时,有两种处理方式:
Add((double)a, f)
或者Add(a, (int)f)
2. 显示实例化:在函数模板名后的<>中指定模板参数类型
template<class T>
T Add(const T a, const T b)
{
return a + b;
}
void Test1()
{
int a = 10, b = 20;
double e = 1.1, f = 2.2;
cout << Add<int>(a, f) << endl;//实例化成Add
cout << Add<double>(a, f) << endl;//实例化成Add
}
此时,如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错
int Add(int a, int b)
{
return a + b;
}
template<class T>
T Add(const T a, const T b)
{
return a + b;
}
void Test2()
{
int a = 10, b = 20;
Add(a, b);//与非模板函数匹配,编译器不需要特化
Add<int>(a, b);//调用编译器特化的Add版本
}
int Add(int a, int b)
{
return a + b;
}
template<class T1, class T2>
T1 Add(const T1 a, const T2 b)
{
return a + b;
}
void Test3()
{
int a = 10, b = 20;
double c = 1.1;
Add(a, b);
Add(a, c);
}
类模板和函数模板非常相似
template<class T1, class T2, ......>
class 模板名
{
//类内成员定义
};
例如,vector类模板
template<class T>
class vector//注意,这里的vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具
{
public:
vector()
:_start(nullptr)
, _finish(nullptr)
, _endOfStorage(nullptr)
{}
~vector()
{
delete[] _start;
_start = _finish = _endOfStorage = nullptr;
}
//......
private:
T* _start;
T* _finish;
T* _endOfStorage;
};
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类
//vector
Test4()
{
vector<int> vi;
vector<string> vstr;
}