C++面向对象(六):模板

C++面向对象:模板

会有点长,不过读过就全学会喽!!!!!!
会有点长,不过读过就全学会喽!!!!!!
会有点长,不过读过就全学会喽!!!!!!

1 . 模板的概念

由于 C + + 是强类型语言,因此我们定义求最大值函数 max( )时, 需要对不同的数据类型分别定义不同的版本,例如:

int max (int x, int y)
{
    return ( x > y) ? x∶y;
}
float max(float x, float y)
{
    return ( x > y) ? x∶y;
}
double max( double x, double y)
{
    return ( x > y) ? x∶y;
}

这些函数版本执行的功能都是相同的, 只是参数类型和函数返回类型不同。能否为上述这些函数只写出一套代码呢 ? 解决这个问题的一种方法是使用宏定义,如:

# define max( x, y) ( ( x > y ) ? x∶y)

但是,由于宏定义避开了 C + + 的类型检查机制, 在某些情况下, 将会导致两个不同类型参数之间的比较。例如将一个整数和一个结构进行比较,显然将导致错误。
宏定义带来的另一个问题是,可能在不该替换的地方进行了替换, 如:

class exa
{
public :
    int max (int, int);  // 此处宏扩展将导致语法错误
// …
}

事实上,由于宏定义会造成不少麻烦, 所以在 C + + 中不主张使用宏定义。
解决以上问题的另一个方法就是使用模板。模板是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数, 从而实现了真正的代码可重用性。模板分为函数模板和类模板,它们分别允许用户构造模板函数和模板类。

2 函数模板与模板函数

2 .1 函数模板的声明与模板函数的生成

函数模板的声明格式如下:

template < class type >
返回类型 函数名(参数表)
{
函数体
}

其中 template 是一个声明模板的关键字, 它表示声明一个模板。关键字 class 表明后面的type 是模板形参,在使用函数模板时, 必须将其实例化,即用实际的数据类型替代它。
例如,将求最大值函数 max( )定义成函数模板,如下所示:

template < class T >
T max( T x, T y)
{
return ( x > y) ? x∶y;
}

其中 T 为模板形参, 它既可取系统预定义的数据类型, 又可以取用户自定义的类型。这样定义的 max( )函数代表的是一类函数。若要用这个 max ( ) 函数进行最大值操作, 必须首先将模板形参 T 实例化为确定的数据类型 (如 int 等) ,从这个意义上说,它不是一个完全的函数,我们称之为函数模板。将 T 实例化的参数称为模板实参, 用模板实参实例化的函数称为模板函数。
下面是使用上面定义的函数模板的完整程序。

#include<iostream>
#include<string.h>
using namespace std;
template < class AT >
AT mymax (AT x, AT y)
{
    return ( x > y) ? x: y;
}
int main ( )
{
    int i1 = 10, i2 = 56;
    float f1 = 12.5, f2 = 24.5 ;
    double d1 = 50.344, d2 = 4656.346;
    char c1 ='k', c2 ='n';
    cout << "the max of i1, i2 is: " << mymax(i1,i2 ) << endl;
    cout << "the max of f1, f2 is: " << mymax(f1,f2 ) << endl;
    cout << "the max of d1, d2 is: " << mymax(d1,d2 ) << endl;
    cout << "the max of c1, c2 is: " << mymax(c1,c2 ) << endl;
    return 0 ;
}

程序运行结果如下:
the max of i1, i2 is : 56
the max of f1, f2 is : 24 .5
the max of d1 , d2 is : 4656 .346
the max of c1, c2 is : n
在此程序中生成了四个模板函数: max ( i1, i2 )、max ( f1 , f2 )、max ( d1, d2 ) 、max ( c1,c2) 。max (i1, i2) 用模板实参 int 将类型参数 AT 进行了实例化; max( f1, f2 ) 用模板实参float 将类型参数 AT 进行了实例化; max( d1 , d2 )用模板实参 double 将类型参数 AT 进行了实例化; max( c1, c2 )用模板实参 char 将类型参数 AT 进行了实例化。
从以上例子我们可以看出,函数模板提供了一类函数的抽象, 它以任意类型 AT 为参数及函数返回值。函数模板经实例化而生成的具体函数称为模板函数。函数模板代表了一类函数,模板函数表示某一具体的函数。
函数模板实现了函数参数的通用性,作为一种代码的重用机制, 可以大幅度地提高程序设计的效率。下面再介绍一个与指针有关的例子。

#include<iostream>
#include<string.h>
using namespace std;
template < class T >
T sum( T * array, int size = 0)
{
    T total = 0;
    for (int i = 0; i< size; i ++ ) total += array[i] ;
    return total;
};
int intarray[ ] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} ;
double doublearray[ ] = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10} ;
int main( )
{
    int itotal = sum( intarray, 10 ) ;
    double dtotal = sum ( doublearray, 10 ) ;
    cout <<"The summary of integer array are:" << itotal << endl;
    cout <<"The summary of double array are:"<< dtotal << endl;
    return 0;
}

程序运行结果为:
The summary of integer array are: 55
The summary of double array are: 59 .6
在该程序中, 生成了两个模板函数。其中 sum ( intarray, 10 ) 用模板实参 intarray将类型参数 T 进行了实例化, intarray 为一整型数组名,是一个指向 int 类型的指针;sum( doublearray, 10)用模板实参 doublearray 将 T 进行了实例化, doublearray 为一双精度型数组名,是一个指向 double 类型的指针。
说明:

  1. 在函数模板中允许使用多个类型参数。但是应当注意 template 定义部分的每个模板形参前必须有关键字 class。例如, 下面这个程序中建立了有两个模板形参的函数模板。
#include<iostream>
using namespace std;
template < class type1, class type2 >
void myfunc (type1 x, type2 y)
{
    cout << x <<" "<< y << endl;
}
int main ()
{
    myfunc( 10,"hao") ;
    myfunc( 0.123, 10L) ;
    return 0;
}

程序运行结果为:
10 hao
0 .123 10
在此程序中, 生成了两个模板函数, 其中 myfunc ( 10,”hao”) 分别用模板实参 int 和char * 将模板形参 type1 和 type2 进行了实例化。 myfunc ( 0 .123, 10L ) 分别用模板实参float 和 long 将模板形参 type1 和 type2 进行了实例化。

  1. 在 template 语句与函数模板定义语句之间不允许有别的语句, 例如下面的程序段就不能编译。
// This will not compile .
template < class T >
int i; // 错误,不允许有别的语句
T max( T x, T y )
{ return ( x > y) ? x∶y; }
  1. 模板函数类似于重载函数, 只不过它更严格一些而已。函数被重载的时候,在每个函数体内可以执行不同的动作,但同一函数模板实例化后的所有模板函数都必须执行相同的动作。例如,下面的重载函数就不能用模板函数代替, 因为它们所执行的动作是不同的。
void outdate (int i)
{ cout << i; }
void outdata( double d )
{ cout << ”d =<< d << endl; }
2 .2 函数模板的异常处理

虽然函数模板中的模板形参 T 可以实例化为各种类型,但实例化 T 的各模板实参之间必须保持完全一致的类型,否则会发生错误。

template < class T >
T max( T x, T y)
{ return ( x > y) ? x∶y; }
void fun( int i, char c )
{
max(i, i); // 正确, 调用 max (int , int) 
max(c, c); // 正确, 调用 max ( char, char)
max(i, c); // 错误
max(c, i); // 错误
}

这里出现错误的原因是,函数模板中的类型参数只有到该函数真正被调用时才能决定。在调用时,编译器将按最先遇到的实参的类型隐含地生成一个模板函数, 用它对所有模板进行一致性检查。如对语句

max(i, c ) ;

编译器将先按变量 i 将 T 解释为 int 类型, 此后出现的模板实参 c 不能解释为 int 类型时,便发生错误,在此没有隐含的类型转换的功能。解决这个问题有以下两种方法:

  1. 采用强制类型转换, 如将调用语句max(i, c ) ;改写成max(i, int( c) ) ;
  2. 用非模板函数重载函数模板, 这种重载有两种表述方式:
    ① **只声明一个非模板函数的原型 用此方式定义非模板函数重载模板函数时,只声明非模板函数的原型,而不给出函数体, 它的函数体是借用函数模板的函数体。当执行此重载版本时会自动调用函数模板的函数体。**例如可将上面的程序改写如下:
template < class T >
T max( T x, T y)
{ return ( x > y) ? x∶y; }
int max (int , int) ;
void fun( int i, char c)
{
max(i, i) ; // 正确, 调用 max (int , int)
max(c, c) ; // 正确, 调用 max ( char, char)
max(i, c) ; // 正确, 调用 max (int , int)
max(c, i) ; // 正确, 调用 max (int , int)
}

虽然非模板重载函数借用了函数模板的函数体, 但它支持数据间的隐式转换, 使得max(int , char )、max( char, int)等变成为合理和正确的调用。因此当程序中出现调用语句max(i, c )和 max( c, i)时, 执行的是重载的非模板函数 max( int , int)。

② 定义一个完整的非模板函数 此方式定义的重载函数, 所带参数的类型可以随意,就像一般的重载函数一样定义。例如:

char * max( char * x, char * y)
{ return (strcmp( x, y) > 0 ) ? x∶y; }

此模板函数 max ( char * x, char * y)重载了上述的函数模板,当出现调用语句**max(”abcd”,”efgh”) ;**时,执行的是这个重载的非模板函数。
在 C + + 中,函数模板与同名的非模板函数重载时, 调用的顺序遵循下述约定:

  1. 寻找一个参数完全匹配的函数, 如果找到了就调用它。
  2. 寻 找一个函数模板, 将其实例 化, 产 生一个匹配的模板函 数, 若 找到了, 就调用它。
  3. 若( 1) 和(2 )都失败, 再试一试低一级的对函数的重载方法, 例如通过类型转换可产生参数匹配等,若找到了, 就调用它。

若(1 )、(2 )、(3 )均未找到匹配的函数, 则是一个错误的调用。
如果在第(1 )步有多于一个的选择, 那么这个调用是意义不明确的,是一个错误调用。

3 类模板与模板类

一个类模板(也称为类属类或类生成类 )允许用户为类定义一种模式, 使得类中的某些数据成员、某些成员函数的参数或返回值, 能取任意数据类型。
定义一个类模板与定义函数模板的格式类似,必须以关键字 template 开始,后面是尖括号括起来的模板参数,然后是类名, 其格式如下:

template < class Type >
class 类名 {
// . . .
} ;

其中 template 是一个声明模板的关键字, 它表示声明一个模板。关键字 class 表明后面的Type 是模板参数。在类定义中,欲采用通用数据类型的数据成员、成员函数的参数或返回值,前面需加上 Type。例如, 下面的程序中建立了一个用来实现堆栈的类模板。

const int size = 10;
template < class Type >
class stack
{
    Type stck[size] ;
    int tos;
public :
    void init( )
    {
        tos = 0;
    }
    void push ( Type ch ) ;
    Type pop() ;
};

在类定义体外定义成员函数时,若此成员函数中有模板参数存在, 则需要在函数体外进行模板声明, 并且在函数名前的类名后缀上“ < Type > ”。例如, 成员 函数 push ( ) 和pop( ) 在类定义体外定义为:

template < class Type >
void stack < Type > ::push( Type ob)
{
    if ( tos = = size )
    {
        cout << "stack is full";
        return ;
    }
    stck[ tos] = ob;
    tos ++ ;
}
template < class Type >
Type stack < Type > ::pop( )
{
    if (tos = = 0)
    {
        cout << "stack is empty";
        return 0 ;
    }
    tos -- ;
    return stck[ tos] ;
}

类模板不代表一个具体的、实际的类, 而代表着一类类。实际上, 类模板的使用就是将类模板实例化成一个具体的类,它的格式为:

类名 < 实际的类型 > 对象名;

例如,使用上面的类模板, 创建两个模板参数为 char 型的对象, 语句如下:

stack < char > s1 ,s2;

下面是使用类模板 stack 的完整例子, 在此例子中建立了字符型和整型两个堆栈。

#include<iostream>
using namespace std;
const int size = 10;
template < class Type > // 声明一个类模板 (
class stack  // 定义类模板
{
    Type stck[size] ; // 数组可取任意类型,即模板参数类型 Type
    int tos;
public :
    void init()
    {
        tos = 0;
    }
    void push ( Type ch ) ; // 参数取 Type 类型
    Type pop( ) ; // 返回类型取 Type 类型
};
template < class Type >
void stack < Type > ::push( Type ob)
{
    if ( tos == size )
    {
        cout << "stack is full";
        return ;
    }
    stck [tos] = ob;
    tos ++ ;
}
template < class Type >
Type stack < Type > ::pop( )
{
    if ( tos == 0 )
    {
        cout << "stack is empty";
        return 0;
    }
    tos -- ;
    return stck[ tos] ;
}
int main ()
{
    // 定义字符堆栈
    stack < char > s1,s2 ;
    // 创建两个模板参数为 char 型的对象
    int i;
    s1.init();
    s2.init();
    s1.push('a');
    s2.push('x');
    s1.push('b');
    s2.push('y');
    s1.push('c');
    s2.push('z');
    for(i = 0; i < 3 ; i ++ )
        cout << "pop s1: "<< s1.pop( ) << endl;
    for(i = 0; i < 3 ; i ++ )
        cout << "pop s2:"<< s2.pop( ) << endl;
    // 定义整型堆栈
    stack < int > is1, is2;
    // 创建两个模板参数为 int 型的对象
    is1.init();
    is2.init();
    is1.push(1);
    is2.push(2);
    is1.push(3);
    is2.push(4);
    is1.push(5);
    is2.push(6);
    for (i = 0; i < 3 ; i ++ )
        cout << "pop is1:"<< is1 .pop( ) << endl;
    for (i = 0; i < 3 ; i ++ )
        cout << "pop is2:"<< is2 .pop( ) << endl;
    return 0 ;
}

程序运行结果如下:
pop s1 : c
pop s1 : b
pop s1 : a
pop s2 : z
pop s2 : y
pop s2 : x
pop is1: 5
pop is1: 3
pop is1: 1
pop is2: 6
pop is2: 4
pop is2: 2
此例用 stack < char > 创建了两个模板为 char 型的对象 s1 和 s2 ,用 stack < int > 创建了两个模板类型为 int 型的对象 is1 和 is2。在 main( ) 函数中我们还可以定义其它类型的类对象,如:

stack < double > ds1 , ds2 ;

这些实例化的类, 如 stack < char > 、stack < int > 、stack < double > 等, 被称为模板类。模板类是类模板对某一特定类型的实例。类模板代表了一类类, 模板类表示某一具体的类。
再看一个程序,这个程序中建立了一个简单的单向链表类模板, 然后通过建立一个保存字符的链表演示该类。

#include<iostream>
using namespace std;
template < class datat>
class lists
{
    datat data;
    lists * next ;
public :
    lists( datat d ) ;
    void add(lists * node )
    {
        node -> next = this;
        next = 0 ;
    }
    lists * getnext( )
    {
        return next ;
    }
    datat getdata()
    {
        return data;
    }
} ;
template < class datat >
lists < datat > ::lists( datat d )
{
    data = d;
    next = 0;
}
int main ()
{
    lists < char > start('a') ;
    lists < char > * p, * last;
    int i;
    // build a list
    last = &start;
    for (i = 1; i < 26; i ++ )
    {
        p = new lists < char > ('a'+ i) ;
        p -> add(last) ;
        last = p;
    }
    cout << endl;
    // follow the list
    p = &start;
    while (p)
    {
        cout << p -> getdata() ;
        p = p -> getnext( ) ;
    }
    return 0;
};

程序运行结果如下:
abcdefghujklmnopqrstuvwxyz
当执行这个程序时,它建立了包含字母表字符的链表, 并显示它们。由于使用了类模板,我们在建立 list 的对象时,只要改变模板参数的类型, 就可以改变链表所保存的数据类型。例如可以用下面的声明建立保存整数的对象:

list < int > int - start( 1) ;

说明:

  1. 在每个类模板定义之前, 都需要在前面加上模板声明,例中都要加上:template < class Type >类模板在使用时,必须在名字后面缀上模板参数 < Type > ,例如stack < Type >
  2. 模板类可以有多个模板参数, 在下面的短例中建立了使用两个模板参数的类模板。
#include<iostream>
using namespace std;
template < class T1, class T2 > // 声明具有两个参数的模板
class myclass
{
    // 定义类模板
    T1 i;
    T2 j;
public :
    myclass( T1 a, T2 b)
    {
        i = a ;
        j = b;
    }
    void show( )
    {
        cout << "i = "<< i <<"j = "<< j<< endl;
    }
};
int main ( )
{
    myclass < int, double > ob1( 12,0.15) ;
    myclass < char, char * > ob2('x',"This is a test") ;
    ob1.show( ) ;
    ob2.show( ) ;
    return 0 ;
}

程序运行结果如下:
i = 12 j = 0 .15
i = x j = This is a test
这个程序声明了一个类模板,它具有两个模板参数。在 main ( ) 函数中定义了两种类型的对象,ob1 使用了 int 型与 double 型数据, ob2 使用了 char 型和 char * 型数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值