何为泛型编程呢?简单的说就是,我们按照特定语法写代码,然后让编译器去具体实现这些代码。而函数模板,就是让编译器按照调用时的实参自动生成相应的函数版本.
定义和使用函数模板
格式如下:
template<typename AnyType>
returnType functionName(argument-list){
// do something
}
其中template和typename是关键字。
Tips:
在c++98之前c++是使用class关键字来定义模板的,而在c++98开始使用typename关键字定义模板了。
示例:
现在假设我们想写一个swap函数来交换两个数的值
#include <iostream>
#include<cstring>
#include"test_h.h"
using std::string ;
using std::cout;
using std::endl;
//function declaration
template<typename T>
void swap(T& a,T& b);
int main(int argc, char** argv) {
int i1=19;
int i2=20;
double d1=19.0;
double d2=20.0;
cout<<i1<<endl;
cout<<i2<<endl;
cout<<d1<<endl;
cout<<d2<<endl;
cout<<"after transform"<<endl;
swap(i1,i2);
swap(d1,d2);
cout<<i1<<endl;
cout<<i2<<endl;
cout<<d1<<endl;
cout<<d2<<endl;
}
//function definition
template<typename T>
void swap(T& a,T& b){
T temp;
temp=a;
a=b;
b=temp;
}
编译器会根据swap()中的参数类型自动生成int和double版本的函数。
Node:
记住template在定义和声明中都是不能少的
模板函数使用的局限性
假设有如下模板
template<typename T>
void f(T a,T b){
//……
}
如果a,b是数组的话a=b
是不可以的
如果a,b是结构的话a>b
也是不可以的
所以模板编程有时候你要考虑一下参数的特殊性。这时候我们就要用到显示具体化了
函数模板显式具体化
格式如下:
template <>returnTyoe function< Type>(Type, Type)
其中template <>为关键字,如果编译器可以根据参数列表推断模板类型那么< Type>中的Type为可选项
示例:
void swap(int a,int b);
template <>void swap<>(double, double);
template<typename T>
void swap(T, T);
上卖弄三种分别为非模板函数,模板的显式具体化,模板函数
如果程序中同时定义了三种函数,会优先调用非模板函数,然后模板的显式具体化,最后模板函数
函数实例化
一开始定义的模板函数swap(T a,T b)它会告诉编译器生成函数的方式,但是还没有生成函数,而当调用swap(i1,i2)和swap(d1,d2)时就会生成相应的模板实例。模板的显式具体化也一样都是不会生成函数实例的,而是出现了具体类型之后再让编译器按照模板具体化创建函数实例。
在以前c++只能通过判断函数参数来实例化函数,这种叫做隐式实例化而现在的c++可以通过显式实例化了,格式如下
template returnType function<Type>(Type,Type);
上面的代码告诉编译器创建一个接受Type类型函数的实例,其中template为关键字,必须要写的。
示例:
template void swap<int>(int,int);//函数实例化
template<> void swap<int>(int,int);//模板具体化
上面的实例化告诉编译器使用swap模板生成一个接受int参数的函数实例,他已经在代码中了。
而模板显式具体化,告诉编译器当调用函数时如果参数为int时,就按照模板具体化定义的形式创建函数。
除了上面的显示实例化的手段,我们还可以在程序中手动使用函数模板来创建显示实例化,如下
template<typename T>
void swap(T a,T b){
T temp;
temp=a;
a=b;
b=temp;
}
……
int i=19;
double d=19.0;
cout<<swap<double>(i,d)<<endl;//fun(Type)来显示创建函数实例
上卖弄的模板函数与swap(i,d)是不匹配的,因为两个参数是不同的,但是通过显式实例化swap(i,d)来用模板 swap(T a,T b)创建double版本的函数,然后int的值会自动转换为double,以便函数使用
函数模板的调用
当同时有非模板函数,模板函数,模板显示具体化时定义时。会优先调用非模板函数,然后模板的显示具体化,最后模板函数
void f(int); //#1
float f(float,float=3); //#2
void f(char) //#3
char * f(const char *) //#4
char f(const chat *) //#5
template <class T>void f(const T &) //#6
template <class T>void fT *) //#7
如果调用
f('B')
上面#3,#5,#6都是原型匹配,但是#3,#5优先于#6,#6优先于#1
decltype
template<typename T>
void f(T a,T b){
ab=a+b;//ab是什么类型的呢?
}
所以c++11引入了关键字decltype来解决这个问题
int x;
decltype(x) y;//y is int
decltype(x+y) xy;//xy is x+y type
xy=x+y;
decltype(x+y)xy=x+y;//xy is x+y type
上面是三种decltype的简单使用。而对于如下标准格式
decltype(expression) var;
编译器会编译一个核对表,核对表的简化版如下
第一步:如果expresssion是一个没有用括号的标识符,则var的类型与该标识符相同,包括const等修饰符
double x=19.0;
double y=19.0
double &rx=x;
const double *pd;
decltype (x) w;//w is double
decltype (rx) u;
//u is double &
decltype (pd) v ;
//v const double *
第二步:如果experssion是一个函数调用,var是的类型与函数返回值一样
int f(int);
decltype( f(3)) m//m is int
Note:
这实际不会调用函数,编译器会根据函数原型判断返回值类型
第三步:如果expression是一个左值,则var为指向类型的引用。这好像意味着前面w应为引用类型,因为x是一个左值,但是这种情况下已经在第一步处理过了。要进入第三步expression不能是未用括号括起来的标识符
double xx=19.0;
decltype((xx))r2=xx;//r2 is double &
devltype(xx)w=xx//w is double
第四步:如果前面条件都不满足,则var的类型与expression类型相同
int j=3;
int& k=j;
int& n=j;
decltype(j+6) i1;//i1 is int
decltype(100L) i2;
//i2 is long
decltype(k+n) i3;
//i3 is int
c++11后置返回类型
template<typename T>
Type f(T a,T b){
return a+b;//a+b的返回上什么类型呢?
}
decltype (a+b) 不行吗?因为这时候还没有定义a,b,编译器不知道a,b是什么。c++11为auto新增了语法功能来解决这个问题,如下
auto f(int i1,int i2)->double;
auto告诉编译器返回类型在后面写着。他可以和decltype 连用
auto f(int i1,int i2)->decltype(x+y);
这样就完成了返回类型的自动换了。