环境:win10 vs2013

1.什么是函数模板?
2.函数模板怎么写?
3.函数模板的实例化?
4.参数如何推演?
5.函数模板如何编译?
6.函数模板的模板参数列表?
7.函数模板重载?
8.函数模板的特化?

那就跟着我来看看函数模板吧
1.什么是函数模板?

  函数模板:代表了一个函数家族,该函数和类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本
  模板就像是一个模具,我们在生活中可以见到很多模具,我们可以根据模具做出不同的东西

2.函数模板怎么写?

函数模板的格式
template <typename T1,typename T2,...,typename Tn>
返回值类型  函数名(参数列表)
{
......
}
例如:
template <typename T>//T是模板形参名字,可以是任意起名
T Add(T left, T right)
{
   return left+right;
}
typename是用来定义模板参数的关键字,也可以使用class,但是建议使用typename(typename有些编译器不支持)
注意:不能使用struct代替class

3.函数模板的实例化?

模板是一个设计图,它本身不是类或者函数,编译器用模板产生指定的类或者函数的特定类型版本,产生模板特定类型得过程称为函数模板实例化。

4.参数如何推演?
实参推演发生在隐式实例化中

#include<iostream>
#include<stdlib.h>
using namespace std;
template<class T>
T Add(T left, T right)
{
    cout << "left类型:"<<typeid(left).name() << endl;
    //可以输出left的类型 (函数name()是对象typeid(left)的成员函数)
    return left + right;
}
int main()
{
    cout<<Add(10, 20)<<endl;
    cout<<Add('1', '2')<<endl;
    system("pause");
    return 0;
}
Add(10,20)//在底层调用Add函数时,使用的函数名是Add<int>
Add('1', '2')//在底层调用Add函数时,使用的函数名是Add<char>

编译器实参推演:
编译器在编译时,会根据传的实参的类型,通过函数模板生成不同的实例,在此代码中会生成一个int型的Add()函数和一个char型的Add()函数

注意:
Add(1,'1')
//在底层时是Add<int ,char>,这种不可以运行,因为"T"只代表一种类型(如果想传不同类型的数据,可以定义为T1,T2,......,Tn,T1和T2是不同的类型,可是T1只能代表一种类型),此加法函数中有int型数据和char型数据,所以编译器在实参推演时,不知道应该推演成int类型的数据还是char类型的数据
解决办法:
(1)显式实例化
     Add<int>(1,'1')//在实例化阶段,会进行隐式类型转换,把char型的转换成int型的2)强转
     Add(1,(int('1'))//进行强转,把char型转换成int型的     

运行结果:

类型形参转换
一般不会转换实参来匹配已有的实例化,相反会产生新的实例
编译器会执行两种转换:
1.const转换:接收const引用或const指针的函数可以分别用非const对象的引用或指针来调用(即就是说传的普通数据的引用,执行此函数时函数模板会生成参数类型为const的函数 )
2.数组或函数的转换:
   (1)数组实参将转换为指向其第一个元素的的指针;
   (2)函数实参将转换为当做指向函数类型的指针;

5.函数模板如何编译?
函数模板被编译了两次

分为两个阶段
(1)实例化之前,对函数模板进行语法检测,查看是否出现语法错误,如:遗漏分号(此种错误在此阶段检查不出来,即就是如果遗漏分号,编译器也不会报错)

(2)在实例化期间,检查模板代码,查看是否所有的调用都有效,生成不同类型函数(即生成合适的、满足要求的函数)的代码,再进行编译这些代码(此阶段会进行代码各种错误检测,如果有错编译器就会报错,直到代码没有任何错误)

6.函数模板的模板参数列表?
函数模板有两种类型参数:模板参数和调用参数

1.模板参数
模板参数有两种类型:类型形参和非类型形参
(1)模板形参名字只能在模板形参之后到模板声明或未定义的末尾之间使用,遵循名字屏蔽规则
#include<iostream>
#include<stdlib.h>
using namespace std;
typedef  int T;//把int重命名为T
template<class T>
//模板参数名字也是T(模板形参名字只能在模板形参之后到模板声明或定义的末尾之间使用,在此程序中就是说T只能在模板函数体中、返回值中、参数类型中使用也可在强制类型转换中使用)
T Add(T left, T right)
{
    return left + right;
}
T a;
int main()
{
    cout<<Add(10, 20)<<endl;
    cout<<Add('1', '2')<<endl;
    system("pause");
    return 0;
}
名字屏蔽规则就是说:
   在此代码中有一个typedef int T,在函数模板中有一个template<class T>,但是在函数模板中的T代表的是模板形参T,不是int型的,只是一个类型,会根据实参类型的不同而变化,模板形参中的T暂时会把typedef定义的T屏蔽(这样的话就可以生成根据传的实参的类型,生成不同的函数),出了函数模板的作用域,T就是int型的

(2)模板形参的名字在同一个模板中不允许出现多次,只能使用一次
例如:
template<class T, class T>
void Test(T a, T b);
编译器就会报错:错误  重定义 模板 参数“T”

(3)模板形参的前面必须加上关键字:class  或者  template
注意:在函数模板的内部不能指定缺省的模板形参

注意:定义模板函数时,模板形参不能为空

2.非模板类型参数
非模板类型参数:是在模板内部定义的常量,在需要常量表达式的时候,可以使用非模板类型参数
例如:求数组的长度(求数组的长度时,不仅需要数组的类型,还需要数组元素的个数)
template<class T,int N>//T是数组的类型,N是数组元素的总个数

7.函数模板重载?

#include<iostream>
#include<stdlib.h>
using namespace std;
template<class T>
T Max(const T& left, const T& right)
{
    return left > right ? left : right;
}
template<class T>
T Max(const T&a, const T&b, const T&c)
{
    T max = Max(a, b);
    return max > c ? max : c;
}
int main()
{
    cout << Max(10, 200) << endl;
    cout << Max(1, 4, 8) << endl;
    cout << Max(1.1, 2.2) << endl;
    cout << Max(1.1, 2.2, 3.3) << endl;
    system("pause");
    return 0;
}

运行结果:

注意:函数模板的所有重载版本的声明到必须放在该函数被调用之前
即就是函数模板的所有形式的声明都必须放在该函数被调用之前,因为编译器编译代码时是在main函数之前或main函数之中找需要调用的函数的定义或声明的,如果放在之后,编译器就会找不到

8.函数模板的特化?

函数模板可以生成所有满足实参类型的函数,但是有时实例化出的函数是错误的,或者是不能编译的。
特化:就是特别写出函数模板不能解决的某个类型的函数
特化形式:
1.定义模板关键字:template< >
2.函数名后跟一对尖括号(<>),尖括号中指定这个特化定义的模板形参
3.函数形参表:一定要与函数模板的基础参数类型完全相同(如果不相同,会产生无法想象的后果)
4.函数体

函数模板的特化很复杂,一不小心就会写出有很多bug的代码,自己写一个解决此问题的函数也可以解决函数模板解决不了的问题,而且还比较方便,所以建议遇到此问题时,尽量自己写一个函数。

函数模板注意的问题

1.对于非模板函数和同名函数模板,都可以完成程序的要求,那编译器会优先调用非函数模板,而不会从该模板生成一个函数,但是如果函数模板生成的函数更匹配要求,那就选择该模板;
2.模板函数不允许自动类型转换,但是普通函数可以进行自动类型转换;
3.一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。

更多推荐

函数模板