概念
函数模版是通用的函数描述,也就是说,它们使用泛型来定义函数,其中的泛型可以用具体的类型来进行代替,通过该类型作为参数传递给该模版,可以使编译器生成该类型的函数
例如:
有一个交换两个int类型的函数,假如要更改为交换两个double类型的函数,一种方法是复制原来的代码,并且都double替换掉所有的int,但是这种修改会浪费时间,并且容易出错,但C++的函数模版功能能够自动完成这个过程
函数模版的定义例子
template <typename, AnyType >
void swap(AnyType &a , AnyType &b)
{
函数体;
}
第一行指出,要建立一个模版,并将类型命名为AnyType, 关键字template和typename是必须的除非可以使用关键字class来代替typename,类型名AnyType可以任意选择
例子:
#include<iostream> using namespace std; template <typename T > void swapi(T &a , T &b); int main(void) { int a = 10; int b = 20; cout<<"a = "<<a<<" b = "<<b<<endl; swapi(a,b); cout<<"a = "<<a<<" b = "<<b<<endl; double x= 2.5, y = 3.5; swapi(x,y); cout<<"x = "<<x<<" y = "<<y<<endl; } template <typename T> void swapi(T &a , T &b) { T tem = a; a = b; b = tem; }
在本例中将类型命名为T,注意,在运行此程序时,调用了两次swapi函数,编译器会生成两个swapi函数,一个是int类型的,一个是double类型的,即最终也会生成两个独立的函数,使用函数模版使得编译器能够生成多个函数,使得生成函数更简单可靠
我们通常会将函数放在头文件中,并在使用模版的文件中包含头文件
重载的模版
当我们需要对多个不同类型的数据使用同一种算法时,可以使用模版,但是并不是所有的类型都使用相同的算法,为了解决这种问题,就可以像函数重载那样使用重载模版进行定义,注意,被重载的模版的特征标(参数)必须不同,例如
原来的模版: (T &, T &);
新模版 : (T a[], T b[] &, n); //交换两个等长数组的值
两种的特征标不同
程序运行调用时,编译器根据传入参数的不同,选择使用不同的函数模版
函数模版的局限性
函数模版不一定能够处理所有传入的形参,按照上例实现两个数的交换,如果传入的参数是数组名,那么就无法进行处理,如果传入的是结构体,而我们只想交换结构体中的某些成员,而不相进行全部的交换,上述的模版也无法实现
解决方法:显示具体化的函数模版
template <> 函数返回值 函数名<指定的类型>(形参列表)
连接上例
typedef struct job { char *s; int i; double b; }job; //这条语句告诉编译器swapi无法处理job类型的数据 //需要使用特殊的处理,而不是使用原来的函数模版 template <> void swapi<job>(job &j1, job &j2) { //只进行部分的交换 double t = j1.b; j1.b = j2.b; j2.b = t; int c = j1.i; j1.i = j2.i; j2.i = c; }
注意:
对于给定的函数,可以有非模版函数,模版函数,显示具体化模版函数以及它们的重载版本
显示具体化的原型和定义应该以template<>开头,并通过名称来指出类型
具体化优先于常规模版,而非模版函数优先于具体化和常规模版
实例化和具体化
我们在代码中包含函数模版本身并不会生成函数的定义,它只是一个用于生成函数定义的方案,编译器使用模版为特定的类型生成函数定义时,得到的是模版的实例,即函数模版并不是函数的定义,只有在真正的传入参数的时候,模版才会转化为函数的定义,这被称为实例化,编译器自动进行的称为隐式的实例化,现在,可以进行显示的实例化,例如
语法为声明所需的种类,用<>符号来指定类型,并在声明前加上关键字template
如下所示:
template void swapi<int >(int , int );
这意味着可以直接命令编译器创建特定的实例
注意,显示具体化的template后是加有<>符号的,而实例化并没有
当我们的模版函数需要处理一种特殊的数据类型时,使用显示具体化,进行特殊的处理
而显示实例化是无论该函数是否被调用,我们都要为当前的模本函数生成一个函数的定义,如果没有进行显示实例化,那么什么时候调用什么时候才会将模版函数的定义,实例化主要用于库文件的编写
自己选择调用的函数
函数名<>(实参);
//此时<>告诉编译器去调用模版函数
函数名<数据类型>(实参);
//此时<数据类型>告诉编译器要将实参转化为指定的数据类型,再去调用模版函数
关键字decltype(C++11)
在处理函数模版时,可能不知道给某个变量具体的类型
template <typename T1, typename T2>
void ft(T1 x, T2 y)
{
return ps = x+y;
}
此时不知道ps的类型到底是什么,是T1类型还是T2类型的,在这种情况下,没有办法声明ps的类型,而C++11的关键字decltype提供了解决的方法,例如
int x;
decltype (x) y;
此时让y的类型与x的类型一致,并且,给ps提供的参数也可以是表达式
decltype (x+y) ps = x + y;
编译器确定类型的核对表
decltype(表达式) var
1.如果表达式是一个没有括号括起来的标识符,则var的类型与标识符的类型相同,例如
int a;
decltype (a) b; b也为int类型
2.如果表达式是一个函数的调用,则var的类型与函数的返回值的类型相同
注意:此时并不会实际的调用函数,编译器通过查看函数的原型来获取返回值
3.如果表达式是一个左值,并且是由括号()括起来的,那么var为指向其类型的引用,例如
int a = 10;
decltype((a)) b;
decltype(a) c;
此时b是a类型的引用,而c为int类型
4.如果前面的条件都不满足,则var的类型与表达式的类型一致
C++11后置返回类型
有一个问题decltype也无法解决,即
template <typename T1, typename T2>
类型? ft(T1 x, T2 y)
{
return x+y;
}
此时返回值的类型不确定
使用auto关键字
auto h(int a, double b) -> double
此时是后置返回类型,这样就可以解决上述的问题
template <typename T1, typename T2>
auto ft(T1 x, T2 y) -> decltype(x+y)
{
return x+y;
}
此时指定函数的返回值被指定为x+y类型