第3章 模 板 使用模板可以建立具有通用类型的函数库或类库,为一系列逻辑功能相同而数据类型 不同的函数或类创建框架。模板提供了一种重用程序源代码的有效方法,使大规模软件开 发更加方便。 3.1 模板的概念 模板的本质就是将所处理的数据类型说明为参数,是对具有相同特性的函数或类的再 抽象。它将程序处理的数据类型参数化,可使一段程序代码处理多种不同类型的数据。 C++程序由类和函数组成,类对应类模板,函数对应函数模板。 为了更好地理解,先来考察3个Swap()函数,是如何交换两个整型数、两个单精度实型 数以及两个双精度实型数的。这3个Swap()函数的功能完全一样,只是所处理的数据类型 不同,下面是这3个函数的具体实现: void Swap(int &x,int &y) //交换整型数x、y { int temp=x; x=y; y=temp; //通过循环赋值交换x、y } void Swap(float &x,float &y) //交换单精度实型数x、y { float temp=x; x=y; y=temp; //通过循环赋值交换x、y } void Swap(double &x,double &y) //交换双精度实型数x、y { double temp=x; x=y; y=temp; //通过循环赋值交换x、y } 上面通过函数重载虽然能让Swap()函数处理多种类型的数据,但不是任意的,只能交 换一对相同类型的数据。最好的解决方法是将类型参数化后得到函数模板。使用函数模板 定义如下: template //此处的class 不是定义类的标识,只表明Type 是一个类型参数 void Swap(Type &x,Type &y) //交换x、y { Type temp=x; x=y; y=temp; //通过循环赋值交换x、y } 这样得到可以将任意一种类型Type的两个数据进行互换的函数模板。 ·75· 下面的3个类Integer、Float和Double分别用来处理整型数、单精度实型数以及双精 度实型数。这3个类的处理功能完全一样,只是处理的数据类型不同,下面是这3个类的 声明: //声明整型类 class Integer {p rivate: //数据成员 int num; //数据值 public: //公有函数 Integer(int n=0): num(n){ } //构造函数 void Set(int n) { num=n; } //设置数据值 int Get() const { return num; } //返回数据值 }; //声明单精度实型数类 class Float {p rivate: //数据成员 float num; //数据值 public: //公有函数 Float(float n=0): num(n){ } //构造函数 void Set(float n) { num=n; } //设置数据值 float Get() const { return num; } //返回数据值 }; //声明双精度实型数类 class Double {p rivate: //数据成员 double num; //数据值 public: //公有函数 Double(double n=0): num(n){ } //构造函数 void Set(double n) { num=n; } //设置数据值 double Get() const { return num; } //返回数据值 }; ·76· 上面实现的3个类的功能相同,只是处理的数据类型不同,都不能处理任意类型的数 据,但采用类型参数化后就可以处理任何类型的数据,这样就得到了类模板。使用类模板定 义如下: template class Number { private: //数据成员 Type num; //数据值 public: //公有函数 Number(Type n=0): num(n){} //构造函数 void Set(Type n) { num=n; } //设置数据值 Type Get() const { return num; } //返回数据值 }; 注意:函数模板或类模板是对一组函数或类的描述,模板是一种由通用代码构成,使用 类型参数产生一组函数或类的机制。 3.2 函数模板及模板函数 函数模板是对一批功能相同的函数的说明,它不是某一个具体的函数,而是带有类型参 数的一种描述。模板函数是将函数模板内的数据类型参数取某一个具体的数据类型后得到 的具体函数。 3.2.1 函数模板的声明及生成模板函数 使用函数模板的方法是先声明函数模板,然后将其类型参数具体化,形成相应的模板函 数后,才能调用模板函数。 函数模板的一般声明格式如下。 格式1: template 返回值类型函数模板名(形参表) { … //函数模板体 } 格式2: template 返回值类型函数模板名(形参表) { ·77· … //函数模板体 } 其中,template是一个声明模板的关键字,class在此处并不表示类,只是借用此关键字表示 其后是一个类型参数。“class类型参数名1,class类型参数名2,…”称为类型形参表,类 型参数名可以用任何实际的类型(包括类类型)进行具体化(也称为实例化)得到模板函数。 class和typename的作用相同,都是表示类型名,二者可以互换。大部分C++ 程序员 都喜欢用class,这是因为class更容易输入,typename是新加入标准C++ 中的,使用 typename时,含义非常清楚。 在使用函数模板时,用实际的数据类型具体化(实例化)类型形式参数,再根据实际参数 类型生成一个具体的模板函数,模板函数的函数体与函数模板的函数模板体完全相同,在程 序中真正执行的代码是模板函数的代码。 在使用函数模板生成模板函数时,有两种使用方式。 方式1: 函数模板名(实参表) 方式2: 函数模板名<类型1, 类型2, …>(实参表) 方式1将根据实参类型确定类型形式参数的具体类型,方式2中,“<类型1,类型2,…>” 称为类型实参表,用类型实参表中的类型来确定类型形式参数的具体类型。 说明:类型形参表与类型实参表通常只包含一个类型,这时函数模板的一般声明格式 如下。格 式1: template 返回值类型函数模板名(形参表) { … //函数模板体 } 格式2: template 返回值类型函数模板名(形参表) { … //函数模板体 } 使用函数模板生成模板函数的两种方式。 方式1: 函数模板名(实参表) 方式2: ·78· 函数模板名<类型> (实参表) 例3.1 函数模板的声明与模板函数的调用示例,程序演示见3011.mp4~3013.mp4。 //文件路径名:e3_1\main.cpp #include //编译预处理命令 using namespace std; //使用命名空间std template Type Max(Type x,Type y) //求x、y 的最大值 { return x(2, 3.0)< //编译预处理命令 #include //编译预处理命令 using namespace std; //使用命名空间std template Type Max(Type x,Type y) //求x、y 的最大值 { return x //编译预处理命令 using namespace std; //使用命名空间std template Type Max(Type x,Type y) //求x、y 的最大值 { return x Type Max(Type x,Type y,Type z) //求x、y、z 的最大值 { Type m=x Type Max(Type a[], int n) //求a[0],a[1],…,a[n-1]的最大值 { Type m=a[0]; //假设a[0]为最大值 for (int i=1; i class 类模板名 { … //类模板体 }; 形式2: template class 类模板名 { … //类模板体 }; 类模板的成员函数模板不但可以在类模板体内定义,也可以在类模板体外定义。在类 模板体内定义时,与一般类的成员函数的定义方法完全一样;在类模板体外定义时,需要采 用下面的形式。 形式1: template 返回值类型类模板名<类型参数名1, 类型参数名2, … >::成员函数模板名(形参表) { … //成员函数模板体 } 形式2: template 返回值类型类模板名<类型参数名1, 类型参数名2, … >::成员函数名(形参表) { … //成员函数模板体 } 其中,“class类型参数名1,class类型参数名2,…”与“typename类型参数名1,typename 类型参数名2,…”为类型形参表,与函数模板中的类型形参表意义一样。 类模板必须用实际的数据类型具体化(实例化)类型形式参数,再根据实际参数类型,生 成一个具体的模板类,然后才能用来定义具体对象。一般语法格式如下: 类模板名<类型1, 类型2, … >对象名; 例3.4 使用类模板的示例,程序演示见3041.mp4~3043.mp4。 ·82· //文件路径名:e3_4\main.cpp #include //编译预处理命令 using namespace std; //使用命名空间std //声明数组类模板 template class Array {p rivate: //数据成员 Type *elem; //存储数据元素值 int size; //数组元素个数 public: //公有函数模板 Array(int sz): size(sz) { elem=new Type[size]; } //构造函数 ~ Array(){ delete elem; } //析构函数 void SetElem(Type e, int i); //设置元素值 Type GetElem(int i) const; //求元素值 }; template void Array ::SetElem(Type e,int i) //设置元素值 { if (i<0||i>=size) { cout<<"元素位置错!"< Type Array::GetElem(int i) const //求元素值 { if (i<0||i>=size) { cout<<"元素位置错!"< obj(n); //定义数组对象 int i; //定义临时变量 for (i=0; i //编译预处理命令 using namespace std; //使用命名空间std //声明数组类模板 template class Array {p rivate: //数据成员 Type elem[size]; //存储数据元素值 public: //公有函数模板 void SetElem(Type e,int i); //设置元素值 ·84· Type GetElem(int i) const; //求元素值 }; template void Array ::SetElem(Type e,int i) //设置元素值 { if (i<0||i>=size) { cout<<"元素位置错!"< Type Array ::GetElem(int i) const //求元素值 { if (i<0||i>=size) { cout<<"元素位置错!"<obj; //定义数组对象 int i; //定义临时变量 for (i=0; i int Partition(Type a[], int low, int high) //交换a[low..high]中的元素,使枢轴移动到适当位置,要求在枢轴之前的元素 //不大于枢轴,在枢轴之后的元素不小于枢轴的,并返回枢轴的位置 { while (low=a[low]) { //a[low]为枢轴,使high 右边的元素不小于a[low] high--; } Type tem=a[low]; a[low]=a[high]; a[high]=tem; //交抽a[low]、a[high] while (low void QuickSortHelp(Type a[],int low,int high) //对数组a[low..high]中的元素进行快速排序 { if (low void QuickSort(Type a[],int n) //对数组a 进行快速排序 { QuickSortHelp(a,0,n -1); } #endif (3)建立源程序文件main.cpp,实现main()函数,具体代码如下: //文件路径名: quick_sort\main.cpp #include //编译预处理命令 using namespace std; //使用命名空间std #include "quick_sort.h" //快速排序 int main() //主函数main() { int a[]={49,38,66,97,38,68}; //数组 int i,n=6; //定义变量 cout<<"排序前:"; for (i=0; i //编译预处理命令 using namespace std; //使用命名空间std //声明类模板MyCalss template class MyCalss {p rivate: //数据成员 Type value; //存储数据元素值 public: //公有函数 MyCalss(const Type &v): value(v){ } //构造函数 template friend void Show(const MyCalss&obj); //输出对象的信息 }; template void Show(const MyCalss &obj) //输出对象的信息 { cout< obj(8); //定义对象 Show(obj); //输出对象的信息 return 0; //返回值0, 返回操作系统 } 对于VisualC++6.0,还可去掉类体模板中的“template”,也就是采用如 下格式: //本程序适合于Visual C++6.0 //文件路径名:trap 3_2\main.cpp #include //编译预处理命令 using namespace std; //使用命名空间std //声明类模板MyCalss template class MyCalss {p rivate: //数据成员 Type value; //存储数据元素值 ·89· public: //公有函数模板 MyCalss(const Type &v): value(v){ } //构造函数模板 friend void Show(const MyCalss&obj); //输出对象的信息 }; template void Show(const MyCalss&obj) //输出对象的信息 { cout<”,也就是采用如下格式: //本程序适合于Dev-C++ //文件路径名:trap3_3\main.cpp #include //编译预处理命令 using namespace std; //使用命名空间std //声明类模板MyCalss template class MyCalss { private: //数据成员 Type value; //存储数据元素值 public: //公有函数模板 MyCalss(const Type &v): value(v){ } //构造函数模板 friend void Show(const MyCalss&obj){ cout<”括起来的部分是模板的类型形参表 C.类模板不能有数据成员 D.在一定条件下函数模板的类型实参可以省略 ·90· 2.有以下函数模板定义: template Type Fun(const Type &x, const Type &y) { return x * x+y * y; } 在下列对Fun()的调用中,错误的是。 A.Fun(3,5) B.Fun(3.0,5.5) C.Fun(3,5.5) D.Fun(3,5.5) 3.关于关键字class和typename,下列表述中正确的是。 A.程序中typename都可以替换为class B.程序中的class都可以替换为typename C.在模板类型形参表中只能用typename声明参数的类型 D.在模板类型形参表中只能用class声明参数的类型 4.有以下函数模板: template Type Square(const Type &x) { return x * x; } 其中,Type是。 A.函数形参B.函数实参 C.模板类型形参D.模板类型实参 5.C++中的模板包括。 A.对象模板和函数模板B.对象模板和类模板 C.函数模板和类模板D.变量模板和对象模板 二、填空题 1.已知一个函数模板的声明如下: template T1 Fun(T2 n) { return n*5.0; } 若要求以int型数7为函数实参调用该模板函数,并返回一个double型数,则该调用应 表示为。 2.已知intdbl(intn){returnn+n;}和longdbl(longn){returnn+n;}是一个函 数模板的两个模板函数,则该函数模板的声明是 template 3.下面程序的运行结果是。 //文件路径名:ex3_2_3\main.cpp #include //编译预处理命令 using namespace std; //使用命名空间std template ·91· Type Min(const Type &a, const Type &b) //求a、b 的最小值 { if (a