函数模板与模板函数

时间:2020-12-25 05:33:55

1.函数指针——指针函数 

函数指针的重点是指针。表示的是一个指针,它指向的是一个函数,例子: 

int   (*pf)(); 

指针函数的重点是函数。表示的是一个函数,它的返回值是指针。例子: 

int*   fun(); 

2.数组指针——指针数组 

数组指针的重点是指针。表示的是一个指针,它指向的是一个数组,例子: 

int   (*pa)[8]; 

指针数组的重点是数组。表示的是一个数组,它包含的元素是指针。例子; 

int*   ap[8]; 

3.类模板——模板类(class   template——template   class) 

类模板的重点是模板。表示的是一个模板,专门用于产生类的模子。例子: 

template   <typename   T> 

class   Vector 



            … 

}; 

使用这个Vector模板就可以产生很多的class(类),Vector <int> 、Vector <char> 、Vector <   Vector <int>   > 、Vector <Shape*> ……。 

模板类的重点是类。表示的是由一个模板生成而来的类。例子: 

上面的Vector <int> 、Vector <char> 、……全是模板类。 

这两个词很容易混淆,我看到很多文章都将其用错,甚至一些英文文章也是这样。将他们区分开是很重要的,你也就可以理解为什么在定义模板的头文件.h时,模板的成员函数实现也必须写在头文件.h中,而不能像普通的类(class)那样,class的声明(declaration)写在.h文件中,class的定义(definition)写在.cpp文件中。

array是一个模板,array<int, 50>是一个模板实例 - 一个类型。从array创建array<int, 50>的过程就是实例化过程。实例化要素体现在main.cpp文件中。如果按照传统方式,编译器在array.h文件中看到了模板的声明,但没有模板的定义,这样编译器就不能创建类型array<int, 50>。但这时并不出错,因为编译器认为模板定义在其它文件中,就把问题留给链接程序处理。

现在,编译array.cpp时会发生什么问题呢?编译器可以解析模板定义并检查语法,但不能生成成员函数的代码。它无法生成代码,因为要生成代码,需要知道模板参数,即需要一个类型,而不是模板本身。

这样,链接程序在main.cpp 或 array.cpp中都找不到array<int, 50>的定义,于是报出无定义成员的错误。


关于一个缺省模板参数的例子: 

template   <typename   T   =   int> 

class   Array 



            … 

}; 

第一次我定义这个模板并使用它的时候,是这样用的: 

Array   books;//我认为有缺省模板参数,这就相当于Array <int>   books 

上面的用法是错误的,编译不会通过,原因是Array不是一个类。正确的用法是Array <>   books; 

这里Array <> 就是一个用于缺省模板参数的类模板所生成的一个具体类。 

4.函数模板——模板函数(function   template——template   function) 

函数模板的重点是模板。表示的是一个模板,专门用来生产函数。例子: 

template   <typename   T> 

void   fun(T   a) 



            … 



在运用的时候,可以显式(explicitly)生产模板函数,fun <int> 、fun <double> 、fun <Shape*> ……。 

也可以在使用的过程中由编译器进行模板参数推导,帮你隐式(implicitly)生成。 

fun(6);//隐式生成fun <int> 

fun(8.9);//隐式生成fun <double> 

fun(‘a’);//   隐式生成fun <char> 

Shape*   ps   =   new   Cirlcle; 

fun(ps);//隐式生成fun <Shape*> 

模板函数的重点是函数。表示的是由一个模板生成而来的函数。例子: 

上面显式(explicitly)或者隐式(implicitly)生成的fun <int> 、fun <Shape*> ……都是模板函数。 

模板本身的使用是很受限制的,一般来说,它们就只是一个产生类和函数的模子。除此之外,运用的领域非常少了,所以不可能有什么模板指针存在的,即指向模板的指针,这是因为在C++中,模板就是一个代码的代码生产工具,在最终的代码中,根本就没有模板本身存在,只有模板具现出来的具体类和具体函数的代码存在。 

提醒:在本文的几个术语中,语言的重心在后面,前面的词是作为形容词使用的。 

 

2 函数模板的异常处理

函数模板中的模板形参可实例化为各种类型,但当实例化模板形参的各模板实参之间不完全一致时,就可能发生错误,如:

template<typename T>       
void min(T &x, T &y)
{  return (x<y)?x:y;  }

void func(int i, char j)
{
   min(i, i);
   min(j, j);

   min(i, j);   
   min(j, i);
}

例子中的后两个调用是错误的,出现错误的原因是,在调用时,编译器按最先遇到的实参的类型隐含地生成一个模板函数,并用它对所有模板函数进行一致性检查,例如对语句

min(i, j);

先遇到的实参i是整型的,编译器就将模板形参解释为整型,此后出现的模板实参j不能解释为整型而产生错误,模板函数是没有隐含的类型转换功能的解决此种异常的方法有两种:

⑴采用强制类型转换,如将语句min(i, j);改写为min(i,int( j));

⑵用非模板函数重载函数模板

方法有两种:

① 借用函数模板的函数体

此时只声明非模板函数的原型,它的函数体借用函数模板的函数体。如改写上面的例子如下:

template<typename T>       
void min(T &x, T &y)
{  return (x<y)?x:y;  }

int min(int,int);

void func(int i, char j)
{
   min(i, i);
   min(j, j);

   min(i, j);
   min(j, i);
}

执行该程序就不会出错了,因为重载函数支持数据间的隐式类型转换。

② 重新定义函数体

就像一般的重载函数一样,重新定义一个完整的非模板函数,它所带的参数可以随意。

C++中,函数模板与同名的非模板函数重载时,应遵循下列调用原则:

• 寻找一个参数完全匹配的函数,若找到就调用它。若参数完全匹配的函数多于一个,则这个调用是一个错误的调用。

• 寻找一个函数模板,若找到就将其实例化生成一个匹配的模板函数并调用它。

• 若上面两条都失败,则使用函数重载的方法,通过类型转换产生参数匹配,若找到就调用它。

•若上面三条都失败,还没有找都匹配的函数,则这个调用是一个错误的调用。

 

3.函数模板与类模板有什么区别?

答:函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显式地指定。

     即函数模板允许隐式调用和显式调用而类模板只能显示调用