zoukankan      html  css  js  c++  java
  • 模板函数函数模板 Function Template(C++Primer10)

    在写这篇文章之前,xxx已经写过了几篇关于改模板函数主题的文章,想要了解的朋友可以去翻一下之前的文章

        

        每日一道理
    喜马拉雅直冲霄汉,可上面有攀爬者的旗帜;撒哈拉沙漠一望无垠,可里面有跋涉者的脚印;阿尔卑斯山壁立千仞,可其中有探险者的身影;雅鲁藏布江湍急浩荡,可其中有勇敢者的故事。

    10 函数模板

    10.1 函数模板定义

    template <typename A, typename B, int size> A Func(const B(&rArray)[size]) { return A();}

    >关键字template面后是用逗号隔分的模板数参表template parameter list, 用<>括包起来; 模板数参列表不能为空;(特化时可以); 

    模板数参可认为模板类型数参template type parameter,代表一种类型; 也可所以模板非类型数参template nontype parameter, 代表一个常量表达式;

    一般情况尖括号<>中的typename代表C++内置数据类型, class代表C++数据类型(自定义);

    template <class Type, int size>
    Type min(const Type(&rArray)[size]) //SIZE作为数参一分部, 译编器会检查参实度长是不是匹配
    {
        Type minVal = rArray[0];
        for(int i=0; i<size; i++)
        {
            if(rArray[i]<minVal)
                minVal = rArray[i];
        }
        return minVal;
    }
    //---
    const int size = 5;
    int iArray[size] = {5, 7, 3, 22, 99};
    cout<<min(iArray)<<endl;

    >类型和值的替换程过称为模板实例化template instantiation;

    模板类型数参被作当一个类型标识符, 应用方法和内置或户用定义类型一样; e.g.声明变量, 强制类型转换;
    模板非类型数参被用作常量值; e.g.组数小大, 枚举常量初值;

    Note 如果在全局域中声明白与模板数参同名的对象,,函数或类型, 则该全局名将被藏隐; 
           函数模板定义中声明的对象或类型不能与模板数参同名;(效无或error);

    模板类型数参名名可以用来指定函数模板的返回值; 模板数参名在统一模板数参表中只能被应用一次, 但是模板数参名可以在多个函数模板声明或定义之间复重应用; 一个模板的定义和多个声明所应用的模板数参不须要同相; 模板数参在函数数参表中涌现的次数没有制限; 每一个模板类型数参后面都必须有关键字class或typename;

    函数模板数参表中typename和class意思同相; 都可以用来声明模板数参表中的不同模板类型数参;<<Design and Evolution of C++>>    

    Note 为了让译编器够能分析模板定义, 户用必须指示译编器, 哪些表达式是类型表达式: typename;

     

    template <class Parm, class U>
    Parm minus(Parm* array, U value)
    {
        typename Parm::name* p;//指针声明
        Parm::name* p; //译编器不知道这是指针声明还是乘法
    }

    Note 函数模板声明为inline或extern时, 指示符放在模板数参表面后, 不能放在关键字template后面;


    10.2 函数模板实例化

    函数模板指定现实类型或值构造出独立函数的程过: 模板实例化template instantiation;

    这个程过是式隐生发的, 可看做是函数模板被用调或取函数模板的址地的反作用;

    int (*pf)(int (&)[10]) = &min;// min(int(&)[10]);

    译编器会检查函数用调中供提的函数参实的类型. 用函数参实的类型来定决模板参实的类型和值的程过被称为模板参实推演template argument deduction;

    Note 在取函数模板实例的址地时, 必须通过上下文为模板参实定决一个一唯的类型或值;

    typedef int (&rai)[10];
    typedef double (&rad)[20];
    void func( int (*)(rai) ){}
    void func( double (*)(rad) ){}
    //---
    func(&min);//error, overload-function

    >译编错误: func()被载重了, 译编器法无定决Type的一唯类型, 也法无定决size的一唯值; 用调func()法无实例化函数;


    10.3 模板参实推演

    函数模板用调时, 对函数参实类型的检查定决模板参实的类型和值, 这个程过称为模板参实推演template argument deduction;

    >函数参实必须是一个组数类型的左值; 这里的pval是函数数参, 所以转换为指针变量, 与组数左值类型不匹配; (传数参时组数->指针匹配, 指针->组数不可)

    template <class Type, int size>
    Type min( Type (&r_array)[size] ) { /* ... */ }
    void f( int pval[9] ) {
    // 错误: Type (&)[] != int*
    //int jval = min( pval );
    }

    模板参实推演时, 函数的参实类型一不定要严厉匹配函数数参类型;
    答应的类型转换: 左值转换, 定限转换, 到一个类基的转换;

    左值转换括包: 左值到右值的转换, 组数到指针的转换, 函数到指针的转换;

    函数模板参实推演的通用算法:
    1)顺次检查每一个函数参实, 确定在每一个函数参实的类型中涌现的模板数参;
    2)找到模板数参, 通过检查函数参实的类型, 推演应相的模板参实;
    3)函数数参类型和函数参实类型可以转换: -左值转换 -定限饰修符转换 - 派生类到类基的转换;
    4)如果在多个函数数参中找到统一个模板数参, 从每一个应相函数参实推出演的模板数参必须同相(模板数参会被绑定在第一个类型上)


    10.4 示显模板参实

    示显指定explicitly specify 模板参实;

    e.g. func<usigned int>(1024); //1024被有序标准转换成unsigned int类型;

    当函数模板参实被示显指时定, 函数参实转换成应相函数数参的类型可以应用任何式隐类型转换;

    显式特化explicit specification, 只能省略尾部的参实;

     

    char ch; unsigned int ui;
    template <class T1, class T2, class T3>
    T1 sum( T2, T3 );
    typedef unsigned int ui_type;
    ui_type loc1 = sum( ch, ui );//error, T1不能被推演
    ui_type loc2 = sum< ui_type, char, ui_type >( ch, ui );//ok
    ui_type loc3 = sum< ui_type, char >( ch, ui );//ok
    ui_type (*pf)( char, ui_type ) = &sum< ui_type >;//ok
    ui_type loc4 = sum< ui_type, , ui_type >( ch, ui );//error, 只能省略尾部的参实

    >对于载重函数的函数指针类型参实, 为免避二义性, 应用显式指定;

     

    template <class T1, class T2, class T3>
    T1 sum( T2 op1, T3 op2 ) { /* ... */ }
    void manipulate( int (*pf)( int,char ) );
    void manipulate( double (*pf)( float,float ) );
    int main( )
    {
        manipulate( &sum );// 错误: 二义
        manipulate( &sum< double, float, float > );// 用调: void manipulate( double (*pf)( float, float ) );
    }

    提议在可能的情况下省略显式模板参实, 让函数能功更泛化;

    Note 返回值法无推演, 推演只作用在数参上, 如果返回值和数参同相, 则可以被直接推演;


    10.5 模板译编模式

    C++模板译编模式 template compilation model 指定了对于定义和应用模板的程序的织组方法的要求; 
    译编模式: 括包模式 Inclusion Model 和 分离模式 Separation Model;

    10.5.1 括包译编模式

    模板定义在头件文中, 应用模板实例前括包模板定义;

    缺陷: 模式模板体body描述了实现细节, 这些是我们望希对户用藏隐的;
           函数模板定义很大的情况下, 头件文中的细节层次变得可不接受;
           多个头件文之间译编同相的函数模板定义加增译编时光;
    10.5.2 分离译编模式

    函数模板声明在头件文中; 

     

    // model2.h // 分离模式: 只供提模板声明
    template <typename Type> Type min( Type t1, Type t2 );
    // model2.C // the template definition
    export template <typename Type>
    Type min( Type t1, Type t2 ) { /* ...*/ }
    // user.C
    #include "model2.h"
    int i, j;
    double d = min( i, j ); //用法, 须要一个实例

    >关键字export告知译编器在生成被其他件文应用的函数模板实例时须要这个模板定义(可导出的函数模板), 译编器须要证保模板定义是可见的;
    (有些译编器可能不要求export; 标准C++要求在template之前记标export)

    关键字export在头件文的模板声明中不是必须的;(在cpp实现中须要)

    一个模板函数只能被定义为export一次; 译编器每次只处置一个件文, 法无检测到模板函数在多个文本件文中定义为export的情况; 可能生发的情况:
    1)链接错误, 函数模板在多个件文中被定义;
    2)译编器多次为统一个模板参实集合实例化函数模板, 函数模板实例复重定义, 链接错误;
    3)译编器可能只用其中一个export函数模板定义实例化函数模板, 疏忽其他定义;

    Note 不是全部的译编器都支撑分离模式; (VC2010, g++不支撑)

    10.5.3 显式实例化声明

    显式实例化声明助帮控制并加增译编器对模板多次实例化的情况, 加增译编时光; 

     

    template <typename Type>
    Type sum( Type op1, int op2 ) { /* ... */ }
    template int* sum<int*>(int*, int);

    显式实例化声明在程序中只能涌现一次, 函数模板的定义必须可见;

    显式实例化声明是与一个译编项选合联应用的, 该项选压抑程序中模板的式隐实例化; e.g. /ft-; (这个模式下显式实例化声明是必须的)


    10.6 模板显式特化

    模板显式特化定义explicit specialization definition;

     

    template <class T>
    T max( T t1, T t2 ) {
        return (t1 > t2 ? t1 : t2);
    }
    //特化 specialize
    typedef const char *PCC;
    template<> PCC max< PCC >( PCC s1, PCC s2 ) {
        return ( strcmp( s1, s2 ) > 0 ? s1 : s2 );
    }

    >由于有了显式特化, 程序在用调max()时模板不对const char*类型行进实例化, 而是直接用调特化的定义;

    如果模板参实可以从函数数参中推出演来, 模板参实的显式特化可以省略

    template<> PCC max( PCC s1, PCC s2 )

    >省略尖括号"<>"分部;

    应用函数模板特化时, 必须能让全部应用特化的件文看到其声明, 否则译编器找不到特化, 会实例化基模板; 
    如果统一程序在一个件文中实例化函数模板实例(声明可不见), 在另一个件文中又用调其显式特化, 程序法非;

    Note: 显式特化的声明该应被放在头件文中, 并且在全部应用函数模板的程序中括包此头件文;


    10.7 载重函数模板

    载重的函数模板可能会致导二义性;

     

    template <typename T>
    int min5( T, T ) { /* ... */ }
    template <typename T, typename U>
    int min5( T, U );

    >对于相似min(1024, 1); 这样的用调, T和U可所以同为int型, 这样两个模板都可以被实例化; 须要显式指定模板参实免避二义性错误;

    >min5(T,U);处置的用调集是由min5(T,T)处置的超集, 所以只供提min5(T,U)的声明以可就, min5(T,T)该应删除;

    最特化most specialized 同相模板名字和数参个数; 对于不同类型的应相函数数参, 一个模板是另一个的超集;

    template <typename Type> Type sum( Type*, int );//1)
    template <typename Type> Type sum( Type, int );//2)
    int ia[1024];
    // Type == int ; sum<int>( int*, int ); or
    // Type == int*; sum<int*>( int*, int ); ??
    int ival1 = sum<int>( ia, 1024 );

    >1)更特化; 2)是1)的超集;


    10.8 斟酌模板函数实例的载重析解

    函数模板可以被载重, 函数模板可以与一般函数同名;

     

     

    // 一般函数
    int min( int a1, int a2 ) {
        min<int>( a1, a2 );
    }

    >通过一般函数, 无论何时应用整型参实, 程序都用调min(int,int)的特化本版;

    一般函数和函数模板的函数载重析解骤步:

    1)生成候选函数集; 
    斟酌与函数用调同名的函数模板, 如果模板参实推演功成则实例化一个函数模板, 则该模板作为一个候选函数;

    2)生成可行函数集;
    保存候选函数会合可以用调的函数;

    3)对类型转换分划品级;
    a)如果只选择了一个函数, 则用调该函数; b)如果用调是二义的, 则从可行函数会合去掉函数模板实例;

    4)只斟酌可行函数会合的一般函数, 实现载重析解程过;
    a)如果只选择了一个函数, 则用调该函数; b)否则是二义的;


    10.9 模板定义中的名字析解

    类型依赖于模板数参depend on a template parameter;

    模板定义中的名字析解分两个骤步行进:
    首先, 不依赖于模板数参的名字, 在模板定义时被析解; (一般函数 e.g. print("string");)
    其次, 依赖于模板数参的名字, 在模板被实例化时被析解; (依赖于模板的函数; e.g. print(Type t);)

    Note 函数模板的设计者必须确保为模板定义中用到的, 全部不依赖于模板数参的名字供提声明; 否则译编错误;

    在源代码中模板被实例化的置位称为模板的实例化点point of instantiation; 
    如果模板实例要多次应用, 译编器自由选择这些实例化点之一来真正实例化该函数模板; 因此在模板的任何一个实例被应用之前, 该应在头件文中给出全部必须的声明;


    10.10 名字空间和函数模板

    函数模板定义可以放在名字空间中;

    namespace cplusplus_primer {
    // 模板定义被藏隐在名字空间中
    template<class Type>
    Type min( Type* array, int size ) { /* ... */ }
    }
    int ai[4] = { 12, 8, 73, 45 };
    int size = sizeof(ai) / sizeof(ai[0]);
    using cplusplus_primer::min; // using 声明
    min( &ai[0], size );

    作为括包模板定义的库的户用, 如果想为库中的模板供提特化, 必须证保它们的定义被适合地放置在含有原始模板定义的名字空间内;
    1)把模板特化放在基模板在所的名字空间内;
    2)用外围名字空间名饰修名字空间成员名;

     

     

     

    template<> SmallInt cplusplus_primer::min<SmallInt>( SmallInt* array, int size ){ /* ... */ }


    10.11 函数模板示例

    快速排序sort();

     

    #include "Array.h"
    template <class elemType>
    void sort( Array<elemType> &array, int low, int high ) {
        if ( low < high ) {
            int lo = low;
            int hi = high + 1;
            elemType elem = array[lo];//基数
            for (;;) {
                while ( min( array[++lo], elem ) != elem && lo < high ) ;
                while ( min( array[--hi], elem ) == elem && hi > low ) ;
            if (lo < hi)
                swap( array, lo, hi );//较大数放边右, 较小数放左边
            else break;
            }
            swap( array, low, hi );//基数放间中
            //分治递归
            sort( array, low, hi-1 );
            sort( array, hi+1, high );
        }
    }

    ---End---

    文章结束给大家分享下程序员的一些笑话语录: 女人篇
      有的女人就是Windows虽然很优秀,但是安全隐患太大。
      有的女人就是MFC她条件很好,然而不是谁都能玩的起。
      有的女人就是C#长的很漂亮,但是家务活不行。
      有的女人就是C++,她会默默的为你做很多的事情。
      有的女人就是汇编虽然很麻烦,但是有的时候还得求它。
      有的女人就是SQL,她会为你的发展带来莫大的帮助。

  • 相关阅读:
    Java 基础(接口的应用:代理模式 Proxy)
    Appium 环境配置
    破解CCleaner
    数据驱动
    (C语言内存二十)C语言内存泄露(内存丢失)
    (C语言内存十九)C语言野指针以及非法内存操作
    (C语言内存十八)malloc函数背后的实现原理——内存池
    (C语言内存十七)栈溢出攻击的原理是什么?
    (C语言内存十六)C语言动态内存分配
    (C语言内存十五)用一个实例来深入剖析函数进栈出栈的过程
  • 原文地址:https://www.cnblogs.com/xinyuyuanm/p/3060086.html
Copyright © 2011-2022 走看看