8.1模板的概念
函数重载可以实现具有相同功能的函数的函数名相同,使程序更加易于理解。系统可以根据不同的参数类型来区分函数。这样虽然很方便,但是书写函数的个数并没有减少,重载函数的代码量几乎完全相同。那么如何解决这个问题?
C++提供了模板。模板是C++的一个重要特征。模板是类型参数化的工具。所谓类型参数化,是指把类型定义为参数,当参数实例化时,可指定不同的数据类型,从而真正实现代码的可重用。模板分函数模板和类模板,它们分别允许用户构造模板函数和模板类。
例如,求两个数的最大值:
int max(int x, int y)
{
return (x>y)?x:y;
}
char max(char x, char y)
{
return (x>y)?x:y;
}
float max(float x, float y)
{
return (x>y)?x:y;
}
这些函数所执行的功能相同,只是参数类型和函数返回值类型不同。这样不仅程序代码的重用性差,而且存在大量冗余信息,是程序维护起来相当困难。解决这个问题的最好方法是使用模板。
8.2函数模板和模板函数
当对每种数据类型执行相同的操作,用函数模板来完成将非常简单。程序员只需定义一次函数模板,根据调用函数时提供的参数类型,编译器会产生相应的目标代码函数,以正确处理每种类型的调用。
声明函数模板的格式:
template <class 类型参数>
返回类型 函数名(模板形参表)
{
函数体
}
例如,输出不同类型数组的元素值可定义成函数模板:
template <class T>
void Printarray(T *array, int count)
{
for(int i = 0; i < count; i++)
{
cout << array[i] << ” ”;
}
cout <<endl;
}
也可以定义成:
template <typename T>
void Printarray(T *arrya, int count)
{
for(int i = 0; i < count; i++)
{
cout << array[i] << ” ”;
}
cout <<endl;
}
说明:
(1)T是类型参数,它既可以是系统预定义的数据类型,也可以是用户自定义的类型。
(2)类型参数前需要加关键字class(或typename),这个class并不是类的意思,而是表示任何类型的意思。
(3)在使用模板函数时,关键字class(或typename)后面的类型参数,必须实例化,即用实际的数据类型替代它。
(4)< >里面的类型参数可以有一个或多个类型参数,但多个类型参数之间要用逗号分隔。
函数模板应用举例:输出不同类型数组的元素值。
#include <iostream>
using namespace std;
template <class T>
void Printarray(T *array, int count)
{
for(int i = 0; i < count; i++)
{
cout << array[i] << " ";
}
cout <<endl;
}
int main()
{
int a[5] = {1,2,3,4,5};
double b[7] = {1.1,2.2,3.3,4.4,5.5,6.6,7.7};
char c[6] = "HELLO";
Printarray(a,5);
Printarray(b,7);
Printarray(c,6);
return 0;
}
程序在调用Printarray()打印各个数组时,当参数为a,则类型参数T为int;当参数是b,则类型参数T为double;当参数是c,类型参数T为char。从本程序可以看出,使用函数模板不用单独定义3个函数,从而解决了当采用函数重载技术时所产生的代码冗余问题。
注意:(1)在执行Printarray函数调用时,根据参数的类型,系统自动在内存中生成一个模板函数(即实例化函数),并执行该函数。
(2)函数模板中可以使用多个类型参数。但每个模板形参前必须有关键字class或typename。
多个类型参数应用举例:求两个数的最大值。
#include <iostream>
using namespace std;
template <class T1, class T2>
T1 Max(T1 x, T2 y)
{
return (x>y)?x:y;
}
int main()
{
int i = 10;
float f = 12.5;
double d = 50.344;
char c = 'A';
cout << "the max of i,c is: " << Max(i,c) <<endl;
cout << "the max of i,f is: " << Max(f,i) <<endl;
cout << "the max of d,f is: " << Max(d,f) <<endl;
return 0;
}
说明:由于函数模板中的参数类型不一样,因此每个模板形参前都有class。在执行语句Max(i,c)时,令编译器实例化Max模板函数,类型参数T1为int,T2为char,然后调用Max计算最大值。其他语句与此类似。
(3)在template语句和函数模板定义语句之间不允许有其他的语句,例如:
template <class T1, class T2>
int n;
T1 Max(T1 x, T2 y)
{
return (x>y)?x:y;
}
(4)模板函数类似于函数重载,但与函数重载不同。在进行函数重载时,每个函数体的动作可以相同也可以不同;但模板函数中的动作必须相同。例如,下面的函数只能用函数重载,而不能用模板函数。
void print(char *name)
{
cout << name <<endl;
}
void print(char *name, int no)
{
cout << name << no <<endl;
}
(5)函数模板中的模板形参T可以实例化为各种类型,但实例化T的各模板实参之间必须保证类型一致,否则将发生错误,例如:
#include <iostream>
using namespace std;
template <class T>
T Max(T x, T y)
{
return (x>y)?x:y;
}
int main()
{
int i = 10, j = 20;
float f = 12.5;
cout << ”the max of i, j is: ” << Max(i , j) <<endl;
cout << ”the max of i, j is: ” << Max(i , j) <<endl;
return 0;
}
解决这个问题有两种方法:
(1)采用强制类型转换,将Max(i , j)修改为Max(i , int(f))。
(2)定义一个完整的非模板函数重载函数模板,例如:
#include <iostream>
using namespace std;
template <class T>
T Max(T x, T y)
{
return (x>y)?x:y;
}
int Max(int , float);
int main()
{
int i = 10, j = 20;
float f = 12.5;
cout << "the max of i, j is: " << Max(i , j) <<endl;
cout << "the max of i, j is: " << Max(i , j) <<endl;
return 0;
}
int Max(int x, float y)
{
return 0;
}
处理所有类型值的冒泡排序:
#include <iostream>
using namespace std;
template <class T>
void bubble_sort(T *array, int n);
template <class T>
void show_array(T *array, int n);
int main()
{
int a[7] = {7,6,5,4,3,2,1};
double b[5] = {2.4,2.3,2.1,2.9,2.6};
bubble_sort(a,7);
show_array(a,7);
bubble_sort(b,5);
show_array(b,5);
return 0;
}
template <class T>
void bubble_sort(T *array, int n)
{
int i,j,flag;
T temp;
for(i = 0; i < n-1; i++)
{
flag = 0;
for(j = 1; j < n-i; j++)
{
if(array[j-1] > array[j])
{
temp = array[j-1];
array[j-1] = array[j];
array[j] = temp;
flag = 1;
}
}
if(flag == 0)
break;
}
}
template <class T>
void show_array(T *array, int n)
{
int i;
for(i = 0; i < n; i++)
{
cout << array[i] <<" ";
}
cout <<endl;
}
从以上的例子可以看出,函数模板提供了一类函数的抽象,它可以是任意类型T为参数及函数返回值。函数模板经实例化而生成的具体函数模板函数。函数模板代表了一类函数,模板函数表示某一具体的函数。
8.3类模板与模板类
类是对一组对象的公共性质的抽象,而类模板是更高层次的抽象。类模板允许用户为类定义一种模式,使类中的某些数据成员、成员函数的参数或返回值可以根据需要取任意类型。
类模板的定义格式:
template <class T>
class 类名
{
//...
};
说明:
(1)在每个类模板定义之前,都需要在前面加上模板声明,如template <class T>。在使用类模板时首先应将它实例化为一个具体的类(即模板类),类模板实例化为模板类的格式为:类名<具体类型名>。
(2)模板类可以有多个模板参数。
(3)在类定义体外定义成员函数时,如果该成员函数中有类型参数,则需要在函数体外进行模板声明,即在类名和函数名之间缀上<T>。具体格式:
返回类型 类名<T>::成员函数名(参数表)
(4)类模板不代表一个具体的、实际的类,而代表着一类类。因此在使用时,必须将类模板实例化为一个具体的类,格式如下:
类名<实际的类型> 对象名;
实例化的类称为模板类。模板类是类模板对某一特定类型的实例。类模板代表了一类类,模板类表示某一具体的类。
用类模板实现栈的基本运算:
#include <iostream>
using namespace std;
const int Size = 100;
template <class T>
class Stack
{
private:
T s[Size];
int top;
public:
Stack();
void Push(T x);
T Pop();
};
template <class T>
Stack<T>::Stack()
{
top = -1;
}
template <class T>
void Stack<T>::Push(T x)
{
if(top == Size-1)
{
cout << "Stack is full." <<endl;
return;
}
s[++top] = x;
}
template <class T>
T Stack<T>::Pop()
{
if(top == -1)
{
cout << "Stack underflow." <<endl;
return 0;
}
return s[top--];
}
int main()
{
Stack<int> a;
Stack<double> b;
Stack<char> c;
int i;
char ch;
for(i = 0; i < 10; i++)
{
a.Push(i);
}
for(ch = 'a'; ch <= 'j'; ch++)
{
c.Push(ch);
}
for(i = 0; i < 10; i++)
{
b.Push(1.1+i);
}
for(i = 0; i < 10; i++)
{
cout << a.Pop() << " ";
}
cout <<endl;
for(i = 0; i < 10; i++)
{
cout << b.Pop() << " ";
}
cout <<endl;
for(i = 0; i < 10; i++)
{
cout << c.Pop() << " ";
}
cout <<endl;
return 0;
}
类模板中有多个类型参数实例:
#include <iostream>
using namespace std;
template <class T1, class T2>
class A
{
private:
T1 x;
T2 y;
public:
A(T1 a, T2 b);
void print();
};
template <class T1, class T2>
A<T1, T2>::A(T1 a, T2 b)
{
x = a;
y = b;
}
template <class T1, class T2>
void A<T1, T2>::print()
{
cout << x << " " << y <<endl;
}
int main()
{
A<int, double> ob1(10, 12.3);
A<char*, char*> ob2((char*)"Diligence", (char*)"makes your dreams come true");
ob1.print();
ob2.print();
return 0;
}
用类模板和重载[]运算符实现不同类型的数组的输出:
#include <iostream>
using namespace std;
const int Size = 10;
template <class T>
class Array
{
private:
T a[Size];
public:
Array();
T &operator[](int i);
};
template <class T>
Array<T>::Array()
{
int i;
for(i = 0; i < Size; i++)
{
a[i] = 0;
}
}
template <class T>
T & Array<T>::operator[](int i)
{
if(i < 0 || i > Size-1)
{
cout << "Index value of" << i << "is not of bounds." <<endl;
}
return a[i];
}
int main()
{
Array<int> int_array;
Array<double> double_array;
int i;
cout << "Integer array:";
for(i = 0; i < Size; i++)
int_array[i] = i;
for(i = 0; i < Size; i++)
cout << int_array[i] <<" ";
cout <<endl;
cout << "Double array:";
cout.precision(2);
for(i = 0; i < Size; i++)
double_array[i] = double(i)/3;
for(i = 0; i < Size; i++)
cout << double_array[i] <<" ";
cout <<endl;
return 0;
}
由于在类中重载[]运算符,因此执行cout << int_array[i] << “ ”;时,编译系统将int_array[i]解释为int_array.operator[](i),从而调用运算符重载函数operator[](int i)。定义重载[]函数时,由于返回时一个T的引用,因此可以使用重载的[]用在赋值语句的左边,所以语句int_array[i] = i;是合法的。对于对象double_array的操作与int_array相同。
8.4程序实例
用类模板实现学生成绩管理系统:
分析:本例将要操作的所有对象构成一个链表,链表中的每个结点元素就是一个对象。定义一个类模板Linklist,数据成员*head表示指向链表的头指针,链表中每个结点元素包含数据域data和指针域next,数据域data是T类型,指针next指向链表中下一个结点元素。成员函数Inser_Linklist表示插入一个结点元素;成员函数Get_Linklist表示返回第i个结点元素的地址;成员函数Del_Linklist表示删除第i个结点元素;成员函数Print_Linklist表示输出链表中结点元素的值。
Linklist
Node<T> *head;
Linklist();
int Insert_Linklist();
Node<T> *Get_Linklist(int i);
int Del_Linklist(int i);
void Print_Linklist();
~Linklist();
Stu
long no;
char name[10];
float score;
Stu();
void Print();
#include <iostream>
using namespace std;
template <class T>
struct Node
{
T data;
Node *next;
};
template <class T>
class Linklist
{
private:
Node<T> *head;
Node<T> *r;
public:
Linklist();
int Insert_linklist();
Node<T>* Get_linklist(int i);
int Del_linklist(int i);
void Print_linklist();
~Linklist();
};
class Stu
{
private:
long no;
char name[10];
float score;
public:
Stu();
void Print();
};
template <class T>
Linklist<T>::Linklist()
{
head = NULL;
r = head;
}
template <class T>
int Linklist<T>::Insert_linklist()
{
Node<T> *s = new Node<T>;
if(s)
{
if(head)
{
r->next = s;
r = s;
}
else
{
head = s;
r = s;
}
}
if(r)
r->next = NULL;
return 1;
}
template <class T>
void Linklist<T>::Print_linklist()
{
Node<T> *p;
p = head;
while(p)
{
p->data.Print();
p = p->next;
}
cout <<endl;
}
template <class T>
Node<T> * Linklist<T>::Get_linklist(int i)
{
Node<T> *p = head;
int j = 1;
while(p != NULL && j < i)
{
p = p->next;
j++;
}
if(j == i)
return p;
else
return NULL;
}
template <class T>
int Linklist<T>::Del_linklist(int i)
{
Node<T> *p, *s;
if(i == 1)
{
p = head;
head = head->next;
delete p;
return 1;
}
else
{
p = Get_linklist(i-1);
if(p == NULL)
{
cout << "Error!" <<endl;
return -1;
}
else if(p->next == NULL)
{
cout << "NULL!" <<endl;
return 1;
}
else
{
s = p->next;
p->next = s->next;
if(!p->next)
r = p;
delete s;
return 1;
}
}
}
template <class T>
Linklist<T>::~Linklist()
{
delete head;
}
void menu()
{
cout <<endl;
cout << "1......Insert:" <<endl;
cout << "2......Delete:" <<endl;
cout << "3......Display:" <<endl;
cout << "4......Exit:" <<endl;
cout << "Choice:" <<endl;
}
Stu::Stu()
{
cout << "Input number,name,score:" <<endl;
cin >>no>>name>>score;
}
void Stu::Print()
{
cout << no << " " << name << " " << score <<endl;
}
int main()
{
Linklist<Stu> L;
int n,m = 1;
while(m)
{
menu();
cin >> n;
switch(n)
{
case 1:
{
int success;
success = L.Insert_linklist();
if(success == 1)
cout << "Insert successfully" <<endl;
else
cout << "Insert failure" <<endl;
break;
}
case 2:
{
int i,success;
cout << "Input sitution" <<endl;
cin >> i;
success = L.Del_linklist(i);
if(success == 1)
cout << "Delete successfully" <<endl;
else
cout << "Delete failure" <<endl;
break;
}
case 3:
{
cout << "Infomation:" <<endl;
L.Print_linklist();
break;
}
case 0: m = 0;
}
}
return 0;
}
类Stu是类模板Linklist所要实例化的一个具体类。数据成员包括学生的学号、姓名和成绩;成员函数Print表示输出学生信息。
用类模板实现银行办公系统:
本例将要操作的所有对象构成一个队列,队列中的每个结点元素就是一个对象。定义一个类模板Lqueue,数据成员*front表示指向队头的指针,*rear表示指向队尾的指针,链表中每个结点元素包含数据域data和指针域next,数据域data是T类型,指针next指向链表中下一个结点元素。成员函数In_Lqueue表示入队操作;成员函数Empty_Lqueue表示判断队列是否为空;成员函数Out_Lqueue表示出队操作;成员函数Print_Lqueue表示输出队列中结点元素的值。
Lqueue
QNode<T> *front,*rear;
Lqueue();
void In_Lqueue();
int Empty_Lqueue();
void Print_Lqueue();
~Lqueue();
Customer
int account, amount;
Customer();
void Print();
#include <iostream>
using namespace std;
template <class T>
struct Qnode
{
T data;
Qnode *next;
};
template <class T>
class Lqueue
{
private:
Qnode<T> *front,*rear;
public:
Lqueue();
void In_lqueue();
int Empty_lqueue();
int Out_lqueue();
void Print_lqueue();
~Lqueue();
};
class Customer
{
private:
int account;
int amount;
public:
Customer();
void Print();
};
template <class T>
Lqueue<T>::Lqueue()
{
rear = NULL;
front = NULL ;
}
template <class T>
void Lqueue<T>::Print_lqueue()
{
Qnode<T> *p;
p = front;
while(p != NULL)
{
p->data.Print();
p = p->next;
}
cout << endl <<endl;
}
template <class T>
void Lqueue<T>::In_lqueue()
{
Qnode<T> *p;
p = new Qnode<T>;
p->next = NULL;
if(front == 0)
{
front = p;
rear = p;
}
else
{
rear->next = p;
rear = p;
}
}
template <class T>
int Lqueue<T>::Empty_lqueue()
{
if(front == NULL && rear == NULL)
return 1;
else
return 0;
}
template <class T>
int Lqueue<T>::Out_lqueue()
{
Qnode<T> *p;
if(Empty_lqueue() == 1)
return 0;
else
{
p = front;
front = p->next;
delete p;
if(front == NULL)
rear = front;
return 1;
}
}
template <class T>
Lqueue<T>::~Lqueue()
{
delete rear;
}
void menu()
{
cout <<endl;
cout << "1......Push" <<endl;
cout << "2......Show" <<endl;
cout << "3......Pop" <<endl;
cout << "0......Exit" <<endl;
cout << "Choice:" <<endl;
}
Customer::Customer()
{
cout << "Input account and amount:" <<endl;
cin >>account>>amount;
}
void Customer::Print()
{
cout << account << " " << amount <<endl;
}
int main()
{
Lqueue<Customer> L;
int n,m = 1;
while(m)
{
menu();
cin >> n;
switch(n)
{
case 1:
{
L.In_lqueue();
cout << endl << "Element of Queue:" <<endl;
L.Print_lqueue();
break;
}
case 2:
{
int flag;
flag = L.Empty_lqueue();
if(flag != 1)
{
cout << "Element of Queue:" <<endl;
L.Print_lqueue();
}
else
cout << "NULL" <<endl;
break;
}
case 3:
{
int flag;
flag = L.Out_lqueue();
if(flag == 1)
{
cout << endl << "Element of Queue:" <<endl;
L.Print_lqueue();
}
else
cout << "Empty!" <<endl;
break;
}
case 0: m = 0;
}
}
return 0;
}
类Customer是类模板Lqueue所实例化的一个具体类。数据成员包括顾客的账号和金额;成员函数Print表示输出顾客的信息。