十九. 模板

● 模板的基本概念

模板(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();

}

 

 

● 类模板的派生与继承

(略)

 

 posted on 2018-01-29 16:31  Arroz  阅读(457)  评论(0编辑  收藏  举报