函数模板

函数模板

基本语法

有时候对于多种数据类型可能需要执行相同的操作,如果没有函数模板,则需要为各个数据类型单独实现相应的函数,这样会导致代码的编写非常繁琐。有了函数模板之后只需要为所有的类型实现一个公用的函数模板即可,编译器会根据该模板为不同的类型生成相应的函数。我们声明一个函数模板的时候不创建函数,只是提供创建函数的模板,当需要使用到函数的时候,编译器才会根据模板创建函数
下面是声明一个函数模板的基本格式:

template <class T> //这里class也可以用typename代替,两者的效果一样
void swap(T &a, T &b){
    T temp = a;
    a = b;
    b = temp;
}

int ia = 0, ib = 1;
double da = 0, db = 1;
swap(ia, ib);   //传入类型为int
swap(da, db);   //传入类型为double
printf("ia = %d, ib = %d\n", ia, ib);
printf("da = %f, db = %f\n", da, db);

重载的模板

有的时候对于不同的参数需要采用不同的算法,这个时候就需要使用重载的模板。与普通函数的重载类似,模板的重载只需要保持模板的函数名相同,而签名不同即可。例如:

template <class T>
void swap(T &a, T &b);  //#1

template <class T>
void swap(T * a, T * b, int n)  //#2

int a = 0, b = 1;
int ar[] = {0, 0, 0}, br[] = {1, 1, 1};
swap(a, b);         //#1
swap(ar, br, 3);    //#2

显式具体化

如果对所有的数据类型都采用同一个模板,可能会导致一些问题,例如如果定义一个sum()函数表示将两个数据相加,使用运算符+实现,这对基本数据类型管用,但对于高级的数据类型,可能并没有定义+的操作。例如:

template <class T>
T sum(T a, T b){
    return a + b;
}

struct car
{
    char brand [20];
    double price;
};

上述函数对int类型可以很好地运行,但是对于一个结构体car则会出错,所以我们可以使用显式具体化这一特性来针对结构体car单独实现特定的操作。显示具体化在声明的时候必须以template <>开头,例如:

template <> car sum<car>(car a, car b){     //<car>可以省略
    a.price += b.price;
    return a;
}

对于同一个函数名如果有非模板函数,模板函数和显式具体化函数,则它们的优先级为非模板函数>显式具体化函数>模板函数

实例化和具体化

另一个容易与显示具体化混淆的操作是显示实例化,显示实例化表示的是给定模板需要的数据类型,从已有的函数模板中实例化出一个函数。实例化出来的函数中执行的算法,已经在函数模板中给出,而具体化的时候需要重新实现函数中执行的算法。例如:

template <class T>
T sum(T a, T b){
    return a + b;
}
//实例化,不需要自己的实现,算法已由模板给出
template sum<int>(int a, int b);

//具体化,函数的算法需要自己的实现
template <> car sum<car>(car a, car b){     //<car>可以省略
    a.price += b.price;
    return a;
}

除了单独用一个语句实现实例化,还可以在函数调用的时候显式实例化,例如:

int a = 1;
double b = 1;
printf("a + b = %f", sum<double>(a , b));

注意这里a跟b不属于一个数据类型,所以如果不显示实例化函数,编译器将找不到与该调用匹配的模板

编译器选择使用哪个函数版本

当编译器发现有多个可以选择的函数版本的时候,其选择的优先级是:

  1. 完全匹配,但普通函数高于模板
  2. 提升转换,如char和short转换为int
  3. 标准转换,如int转换为char,long转换为double
  4. 用户定义的转换,如类声明中定义的转换

decltype的作用

有时候在声明模板的时候没有办法事先确定一个变量的类型,例如:

template <class T1, class T2>
void ft(T1 x, T2 y){
    T1 z = x + y;
}

其实这里z的类型不一定为T1,例如当T1为intT2为double时,z的类型为double,而不是跟T1一样的int。这个时候我们就可以使用decltype关键字来确定z的类型。decltype关键字的用法为:

decltype(expression) var;

变量var的数据类型根据expression确定。其规则如下:

  1. 如果expression是一个没有用括号括起的变量,则var的类型与该变量一致
double x = 5.5;
decltype(x) var;    //double
  1. 如果expression是一个函数调用,则var的类型为该函数返回值的类型,注意被调用的函数不会被执行,只是要获取其返回值的类型
int f(int);

decltype(f(3)) var;   //int
  1. 如果expression是一个用括号括起来的变量,则var是指向该类型的引用
double x = 5.5;
decltype((x)) var;      //double &
  1. 如果expression是都不满足上述条件,则varexpression相同
decltype(100) var;  //int

后置返回类型

最后还有一种情况是decltype关键字没有办法解决的,就是当函数的返回值不能确定时,例如:

template <class T1, class T2>
T1 sum(T1 x, T2 y){
    return x + y;
}

这里并不能直接确定xy相加之后的类型为T1,例子与上述相似。但是又不能通过decltype解决,因为这个时候xy还没有被声明,所以不能使用。这个时候就可以使用另一种方式声明函数的返回类型——后置返回类型:

template <class T1, class T2>
auto sum(T1 x, T2 y) -> decltype(x + y){
    return x + y;
}

这里的auto只是一个占位符,其类型由后置返回类型decltype(x + y)提供。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值