函数模板
函数模板的基本形状
template <typename T>
T fun(T val1, T val2)
{
return val1*val2;
}
开头的template
表示接下来的函数是一个模板函数
其中的typename
也可以用class
来替代.
T
在之后的函数里可以用来代指函数类型.你也可以把它改成其他名字,然后用到它的地方也改成对应的名字就行.
总之,必须以template
这个关键字开头,后面一个<typename T>
或者<class T>
, T
只是一个标识符,不是固定搭配,可以任意写.
再之后就是写你想要的函数.
要注意,函数里用到的操作符对于传进去的参数的类型必须要合法
例如, 如果传进去一个string
,那里面就不能对这个变量用减号-
,因为string
没有减法.(编译器可以发现这个错误,所以这样的代码是编译不过的)
C++的max()
等函数使用了这种方式,所以你会发现max()
的两个参数必须相同.
实例化
在编译的时候,编译器会用具体的类型来代替类型模板参数,这个过程叫做实例化.
其实就是生成指定了实际类型的函数,来替换模板函数.
但是只会生成需要的类型,而不是每种类型都来一个.
对于以上的那个模板函数,程序在编译的时候,会检查这个函数在代码中被传入了什么类型,然后对于每种用到的类型,生成一个函数来替换原来的模板函数.
例如,如果程序中的main
如下:
int main()
{
fun((int)1, (int)2);
fun((double)1.0, (double)2.0);
}
那么就只会生成int fun(int a, int b)
和double fun(double a, double b)
.
使用多个类型
你可以在模板函数里用多个类型,形状如下
template <typename X, typename Y>
void fun(X tv1, Y tv2)
{
cout << tv1 << ' ' << tv2 << endl;
}
这时,调用像这样fun(1, 2.0)
.
手动指定类型
但是使用多个类型的时候,编译器不一定能推测出到底是哪个对应什么类型,例如
template <typename X, typename Y, typename Z>
Z fun(X a, Y b)
{
return a*b;
}
如果你这样调用int tmp = fun(1, 2.0)
, 就会运行错误,因为编译器推测不出Z
是什么类型.
所以你应该在调用的时候,通过尖括号指定类型,像这样int tmp = fun<int, double, int>(1, 2.0)
.
仅仅指定一部分类型
其实我们可以只指定推测不出来的类型,而不指定能够推测的类型.
要怎么做,我们可以把推测不出来的类型放到最前面,如下
template <typename Z, typename X, typename Y>
Z fun(X a, Y b)
{
return a*b;
}
那么我们可以这样调用int tmp = fun<int>(1, 2.0)
.
尖括号里面只要写一个int
,用来指定推测不出来的Z
就好了.
注意,一定要把需要指定的参数堆到最前面,不能跳着指定的.
auto推断返回值
很多时候,推测不出的是返回值,我们可以用auto
代替返回值,形状如下
template<typename X, typename Y>
auto fun(X x, Y y)
{
return x*y;
}
返回类型后置语法
有这样一种操作
template<typename X, typename Y>
auto fun(X x, Y y) -> decltype(x*y)
{
return x*y;
}
这里auto
并没有进行类型推导.
空函数列表的作用
有时候一个模板函数本身已经有能力推测出所有类型了,我们没有必要再加尖括号.
但如果存在一个同名的普通函数fun(int a, int b)
, 那么这样调用fun(1,2)
的时候,就会优先调用普通函数.
如果我们加一个尖括号fun<>(1, 2)
,虽然没有含义,但是可以让它调用模板函数.