模板是C++泛型编程的基础。一个模板就是一个创建类或者函数的蓝图或者公式。通过提供有效的信息,我们将模板转换为特点的类或者函数,这个转换发生在编译过程。
定义模板
假设我们需要一个函数来比较两个值的大小,并指出哪个值大。假如说我们需要比较两个整数谁比较大。那么有以下代码
int compare(const int &a,const int &b)
{
if(a>b) return 1;
if(b>a) return -1;
return 0;
}
那么如果是我想比较两个string的大小呢?那会有如下代码
int compare(const string &a,const string &b)
{
if(a>b) return 1;
if(b>a) return -1;
return 0;
}
这时候大家会发现一个问题,这两个函数几乎相同,唯一的不同是参数的差异,函数体完全一样。如果需要比较的类型少还好,如果所有类型都要比较的话,难道所有的都要写一遍?这不就是傻小子睡凉炕,全凭火力旺!
有没有改进措施?当然是有的,我们发现唯一不同的是参数,那么其实可以通过函数模板这种高效方法解决此问题。
函数模板
我们可以定义一个通用的函数模板,来供功能一样,参数不同的任务去使用。
一个函数模板就相当于一个公式,可以用来生成对应特定类型的函数版本
上面的任务可以写成下面的模板。
template <typename T>
int compare(const T &v1,const T &v2)
{
if(v1>v2) return 1;
if(v2>v1) return -1;
return 0;
}
我们的compare函数声明了一个名为T的类型参数,在compare函数中,我们用T表示一个类型,而T表示的实际类型在编译时根据compare的使用情况来确定。
同时我们可以通过上面的例子,总结出函数模板的定义方式:
- 模板定义以关键字template开始,后面跟一个模板参数列表
- 在参数列表内部用逗号分隔开一个或多个列表。在模板定义中,模板参数列表不能为空
- 在模板参数列表中,每一个类型参数前必须使用关键字class 或者 typename。这两个关键字的含义相同,可以互换使用,可以同时出现
其实我们可以发现,函数模板很像函数。我们通过实参来初始化函数中的形参,在这里我们用模板实参,“初始化”函数模板。
实例化函数模板
当我们调用一个函数模板时,编译器会用函数实参来为我们推断模板实参。也就是说,我们调用上面的compare函数时,编译器会根据我们给定的实参类型,来确定绑定到函数模板参数T的类型。
比方说在下面的例子中,实参类型是int,所以编译器也会推断出模板实参为int,并将它绑定到模板参数T上
cout<<compare(1,0)<<endl;
编译器推断出的模板参数会为我们实例化一个特定版本的函数。那么扩展一下,我们其实可以根据上面的模板,写出下面的代码
//vector<int> vec1{1,2,3} vector<int> vec2{2,3,4} cout<<compare(vec1,vec2)<<endl;
在这个例子中,我们实例化了一个,实参为vector的比较函数
这里我们的函数模板实例化方式是隐式实例化,对应的还有显式实例化
模板类型参数
我们可以利用函数模板中的类型参数,去指定返回类型,或者函数的参数类型,或者函数体内的变量声明或者类型转换。这里的类型参数可以看做类型说明符,就像内置类型或者类类型说明符一样使用。
template <typename T> T foo(T* p) { T tmp = *p;//tmp的类型是指针p指向的类型 ... return tmp; }
函数模板的重载
在c++中,重载不仅可以用在函数上,也可以用在函数模板上。下面的例子虽然只是给出了声明没有给实现,但是也可以发现,函数模板的重载与函数的重载一样,也是通过实参的数量,返回值类型等进行实现。
template<class T> void Swap(T &a, T &b); //模板①:交换基本类型的值 template<typename T> void Swap(T a[], T b[], int len); //模板②:交换两个数组