C++函数模板学习

来源:互联网 发布:js继承是什么意思 编辑:程序博客网 时间:2024/05/16 16:24
   1. 模板定义
          C++是一种强类型语言,它要求对于每一个实现都要有一个实例。如对于简单的min()的int和double两种实例都要事先定义,这给程序员带来很多不便性。这一章我们来学习函数模板,看它是如何解决这个问题的。函数模板提供一种用来自动生成各种类型函数实例的算法,程序员只需要补始化其中部分参数和返回值,或者都不初始化,只需要申明通用的类型,而函数体则不需要改变。如下面实例:
template <class Type>
Type min( Type a, Type b ) {
return a < b ? a : b;
}
         
          关键字template 总是放在模板的定义与声明的最前面关键字后面是用逗号分隔的模板参数表template parameter list 它用尖括号<>括起来。该列表是模板参数表不能为空模板参数可以是一个模板类型参数template type parameter 它代表了一种类型也可以是一个模板非类型参数template nontype parameter它代表了一个常量表达式。上例中的class也可以是typename,这两个意义相同。
 
        如果在全局域中声明了与模板参数同名的对象函数或类型则该全局名将被隐藏。同时函数定义中的的类型不能同模块类型相同。
typedef double Type;
template <class Type>
Type min( Type a, Type b )
{
typedef int Type;//error
}
    
      上例中,min函数中的Type只是作为模板参数而不是double类型。
       当一个名字被声明为模板参数之后它就可以被使用了一直到模板声明或定义结束为止。模板类型参数被用作一个类型指示符,可以出现在模板定义的余下部分,它的使用方式与内置或用户定义的类型完全一样,比如用来声明变量和强制类型转换。模板非类型参数被用作一个常量值可以出现在模板定义的余下部分。它可以用在要求常量的地方或许是在数组声明中指定数组的大小或作为枚举常量的初始值。
template <class Type, int size>
Type min( const Type (&r_array)[size] )
{
/* 找到数组中元素最小值的参数化函数 */
Type min_val = r_array[0];
for ( int i = 1; i < size; ++i )
if ( r_array[i] < min_val )
min_val = r_array[i];
return min_val;
}
 
实例化:
in arry[] = {1,2,3,4,5,6};
int minum = min(arry); //size = 6 r_array = arry;
 
//注:这种用法在VC6.0中不支持而在G++和VS2005中都已经支持。
 
 一个模板的定义和多个声明所使用的模板参数名无需相同
template <class T> T min( T, T );
template <class U> U min( U, U );
// 模板的真正定义
template <class Type>
Type min( Type a, Type b ) { /* ... */ }
 
上面两个声明是同一个模板。最后只需要一个定义即可。
 
函数模板也可以声明为extern和inline函数。如下所示。
// ok: 关键字跟在模板参数表之后
template <typename Type> inline
Type min( Type, Type )
{
}
      
        在模板中有一个高深的typename,我们在这里只稍微看一下它的用法。关键字typename 也可以被用在模板参数表中以指示一个模板参数是一个类型。
       编译器不知道name 是否为一个类型,因为它只有在模板被实例化之后才能找到Parm 表示的类的定义。为了让编译器能够分析模板定义用户必须指示编译器哪些表达式哪些是类型表达式。告诉编译器一个表达式是类型表达式的机制是在表达式前加上关键字typename 例如,如果我们想让函数模板minus()的表达式Parm::name 是个类型名因而使整个表达式是一个指针声明我们应如下修改
template <class Parm, class U>
Parm minus( Parm* array, U value )
{
typename Parm::name * p; // ok: 指针声明
}
 
2 模板参数推演
 
        当函数模板被调用时对函数实参类型的检查决定了模板实参的类型和值这个过程被称为模板实参推演。要想成功地进行模板实参推演函数实参的类型不一定要严格匹配相应函数参数的类型,下列三种类型转换是允许的:左值转换、限定转换和到一个基类转换(该基类根据一个类模板实例化而来的)左值转换包括从左值到右值的转换、从数组到指针的转换或从函数到指针的转换。
实例:
template <class Type>
Type minn( const Type* array, int size )
{
}
 
int ai[4] = { 12, 8, 73, 45 };
int *pai = (int*)&ai;
minn( ai,4 );//这样是允许的从array to pointer
minn(pai,4);//允许限定转换
 
        如果函数参数的类型是一个类模板且如果实参是一个类,它有一个从被指定为函数参数的类模板实例化而来的基类,则模板实参的推演就可以进行。
 
3 显式转化
 
         有此时候不能根据用户传进来的参数决定所有模板函数的值类型,如函数的返回值。还有情况下用户希望自己指定参数类型,这时就需要显式地指出参数模板的类型。
template <typename Type, class T, class U>
Type muli_sum(T arr1,U arr2)
{
}
 
        在上例中,用户使用时就可以如下:
double dd = mult_sum<double,int,int>(a,b);
     
        如果只有一个参数类型,编译器是无法知道的,我们就可以只指定一个,如上例中只需要指定double类型,当然注意了,有多个类型时,如果我们只想指定其中的部分参数类型,我们可以省略尾部的参数类型,如下操作:
double dd = mult_sum<double>(a,b); //ok
double dd = mult_sum<double,,int>(a,b); //error
double dd = mult_sum<double,int>(a,b); //ok
 
4.          模板编译模式
          在C++标准中实际上支持两种编译方式,一种是包含编译式,另一种是分离编译式。包含编译式是指模板函数的声明和定义都是在同一个文件中,一般来说是一个.h 头文件。分离编译式则不同,声明和定义分别在不同的文件中,但是在定义之前需要加一个关键字export,如下:
//template.h
template <class Type, int size>
Type minn( const Type (&r_array)[size] )
 
//template.cpp
export template <class Type, int size>
Type minn( const Type (&r_array)[size] )
{......}
         现在我们碰到的的编译方式都是包含编译式。而一般的编译器也只支持包含编译式,即使是G++和VS2005也不支持,大家可以去尝试一下。
5. 显式特殊化与重载
         有时我们定义了一个模板,但是它并不能解决所有的问题,这时我们就要对它进行特殊化处理或者重载它。
如下面的通用模块:
template <class Type>
Type minn( Type a, Type b )
{
    return (a<b?a:b);
}
       
        而我们想要使用minn函数名来处理两个字符串的大小,而且最后能够返回小的字符串,那我们该怎么做,显然不能使用上面的通用模板。这里我们就需要显式地定义一个自己的处理函数。
 
         在模板显式特殊化定义中,先是关键字template 和一对尖括号<>,然后是函数模板特化的定义。该定义指出了模板名、被用来特化模板的模板实参,以及函数参数表和函数体。
#include <cstring>
// const char* 显式特化:
// 覆盖了来自通用模板定义的实例
typedef const char *PCC;
template<> PCC minn< PCC >( PCC s1, PCC s2 ) {
return ( strcmp( s1, s2 ) > 0 ? s2 : s1 );
}
                像上面显式定义(感觉像是重定义了,实际上不是)以后,在使用时用户可以给minn传递两个int或者char* 类型,编译器会自动选择合适的类型进行转换。而这个标准,VS2005还不支持,而G++却可以很好的支持。在使用中显式特殊化在模板函数声明之后,就可以声明了,而不需要等到模板函数定义之后。而一般编译器对要求模块函数的声明要定义在一起,而且显式声明比须出现在所有使用它的地方,所以建议将显式特殊化和模块放在一起,在使用的时候,将其头文件包含进去就不会出现问题了。
         当然我们也可以直接定义一个 ordinary function declaration,来解决问题,这与上面的有什么不同呢?为什么要这样定义?我觉得也就是为了解决编译不能推演所有用户数据类型的问题。我觉得定义一个ordinary function 更好些,它不会像显式特化要求那么严格,简单容易理解而且可以少写几个单词。
const char *max(const char*,const char*){。。。}这种定义也并不是所有编译都支持的。
         
        现在我们总结一下,下面的几个定义是可以同时并存的,在C++支持比较好的编译器上可以编译通过。
//通用的模板
template <class Type>
Type minn( Type a,Type b)
{
        return (a<b?a:b);
}
 //精确匹配char*类型
typedef char* PCC;
template<> PCC minn< PCC >( PCC s1, PCC s2 )
 {
        return ( strcmp(s1,s2)>0?s2:s1 );
 }
 //精确匹配const char*类型;
const char* minn(const char*s1,const char*s2)
{
 return (strcmp(s1,s2)>0?s1:s2);
}
 
实例应用:
        int c = minn(19,20);
        const char* ca = "aaa", cb = "bbb";
        char* aa = "aaa",bb = "bbb";
        const char *cpp = minn(ca,cb);
        char *pp = minn(aa,bb);
        cout<<c<<endl; //19
        cout<<cpp<<endl; //bbb
        cout<<pp<<endl; //aaa
          
          从上面的例子,我们可以看出不同的定义感觉就是重载而实际上又不是,我们现在来看一下真正的模板重载是什么样的。如下所示:
template <typename Type>
class Array{ /* ... */ };
template <typename Type>
Type sum( const Array<Type>&, int );
 
template <typename Type>
Type sum( const Type*, int );
 
template <typename Type>
Type sum( Type, int);
       
        虽然编译器可以编译通过,但是在使用时并不总是正确而且不好理解,现在我们看一个例子以便理解:
        int ia[8];
        //Type = int; sum <int> (int*, int)
        //Type = int*; sum<int*> (int*, int)
       sum(ia,8);// 究竟调用哪一个?
       实际上,int会被选择。模块重载函数定义中指出谁最特殊就选谁,而Type*显然比Type特殊些,所以在这种模棱两可的情况下,实例就选中了int。
      差不多就这些了,基本上都是从书中学来的.
原创粉丝点击