• (C/C++学习笔记) 十九. 模板


    十九. 模板

    ● 模板的基本概念

    模板(template)

    函数模板:可以用来创建一个通用功能的函数,以支持多种不同形参,进一步简化重载函数的函数体设计。

    语法: template <<模板的类型形参表>> <函数声明>

     

    类模板:代表一簇类, 用户可以为类定义一种模式, 使得类中的某些数据成员, 某些成员函数的参数, 返回值或局部变量能取任意类型(包括系统预定义的和用户自定义的)

    语法: template <<模板的类型形参表>> <类声明>

     

    ※ 泛型编程/泛化编程(generic programming):模板是泛型编程的基础. 泛型编程是独立于流行的面向对象编程的一种新的开发方式; 泛型编程的主要思想是将算法从特定的数据类型中抽象出来, 使算法成为通用的框架(frame), 它可以作用于各种不同的数据类型.

     

    ● 函数模板的格式

    函数模板的格式是:

    template <<模板形参表>>        //实际写代码时, 只用一个尖括号

    返回类型 函数名(形参表)

    {

        函数体

    }

    注意:

    1. 模板形参表有下面几种形式:

    class <数据类型标识符>    //尖括号不用写, 下同

    typename <数据类型标识符>

    ③ 类型说明符 <数据类型标识符>    

    ※ ①②情况的模板形参称为模板类型参数, 类型参数(typename/class后面的标识符所代表的东西, 不同于传统函数中的数值形式的参数)可以用来指定函数模板本身的形参类型, 返回值类型, 以及函数体中的局部变量

    ③情况的形参称为模板非类型形参, 类型说明符是说明变量是什么类型的标识符, int; 这一种模板的类型形参与普通的函数形参的形式相同, 这说明这个形参的类型是确定的, 不像类型参数还需要推导它到底是哪个类型

    2. 函数返回值类型可以是普通类型(void),也可以是模板形参表中指定的类型。

     

    函数模板定义后,就可以用它生成各种具体的函数(称为模板函数)。

    //求绝对值

    #include <iostream>

    using namespace std;

     

    template <typename T>

    T abs (T x)    //其实就是把原来的普通的数据类型换成了这里的"类型参数"T

    {

        return x<0? -x: x;

    }

     

    void main()

    {

        int n=-5;

        double d=-5.5;

        cout<<abs(n)<<endl;

        cout<<abs(d)<<endl;

    }

     

    在上述主函数调用abs(), 编译器可以从实参的类型推导出函数模板的类型参数T到底是什么类型. 但类型参数的类型确定后, 编译器将以函数为样板, 生产一个模板函数, 这一过程称为函数模板的实例化, 该模板函数称为函数模板abs的一个实例. 上例中的两个模板函数是:

    int abs (int x)

    {

        return x<0? -x: x

    }

     

    double abs (double x)

    {

        return x<0? -x: x

    }

     

    注意: ① 函数模板本身在编译时不会生产目标代码, 只有模板生产的实例(模板函数)会生成目标代码;

    ② 被多个源文件引用的函数模板, 应当连同函数体一同放在头文件中, 而不能像普通函数那样只将声明放在头文件中;

    ③ 函数指针也只能指向模板的实例, 而不能执行模板本身

     

    函数模板实例化分为显式实例化与隐式实例化:

    (1) 显式实例化:

    函数名<具体类型名1,具体类型名2..., 常量表达式> (实参表)

    说明:

    ① 根据< >中给出的具体类型,用类似于函数调用实参与形参结合的方式,将模板参数表中的参数化类型一一实例化成具体的类型, 函数中的参数化类型也

    一一实例化。

    ② 如果模板参数表中有形式参数,还需要用常量表达式去初始化。

    例如:

    使用add<double>(8, 9)T add(T x, T y) 实例化成:

        double add(double, double)

    使用sum<int, 100> T sum() 实例化成:

        int sum(), size获得初值100;

     

    (2) 隐式实例化:

    隐式实例化的格式为函数调用式,实例化过程是在实参与形参结合时,用实参的类型实例化形参对应的参数化类型。

    例如:

    使用add( 'A','B') T add(T x, T y) 实例化成:

    char add(char, char)

    注意:

    使用隐式实例化无法初始化模板参数表中的普通类型的形参,如果模板参数表中使用普通类型参数,必须使用显式初始化。

     

    //上面的例子是隐式实例化, 下面是显式实例化的例子:

    #include <iostream>

    using namespace std;

    template <class type,int len>    //定义一个模板类型, 模版参数有类型形参type, 也有非类型形参len

    type Max(type array[len])    //定义函数模板, 形参是type类型的包含len个元素的数组

    {

        type ret = array[0]; //定义一个type类型的变量, 并赋初值为数组的第0个元素的值

        for(int i=1; i<len; i++) //循环次数, 即比较大小的次数为len-1, 遍历数组元素;

        //这里的i不能为0, 因为下面的循环体要求在第一次循环中, ret的值(即上面已经被赋予的array数组第0个元素的值)要和array数组的第1个元素的值进行比较

        {

            ret = (ret > array[i])? ret : array[i];    //ret用来存储经经比较后的值

        }

        return ret;    //返回经比较后得到的最大值

    }

    void main()

    {

        int iset[5] = {1,2,3,4,5};    //定义一个整型数组

        int iret = Max<int,5>(iset);    //调用函数模板Max, 并使其实例化

        cout<<iret<<endl;

        double dset[3] = {10.5,11.2,9.8};    //定义一个实数数组

        double dret = Max<double,3>(dset);    //调用函数模板Max

        cout << dret << endl;

    }

     

    int iset[5] = {1,2,3,4,5};    

    int iret = Max<int,5>(iset);    

    对于上面两句iret共比较了5-1=4--首先iret的初值12, 然后23, 然后34, 然后45;

    如果, for循环的条件是(int i=0; i<len; i++), 那么将会进行5次比较--首先iret(值为1)iset数组的第0个元素比较, 以此类推

     

    类模板定义的语法为

    类模板定义的语法为:

    template <<模板形参表>>    //类模板形参表与函数模板的形式是一样的

    class 类名    //不是模板名

    {

    类成员声明;

    };

     

    模板类的成员函数还可以在类外定义,其语法如下:

    template <<模板形参表>>

    <返回类型> <类名> <<模板参数名表>><函数名> <(参数表)>

    {

    函数体;

    }

     

    类模板实例化的语法:

    1. 生成新类:

    类名 <<类型实参表>>

    类名 <<类型实参表>> 对象1, 对象2, , 对象n (实参表);

     

    注意:

    ① 类模板中的成员函数可以是函数模板,也可以是普通函数,

    ② 一个类模板的类型参数实例化后, 即这个类模板被其它代码引用时, 类模板会被实例化, 生成具体的模板类.

    //例如,下面定义了一个模板类Student,为了增强类的适用性,将学号设计成参数化类型,它可以实例化成字符串、整型等;

    //将成绩设计成参数化类型,它可以实例化成整型、浮点型、字符型(用来表示等级分)等;

    template <class TNO, class TScore, int num> // TNO,TScore 为参数化类型

    class Student

    {

    private:

         TNO StudentID[num]; //参数化(parameterized)类型(可变类型)数组,存储姓名

         TScore score[num]; //参数化类型数组,存储分数

    public:

         TNO TopStudent() //普通函数

         {

         return StudentID[0];

         }    

         int BelowNum(TScore ascore) //成员函数模板

         {

     return 0;

         }

         void sort() //普通函数

         {

         }

    };

    //////////////////////////////////////////////////

    //模板类的成员函数还可以在类外定义

    template <class TNO, class TScore, int num>

    class Student

    {

    private:

    TNO StudentID[num];

    TScore score[num];

    public:

    TNO TopStudent();

    int BelowNum(TScore ascore);

    void sort();

    };

    template <class TNO, class TScore, int num>

    int Student<TNO, TScore, num>::BelowNum(TScore ascore)

    {

    return 0;

    }

    template <class TNO, class TScore, int num>

    void Student<TNO, TScore, num>::sort()

    {

    }

    template <class TNO, class TScore, int num>

    TNO Student<TNO, TScore, num>::TopStudent()

    {

    return StudentID[0];

    }

    //类模板的具体案例

    #include <iostream>

    using namespace std;

    template<class T1,class T2>    //定义类模板, T1T2代表类成员的数据类型, 不是类的类型

    class MyTemplate

    {

        T1 t1;

        T2 t2;

        public:

            MyTemplate(T1 tt1,T2 tt2)    //构造函数

            {

                t1 =tt1;

                t2=tt2;        //构造函数的功能是对数据成员赋值

            }

            void display()

            { cout << t1 << ' ' << t2 << endl;}

    };

    void main()

    {

        int a=123;

        double b=3.1415;

        MyTemplate<int ,double> mt(a,b);

        mt.display();

    }

     

    //默认模板参数: 在类模板定义时, ||类型形式参数中的一个或若干类型形参||赋予||默认值, 该默认值是一个普通的数据类型. 这样, 在生成新类时, 如果被赋予默认值的类型形参没有赋实参, 那么这个类型形参的值就是默认值

    #include <iostream>

    using namespace std;

    template <class T1,class T2 = int> //int是模板形参的默认值

    class MyTemplate

    {

        T1 t1;

        T2 t2;

    public:

            MyTemplate(T1 tt1,T2 tt2)    //构造函数

            {t1=tt1;t2=tt2;}

            void display()

            {

             cout<< t1 << ' ' << t2 << endl;

        }

    };

    void main()

    {

        int a=123;

        double b=3.1415;

        MyTemplate<int ,double> mt1(a,b);

        MyTemplate<int> mt2(a,b); //第二个模板形参的没有被赋予实参, 因此该模板形参的值就是默认的int

        mt1.display();

        mt2.display();

    }

     

    //模板形参表中, 可以有一个非模板类型形参, 并且该形参已经被赋值:

    #include <iostream>

    using namespace std;

    template<class T1,class T2,int num= 10 >

    class MyTemplate

    {

        T1 t1;

        T2 t2;

        public:

            MyTemplate(T1 tt1,T2 tt2) //构造函数

            {t1 =tt1+num; t2=tt2+num;}

            void display()

            { cout << t1 << ' ' << t2 <<endl;}

    };

    void main()

    {

        int a=123;

        double b=3.1415;

        MyTemplate<int ,double> mt1(a,b);

        MyTemplate<int ,double ,100> mt2(a,b); //类模板生成模板类以后, 用模板类创建对象, 并赋值

        mt1.display();

        mt2.display();

    }

     

     

    ● 模板的特殊化/模板的定制(template specialization)

    使用场景: 定义完函数模板或类模板以后, 我们发现有的数据类型不适用于已经定义好的模板(如下面例子中的char* 类型不适用于Type函数模板); 或者我们需要运用一个新的自定义的类类型(如下面例子中类模板的T应该对应的是一个普通的数据类型, int; 但如果我们要T对应一个自定义的Date类类型, 就需要特殊化/定制模板)

    定义的目的: 补充模板, 从而扩展模板的功能

    //函数模板的定制

    #include <iostream >

    #include <string >

    using namespace std;

    template<class Type>

    Type min(Type a,Type b)//定义函数模板

    {

        if(a < b)

            return a;

        else

            return b;

    }

     

    template <> //定义一个针对字符串的补充模板, 即函数模板的定制

    char * min(char * a,char * b) // 上面的空模板形参表值允许在补充模板中使用, 可以把这个补充模板删去; 由于普通函数优先于模板函数, 因此能达到同样的效果, 但还是提倡用空的补充模板, 便于管理, 同时在没调用的情况下不会生产无用的目标代码.

    {

        if(strcmp(a,b)>=0)

            return b;

        else 

            return a;

    }

    void main ()

    {

        cout << "最小值:" << min(10,1) << endl;

        cout << "最小值:" << min('a','b') << endl;

        cout << "最小值:" << min("ah","ai") << endl;

    }

     

    //如果传送给上面这个模板的是指向字符串的字符指针, 即要求返回两个字符串中的较大者时, 模板是一句字符串的第一个字符来确定其大小的, 如果第一个字符相同, 则输出第一个字符串, 因而不能得出正确结果(就算正确结果本身就是第一个字符串, 那也不算真正的正确).

    //类模板的定制

    #include <iostream>

    using namespace std;

    class Date

    {

        int iMonth,iDay,iYear;

        char Format[128];

    public:

        Date(int m=0,int d=0,int y=0)

        {

            iMonth=m;

            iDay=d;

            iYear=y;

        }

        friend ostream& operator<<(ostream& os,const Date t)

        {

            cout << "Month: " << t.iMonth << ' ' ;

            cout << "Day: " << t.iDay<< ' ';

            cout << "Year: " << t.iYear<< ' ' ;

            return os;

            

        }

        void Display()

        {

            cout << "Month: " << iMonth;

            cout << "Day: " << iDay;

            cout << "Year: " << iYear;

            cout << endl;

        }

    };

     

    template <class T>

    class Set

    {

        T t;

        public:

            Set(T st) : t(st) {}

            void Display()

            {

                cout << t << endl;

            }

    };

    class Set<Date>

    {

        Date t;

    public:

        Set(Date st): t(st){}

        void Display()

        {

            cout << "Date :" << t << endl;

        }

    };

    void main()

    {

        Set<int> intset(123);

        Set<Date> dt =Date(1,2,3);

        intset.Display();

        dt.Display();

    }

     

     

    ● 类模板的派生与继承

    (略)

     

  • 相关阅读:
    php中的高危函数
    PHP 站点相对包含,路径的问题解决方法(include,require)
    PHP中::、-&gt;、self、$this操作符的区别
    C#常用类库(100多个)
    Android滑动菜单特效实现,仿人人客户端侧滑效果,史上最简单的侧滑实现
    转载:Android调用相册、拍照实现缩放、切割图片
    在浏览器上直接输入url 时,中文传参乱码问题
    一个asp采集程序
    c#微信开发 转
    如何使用JS来检测游览器是什么类型,或android是什么版本号- 转载
  • 原文地址:https://www.cnblogs.com/ArrozZhu/p/8378035.html
Copyright © 2020-2023  润新知