模板

 1 样板函数
    样板函数定义了适用于不同数据类型操作的通用集。样板函数操作的数据类型是当作
参数传递给它的。通过这种机制,同样的通用过程即可适用于大范围的数据。读者也许知道,
很多算法在逻辑上是相同的,与所使用的数据类型无关。例如,快速排序算法对整型数组和
浮点数组的操作是一样的,只是被排序的数据类型不一样。通过创建样板函数,可以定义独
立于任何数据的算法属性。这样,在执行函数时,编译程序就对实际使用的数据类型自动产
生正确的代码。从根本上讲,创建一个样板函数也就是创建了一个可自动重载自身的函数。
    样板函数由关键字template创建。在C++中,"template(模板)”的普通含义正反映了它
的用途。使用它创建一个描述函数功能的模板(或框架),然后让编译程序填写所需要的细
节。下面是template函数定义的一般形式:
    template<class Ttype> ret_type func_name(parameter list);
        {
        //body of function
      }
      Ttype是函数使用的数据类型的位置保持器名称,该名称可能在函数定义中用到。但
是,它仅是一个位置保持器,编译程序在创建一个函数的特定版本时会自动将它替换为实际
的数据类型。
      下面的短程序创建了一个样板函数,用来交换调用的两个变量的值。由于交换两个值的
一般过程与变量类型无关,因此这样创建样板函数是一个好的选择。
//Function template example.
#include<iostream.h>
//This is a function template.
template<class X> void swap(x&a,x&b)

    x temp;
    temp=a;
    a=b;
333页
    b=temp;

main()
    {
    int i=10,j=20;
    float x=10.1,Y=23.3;
    char a=’x’,b=’z’;
    cout <<"Original i,j:"<<i<<’ ’<<j<<endl;
    cout <<"Original x,y:"<<x<<’ ’ <<y<<endl;
    cout <<"Original a,b:"<<a<<’ ’ <<b<<endl;
    swap(i,j);// swap integers
    swap(x,y);// swap floats
    swap(a,b);// swap chars
    cout<<"Swapped i,j:"<<i<<’ ’<<j<<endl;
    cout<<"Swapped x,y"<<x<<’ ’<<y<<endl;
    cout<<"swapped a,b"<a<<’ ’<<b<<endl;
    return 0;
}
让我们仔细研究一下这个程序。
template <class X> void swap(X &a,x&b)
告诉编译程序两件事:创建模板,样板定义开始。这里,X被当作位置保持器的样板类型。在
template后,定义了swap(),用X作为被交换值的数据类型。在main()函数中,用三种数据
类型调用swap()函数:整型、浮点型和字符型。由于swap()是样板函数,故编译程序自动创
建swap()的三种版本——一个用于交换整型值,一个用于交换浮点值,一个用于交换字符。
      在讨论模板时,有时在其它一些C++文献中可能遇到下面一些术语。首先,样板函数
(由template语句引导的函数定义)也被称为模板函数(template function)。当编译程序创建
这个函数特定的版本时,也就是创建了生成函数(generate function)。生成函数的行为被称
为实例化(instantiating),换句话说,生成函数是模块函数的具体实例。
      从技术上讲,在样板函数定义中,template部分不必与函数名在同一行。例如,下面也是
说明swap()函数的常用方法:
template<class X>
void swap(x&a,x&b)

    x temp;
    temp=a;
    a=b
    b=temp;
}
采用这种形式时有一点要注意,即在template语句和样板函数定义之前不能有其它语句。例
如,下面程序段的编译不能通过:
// This will not compile.
template <class X>
334页
    int i;/this is an error
    void swap(x&a,x&b)
    {
      x temp;
      temp=a;
      a=b;
      b=temp;
    }
正如注释所指示的,template说明必须直接放在函数定义之前。
20.1.1 带两个样板类型的函数
      在template语句中,可以使用逗号分隔列表定义多个样板数据类型。例如,下面的程序
创建了带两个样板类型的样板函数:
    #include <iostream.h>
template <class type1,class type2>
void myfunc(type1 x,type2 y)
    {
      cout<<x<<’ ’<<y<<endl;
    }
main()
    {
      myfunc(10,"hi");
    myfunc(0.23, 10L);
    return 0;
}
    在上面的例子中,当编译程序生成main()中myfunc()的具体实例时,位置保持器类型
type1和type2分别由数据类型int、char*、double和long代替。
    记住:在创建一个样板函数时,实际上允许编译程序针对用户程序所调用函数的不同形
式生成很多不同版本。
20.1.2 显式重裁样板函数
    虽然模板函数在需要时可重载其自身,但用户也可以显式地对其进行重载。如果重载一
个样板函数,那么重载的函数越过(隐去)与具体版本相关的样板函数。考虑第一个示例的如
下版本:
//Overriding a template function.
#include <iostream.h>
template <class X> void swap(x&a,x&b)
    {
    x temp;
      temp=a;
      a=b;
    b=temp;

335页
    // This overides the generic version of swap().
    void swap(int&a,int&b)
    {
        int temp;
        temp=a;
        a=b;
      b=temp;
      cout<<"Inside overloaded swap(int&, int&).\n";
    }
    main()
      {
        int i=10,j=20;
      float x=10.1,y=23.3;
      char a=’x’,b=’z’;
      cout <<"Original i,j:"<<i<<’ ’ <<j<<endl;
      cout <<"Original x,Y:"<<x<<’ ’ <<y<<endl;
      cout <<"Original a,b:"<<a<<’ ’ <<b<<endl;
      swap(i,j);// this calls the explicitly overloaded swap()
      swap(x,y);// swap floats
      swap(a,b);// swap chars
      cout <<"Swapped i,j:"<<i<<’ ’ <<j<< endl;
      cout <<"Swapped x,y:"<<x<<’ ’ <<y <<endl;
      cout <<"Swapped a,b:"<<a<<’ ’ <<b <<endl;
      return 0;
    }
    正如注释所指出的,当调用swap(i,j)时,它调用了程序中定义的swap()显式重载版
本。因此,编译程序不能生成样板swap()函数这一版本,因为样板函数被显式重载所覆盖。
    如例中所示,模板的人工重载允许用户特别定义一个样板函数以适应特殊情况。但一般
情况下,如果用户需要对应不同数据类型函数的不同版本,应该使用重载函数而不是模板。
20.1.3 样板函数的限制
    样板函数与重载函数相似,但具有更多的限制。当函数重载时,每个函数体可能执行不
同的操作,而样板函数对于所有版本必须执行同样的操作,只是数据类型不同而已。例如,在
下面的程序中,重载函数不能用样板函数代替,因为它们做的不是同样的事情:
    #include <iostream.h>
    #include <math.h>
void myfunc(int i)
    {
    cout<<"value is:"<<i<<"/n";

void myfunc(double d)

    double intpart;
    double fracpart;
    fracpart=modf(d, &intpart);
336页
    cout<<FractiOnal part :"<<frcpart;
    cout <<"/n";
    cout <<" Integet part :" <<intpart;

main()

    myfunc(1);
    myfunc(12.2);
    return 0;
}
    这里是对模板函数的另外一些限制。虚函数不能是模板函数,析构函数也不能是模板函
数,模板函数必须使用C++语言连接程序(即,它不能使用连接说明。参见第二十二章)。
20.2 样板函数的应用
    样板函数是C++语言中最有用的特性之一。它们能够适用于任何类型的情况。如前所
述,只要是定义通用算法的函数都可以定义为模板函数。一旦这样做,就可以将它应用于任
意类型数据而不必记忆。在介绍样板类之前,先给出两个样板函数调用的例子。它们阐明了
利用C++这一强大特性的方便性。
20.2.1 样板排序
    排序正是样板函数设计用于的一种类型的操作。在很大程度上,排序算法与需排序的数
据类型无关。下面的程序创建了一个样板冒泡排序程序以说明这一点。虽然冒泡排序是最
一种简单愚笨的排序算法,但操作清晰明了,可以编写出简单易懂的程序(用户可以试着对
自己感兴趣的排序算法创建一个样板函数)。bubble()函数可对任意数组类型排序,它通过
数组第一个元素的指针及数组元素个数被调用:
//A Generic bubble sort.
#include<iostream.h>
template<class x>void bubble(
    X*item,// pointer to array to be sorted
    int count)// number of items in array
{
    register int a,b;
    x t;
    for(a=1; a<count; a++)
      for(b=count-1;b>=a;b--)
        if(items[b,1]>items[b]{
          // exchange elements
          t=items[b-1];
          items[b-1]=items[b-1];
          items[b]=t;
        }

main()
337页

    int iarray[7]={7,5,3,3,9,8,6};
    double darray[5]={3.3,2.5,-0.9, 100.2,3.0};
      int i;
    cout<<"Here is unsorted integer array:";
    for(i=0;i<7;i++)
      cout<<darray[i]<<’ ’;
    cout<<endl;
    cout <<"Here is unsorted double array:";
    for(i=0; i<5;i++)
      cout<<darray[i]<<’ ’;
    cout <<endl;
    bubble(iarray,7);
    bubble(daray,5);
    cout<<"Here is sOrted integer array:";
    for(i=0; i<7; i++)
      cout <<iarray[i]<<";
    cout<<endl;
    cout<<"Here is sorted double array:";
    for(i=0;i<5;i++)
      cout<<darray[i]<<’ ’;
    cout <<endl;
    return 0;
}
      程序产生的结果如下:
Here is unsorted integerarray:7 5 3 3 9 8 7 6
Here is unsorted double array:3.3 2.5 -0.9 100.23
Here is sorted integer array:3 3 5 6 7 8 9
Here is sorted double array:-0.9 2.5 33.3 100.2
    可以看到,上面的程序创建了两个数组:一个整型和一个双精度型,它们各自排序。因为
buble()是模板函数,它将被自动重载以适应两种不同类型数据的排序。
20.2.2 紧缩数组
    另一个得益于模板函数的函数是compact(),该函数对数组的元素进行压缩。从编程经
验可以知道,从一个数组中间移走元素,再排列剩下的元素,将无用的元素放在后面,这种需
求是常见的。这种操作对所有类型的数组是一样的,因为它不依赖于实际的数据类型。下面
程序中的compact()样板函数由指向数组第一个元素的指针、数组元素的个数和移动元素
的起点和终点的下标调用。函数移动这些元素,然后紧缩数组。为说明问题,将紧缩后的数
组末尾无用的元素置为0。
//A Genric array conipaction function.
#include<iostream.h>
template<class x>void compact(
    x*item,//pointer to array to be compacted
    int count,// number of items in array
    int start,// starting index of compacted region
338页
    int end)// ending index of compacted region

      register int i;
      for(j=end+1;i<count; i++,start++)
      items[start]=items[i];
      /*For the sake illustration,the remainder of
        the array will be zeroed.*/
    for(;start<count; start++) items[start]=(X)0;
main()
    {
    int nums[7]={0,1,2,3,3,5,6};
    char str[18]="Generic Functions";
        int i;
    cout <<"Here is uncompacted integer array:";
    for(i=0; i<7; i++)
      cout <<nums[i]<<’ ’;
    cout <<endl;
    cout <<"Here is uncompacted string:";
    for(i=0; i<18; i++)
      cout <<str[i];<<’ ’;
    cout<<endl;
    compact(nums,7,2,3);
    compact(str,18,6,10);
    cout <<"Here is compacted integer array:";
    for(i=0;i<7;i++)
      cout<<nums[i]<<’ ’;
    cout <<endl;
    cout <<"Here is compacted string:";
    for(i=0;i<18;i++)
      cout <<str[i]<<’ ’;
    cout<<endl;
      return 0;
    }
    这个程序紧缩两种类型的数组。一种是整型数组,另一种是字符串。当然,任意数组类
型均可以使用compact()函数。程序输出如下:
Here is uncompacted integer array:0 1 2 3 3 5 6
Here is uncompacted string:Generic F u n c t i o n s
Here is compacted integer array:0 1 3 5 6 0 0 0
Here iscompacted string:G i n e r i c t i o n s
    如上述例子所述,一旦用户开始考虑到模板的概念,许多用途就自然地会想到它。只要
函数的内在逻辑与数据类型无关,就可以创建样板函数。
20.3 样板类
    除了样板函数之外,还可以定义样板类。用户可以创建一个类,它说明该类使用的所有
339页
算法,但实际被操纵的数据类型要在创建类的对象时作为参数指定。
      当一个类包含通用逻辑时,样板类定义是有用的。例如,维护一个整数队列的算法同样
适用于维护字符队列。另外,维护邮政地址链表的机制也可用于维护自动文件信,息链表。通
过使用样板类,可以定义维护队列、链表等的操作,适应任意数据类型。编译程序根据对象创
建时指定的数据类型自动产生正确的对象类型。
      样板类说明的一般模式如下所示:
          template<class Ttype> class class_name{
           .
           .
           .
            }
Ttype为位置保持器类型名,它在类实例化时指定。必要时,可以使用逗号分割列表以定义
多个样板数据类型。
      建立样板类之后,可用以下形式创建样板类的具体实例:
          class_name<type> ob;
其中,type是要操作的数据类型名。样板类的成员函数可自动署名。
      在下面的程序中,stack类(首次在第十一章作了介绍)定义为样板类,可以为任何类型
的对象提供堆栈。下面的例子创建字符栈、整型栈和浮点数栈。
//Demonstarte a genric stack class.
    #include<iostream.h>
    const int SIZE=100;
    //This creates the generic class stack;
template <class SType> class stack{
    SType stack[SIZE];
        int tos;
    public:
    stack();
      ~stack();
      void push(SType i);
      SType pop();
    };
    //stack’s constructor function
    template<class SType> stack<SType>::stack()
    {
      tos=0;
      cout<<"Stack initialized\n";
    }
    /*stack’s destructor function
      This function is nOt required, It is included
      for illustration only.*/
    template <class SType> stack<SType>::stack()
    {
      cout<<"Stack Destroyd/n";
    }
340页
// Path an object onto the stack.
template <class SType> void stack<SType> :: push(SType i)
{
    if(tos==SIZE){
      cout <<"Stack is full";
          return;
      }
    stck[tos]=i;
    tos++;
    }
//Pop an object off the stack.
template <class SType> SType stack<SType>:: pop ()
{
    if(tos==0){
        cout <<"Stack is underflow.";
        return 0;
      }
      tos--;
    return stck[tos];

main()

    stack<int> a;// create integer stack
    stack <double> b;// create a double stack
    stack<char> c;// create a character stack
      int i;
    // use the integer and double stacks
    a.push(1);
    b.push(99.3);
    a.push(2);
    b.push(-12.23);
    cout<<a.pop()<<" ";
    cout<<a.pop()<<" ";
    cout<<b.pop()<<" ";
    cout<<b.pop()<<"/n";
    // demonstrate the character stack
    for(i=0;i<10; i++)C.push(char)’A’ +i);
    for(i=0; i<10; i++)cout <<c.pop();
    cout <<"/n";
      return 0;
}
    从中可以看出,样板类定义与样板函数类似。样板数据类型被用于类说明及其成员函数
中。直到栈的对象说明之后,实际的数据类型才确定。当stack中具体实例说明后,编译程序
自动产生所有必需的函数和变量以处理实际数据。在这个例子中,定义了三个栈类型(一个
是整型,另一个是双精度数,第三个是字符型)。请特别注意下面的说明:
stack<int> a; // create integer stack
stack<double> b;// create a double stack
stack<char>c;// create a character stack
341页
    注意所需的数据类型是如何在三角括号内部传递的。通过在创建stack对象时改变具
体的数据类型,用户可以改变由栈存储的数据类型。例如,可以用下面的说明方法创建另一
个存储字符指针的栈
stack <char * >chrptrstck;
    也可以创建存储自己创建数据类型的栈。比如,要存储地址信息,可用下面的结构:
struct addr {
char name[30];
char street[30];
char city[30];
char state[3];
char zip[12];
}
然后,利用下面的说明方法,用stack生成一个栈,可存储类型addr的对象:
stack <addr> obj;
    如stack类所示,样板函数和样板类提供了一个强有力的工具,使我们最大限度地提高
程序设计的效能,因为它们允许定义适合不同类型数据对象的一般形式。这样就不必为了让
类适用于每种数据类型而建立各种各样的实现办法,给用户节省了不少时间。编译程序自动
对不同的类创建特定的版本。
20.3.1 带两个样板数据类型的例子
    模板类可以带多个样板数据类型。在template说明中,简单地用逗号分隔列表说明类所
需的所有数据类型。例如,下面的例子中创建了带两个样板数据类型的类。
/* This example uses two generic data types in a class definition.*/
#include<iostream.h>
template <class Type1,class Type2> class myclass

    Type1 i;
    Type2 j;
public:
    myclass(Type1 a,Type2 b){i=a;j=b;}
    void show(){cout<<i<<’ ’<<j<<’/n’;}
};
main()

    myclass<int, double> ob1(10,0.23);
    myclass<char, char *> ob2(’x’,"This is a test”);
    ob1.show();// show int, double
    ob2.show();//show char,char *
    return 0;
}
      程序输出结果如下:
10 0.23
342页
x This is a test
    该程序定义两个对象类型,ob1用整型和双精度数据,ob2用字符和字符指针。两种情况
下,编译程序自动产生相应的数据和函数以适应创建对象的需要。
20.4 创建样板数组类
    下面让我们来看一个样板类的常见应用。从第十四章我们知道,可重载[]运算符。这样
可以创建自己的数组实现机制,允许创建“安全的数组”,它提供了运行时边界检查。我们知
道,在C++语言中,在运行时可能越过一个数组边界但不产生运行时间错误信息。但如果创
建一个包括数组的类,且只允许通过重载[]下标运算符对数组进行存取,就可以截取越界下
标。
    将运算符重载和样板类结合起来,就可以创建一个样板安全数组类型,用于建立任何数
据类型的安全数组。下面的程序阐明了这一点:
// A generic safe array example.
#include<iostream.h>
#include"stdio.h"
const int SIZE=10;
template <class AType> class atype{
AType a[SIZE];
public:
AType(){
      register int i;
    for(i=0;i<SIZE;i++)a[i]=i;

AType&operator[](int i);
};
// Provide range checking for atype.
template <class AType> AType&atype<AType> ::operator[](int i)

if(i<0||i>SIZE-1){
    cout <<"/nIndex value of";
    cout <<i<<" is out-of-bounds./n";
    exit(1);

return a[i];

main()

atype<int> intob;// integer array
atype<double> doubleob;// double array
    int i;
cout <<"Integer array:";
for(i=0;i<SIZE;i++) intob[i]=i;
for(i=0; i<SIZE; i++)cout <<intob[i]<<" ";
cout <<’/n’;
343页
    cout<<"Double array:";
    cout.precision(2);
    for(i=0; i<SIZE; i++) doubleob[i]=(double)i/3;
    for(i=0; i<SIZE;i++) cout<<doubleob[i]<<" ";
    cout<<’/n’;
    intob[12]=100; // genrates runtime error
      return 0;
    }
    这个程序实现了一个样板安全数组类型,然后通过创建一个整型数组和一个双精度数
组说明了它的用途(用户也可能需要创建其它一些数组类型)。如同示例所示,样板类的一部
分功能是它们使得用户只需一次性编写代码,然后,用于任意类型的数据而不必对每个应用
重新编码。
    为简单起见,上面的程序用了固定大小的数据,但也可以改变atype类,以说明可变维
数的数组。为此,将数组维数说明为atype构造函数的参数,动态分配这个数组。
    注:类模板的另一个示例见第二十五章,它使用模板类创建一个样板双向链表,可以存
储任意类型的对象。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值