函数模板与模板函数
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.函数模板与类模板有什么区别?
答:函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显式地指定。
即函数模板允许隐式调用和显式调用而类模板只能显示调用