第七章 类属和模板
DESCRIPTION
第七章 类属和模板. 在程序设计中,我们总会发现程序的某些组成模块 所实现的 逻辑功能 是 相同 的,而 不同的 只是被 处理 对 象(数据)的 类型 ,例如下列函数模块: 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; } - PowerPoint PPT PresentationTRANSCRIPT
![Page 1: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/1.jpg)
第七章 类属和模板
![Page 2: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/2.jpg)
在程序设计中,我们总会发现程序的某些组成模块所实现的逻辑功能是相同的,而不同的只是被处理对象(数据)的类型,例如下列函数模块: 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; }
若能将处理对象(数据)的类型作为参数传递给提供同一逻辑功能的模块,便可以实现用同一模块处理不同类型对象的目的,从而大幅度地提高代码重用度和可维护性。这种将程序模块编写成参数化模板的方法就是类属编程。
![Page 3: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/3.jpg)
在 C++ 中,类属编程有两种实现方式:
⑴ 传统的采用创建类属数据结构的编程方式;
⑵ 采用 C++ 提供的类属工具 —— 参数化模板进行编
程的方式。
本章的重点是模板编程,但对传统类属编程的了解
将有助于对模板的理解。
![Page 4: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/4.jpg)
本章要点1 类属编程
类属编程的必要性,类属表的编制和应用实例。
2 模板编程
模板编程的概念,函数模板与模板函数,类模板与模板类,类模板的派生。
3 利用模板工具实现类属数据容器的实例
栈,队列,数组。
![Page 5: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/5.jpg)
7.1 类属7.1.1 为什麽要引入类属编程 为什麽要引入类属编程呢?可以通过一个实例来说
明。若有一个整数链表,可以将它定义成一个类,此类具有将整数插入链表、在链表中查找指定整数、从链表中删除指定整数等操作,类定义和使用如下:#include <iostream.h>
struct node { // 链表结点的数据结构int val; // 结点值node* next; // 结点链值
};
![Page 6: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/6.jpg)
class intlist // 整数链表类{
node * head; // 链表头指针int size; // 链表中的结点个数
public:
intlist() // 构造函数{ head = 0; size = 0; }
~intlist(); // 析构函数bool insert(int); // 向链表中插入一个结点值bool deletes(int); // 从链表中删除一个结点值bool contains(int); // 判断链表中是否包含指定结点值void print(); // 显示输出链表中所有结点值
};
![Page 7: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/7.jpg)
intlist::~intlist()
{
node* temp; // 定义一个结点型指针用于指向被删结点
for (node* p = head;p;) // 循环删除链表中的所有结点
{
temp = p; // 另时指针指向当前结点
p = p->next; // 修改链表中的当前结点指针
delete temp; // 删除当前结点
}
}
![Page 8: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/8.jpg)
bool intlist::insert(int x)
{
node* nodes = new node; // 创建一个新结点 if (nodes) // 判别新结点是否创建成功 {
nodes->val = x; // 将指定值赋予新结点的值域 nodes->next = head; // 将链表的头指针赋予新结点链域 head = nodes; // 修改链表头指针使之指向新结点 size++; // 链表中的结点个数增 1
return true; // 返回成功标志 }
return false; // 返回失败标志}
![Page 9: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/9.jpg)
bool intlist::deletes(int x)
{
node* temp; // 定义临时指针用于指向被删结点
if (head->val == x) // 判别链头结点值是否等于指定值
{
temp = head->next; // 临时指针指向下一个结点
delete head; // 删除链头结点
size--; // 链表中结点个数减 1
head = temp; // 修改头指针指向下一个结点
return true; // 返回成功标志
}
![Page 10: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/10.jpg)
for (node* p = temp = head; p; temp = p, p = p->next)
{ // 循环查找被删结点位置 if ((p->val == x) && (p != head))
{
temp->next = p->next; // 临时指针指向下一结点delete p; // 删除被查找到的结点size--; // 链表中结点个数减 1
return true; // 返回成功标志 }
}
return false; // 返回失败标志}
![Page 11: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/11.jpg)
bool intlist::contains(int x)
{
for (node* p = head; p; p = p->next) // 循环查找指定结点if (p->val == x) // 判断当前结点值是否等于指定值 return true; // 返回找到标志
return false; // 返回未找到标志}
void intlist::print()
{
for (node* p = head; p; p = p->next) // 顺序链表中的结点cout << p->val << " "; // 显示当前结点值
cout << "\n"; // 显示行结束符}
![Page 12: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/12.jpg)
main()
{
intlist list1; // 创建链表对象 list1
list1.insert(20); // 向链表中顺序插入 20 、 45 、 23 、36
list1.insert(45);
list1.insert(23);
list1.insert(36);
list1.print(); // 显示输出链表内容 list1.deletes(23); // 从链表中顺序删除 23 、 44
list1.deletes(44);
list1.print(); // 显示输出链表内容 return 1;
}
![Page 13: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/13.jpg)
假设又需要设计一个相似的链表,所不同的是新链表要保存的数据是 double 类型的。显然,新链表除了
与整数链表中每一个结点的数据类型不同外,其余均完全相同。但即使如此,实现新链表的程序代码仍需要重写一遍,即在上述程序中所有出现 int 的地方,均
改为 double ,并将类名改为 dublist 。修改如下:struct node
{
double val;
node* next;
};
![Page 14: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/14.jpg)
class dublist
{
node * head;
int size;
public:
dublist() { head = 0; size = 0; }
~dublist();
bool insert(double);
bool deletes(double);
bool contains(double);
void print();
};
![Page 15: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/15.jpg)
在相应的函数定义中,也需要将所有 int 参变量改为
double 类型(具体修改不再重复)。
假如现在又需要定义一个 float 类型链表,则要另建
立一套适应 float 类型的新程序代码。按照这样的解决
方法,需要为每一种使用链表结构保存的预定义或用
户自定义类型对象定义适应相应类型的相同逻辑功能
的不同程序代码。显然,这样的方法不但使程序代码
的重用性差,而且维护起来非常困难。类属编程是解
决这类问题的好方法。
![Page 16: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/16.jpg)
类属的概念来源于 ALGOL68 。在 Ada 语言中,类属
是它的一种最重要的核心编程概念,类属也就是软件
处理数据的类型参数化,它表现了一个软件元素能处
理多种类型数据的能力。类属的概念后来有了进一步
的扩展。
类属可分为无约束类属机制和约束类属机制,其中
无约束类属机制是指对类属参数没有施加任何特殊的
限制,而约束类属机制则意味着类属参数需要一定的
辅助条件。
![Page 17: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/17.jpg)
7.1.2 类属表
与其它抽象数据类型一样,类属表是由一个值的集
合和此集合上的可执行操作关联在一起的数据类型。
不同之处在于该值集合的类型不确定,因此不能被预
先定义,只有用确定类型的数据表导出类属表时,值
集合才被建立。
在 C++ 中,要定义对一个数据表的操作时,必须预
先确定表的值集合,即必须确定值集合的类型。如何
来处理这一矛盾,实现一个类属表定义呢?
![Page 18: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/18.jpg)
⑴ 定义一个类型不预先确定值集合的方法是将值集合
中的每个数据元素定义为字符类型指针,即可以用
动态创建的字节串表示值集合中将要存储的任意类
型数据。
⑵ 由导出类属表的具体数据值集合中的数据类型确定
动态创建字节串的长度。例如,一个整型数占两个
字节,则应该用长度为 2 的字节串来表示集合元素
值;又如一个浮点型数占四个字节,则可以长度为
4 的字节串来表示集合元素值。
![Page 19: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/19.jpg)
⑶ 确定的类型数据与动态创建的字节串的关系是通过 共享相同的内存单元,而使用不同的类型表示和操 作实现的。而字节串长度是通过类属表值集合对象 被创建时所执行的类属表构造函数来传递的。 下面给出一个类属表的具体定义,分析程序的构成
和执行逻辑,可以加深理解类属表的实现机制和意义。程序代码如下:#include <iostream.h>
struct node { // 链表中结点的数据结构 node* next; // 结点的链域 char *contents; // 结点的值域(字符串型指针)};
![Page 20: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/20.jpg)
class list // 类属链表类{
node* head; // 头指针 int size; // 结点值域的字节数public:
list(int s) { head = 0; size = s; } // 构造函数 void insert(char* a); // 将指定值加入到链表头 void append(char* a); // 将指定值加入到链表尾 char* get(); // 从链表头获取结点值 void clear(); // 删除链表中的全部结点 ~list(){ clear(); } // 析构函数};
![Page 21: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/21.jpg)
void list::insert(char* a)
{
node* temp; // 定义临时指针指向插入的新结点 temp = new node; // 为新结点分配内存空间 temp->contents = new char[size]; // 为新结点值域分配内
存if(temp != 0 && temp->contents != 0) // 判定结点是否有效
{
for (int i = 0; i < size; i++) // 以字节方式为新结点值域赋值 temp->contents[i] = a[i];
temp->next = head; // 新结点的链域指向链表头结点 head = temp; // 修改头指针,使之指向新结点
}
}
![Page 22: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/22.jpg)
void list::append(char* a)
{
node *previous, *current, *newnode; // 定义临时指针 newnode = new node; // 为加入的的新结点分配内存 newnode->contents = new char[size];// 新结点值域分配内
存 if(newnode==0 || newnode->contents==0) // 判定结点有效?
return;
newnode->next = 0; // 新结点链域为空(表示尾结点) for (int i = 0; i < size; i++) // 以字节方式为新结点值域赋值
newnode->contents[i] = a[i];
if (head) // 判别链表是否为空 { // 不为空
previous = head;
current = head->next;
![Page 23: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/23.jpg)
while(current != 0)
{ // 查找链尾 ( 循环结束时, previous 指向表的最后结点 )
previous = current;
current = current->next;
}
previous->next=newnode; // 尾结点的链域指向新结点
}
else
{ // 为空 head = newnode; // 加入新结点
}
}
![Page 24: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/24.jpg)
char* list::get()
{
if (head == 0) { cout << "This is a empty list"; return 0; }
else
{ // 不为空char* r; // 定义字符型 指针指向将要返回的数据
值r = new char[size];
// 根据数据类型为返回数据值域分配内存空间node* f = head; // 定义临时指针指向链表头结点for (int i = 0; i < size; i++) // 传递返回值
r[i] = f->contents[i];
head = head->next; // 头指针指向链表头的下一个结点 return f; // 返回所获取的数据
}
}
![Page 25: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/25.jpg)
void list::clear()
{
node* p = head; // 定义临时指针 p 指向链表头结点
while (p != 0) // 判别链表是否为空
{ // 顺序删除链表中的所有结点
node* pp = p; // 定义临时指针 pp 指向被删除的结点
p = p->next; // 使 p 指向被删除结点的下一个结点
delete []pp->contents; // 释放被删除结点的值域内存
delete pp; // 释放被删除结点的内存
}
}
![Page 26: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/26.jpg)
main()
{
list my_list(sizeof(float)); // 创建一个 float 数据类型的链表
// 链表头中顺序加入数据 1.5 、 2.5 、 3.5 和 6.0
float r;
r = 1.5;
my_list.insert((char*)&r);
r = 2.5;
my_list.insert((char*)&r);
r = 3.5;
my_list.insert((char*)&r);
![Page 27: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/27.jpg)
r = 6.0;
my_list.insert((char*)&r);
for (int i = 0; i < 4; i++) // 顺序显示输出链表各个结点的值
{
r = (float)*(float*)my_list.get(); // 类型转换,获得浮点数值
cout << r << '\n';
}
return 1;
}
![Page 28: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/28.jpg)
在本程序中应注意下面几个问题:⑴ 结点的存储分配: 成员函数 insert 和 append 需要为结点 newnode
和结 点的数据值域 newnode->contents 动态分配内存。结 点数据值的大小被保存在私有数据成员 size 中。
⑵ 结点的数据内容的赋值: 根据 size 中的数据长度,逐字节传送。 ⑶ main 函数中类属对象的创建: 创建 list 类属表对象 my_list 时,须使用 sizeof(t
ype)
正确传递结点的数据的字节数,例如: sizeof(float) 。
![Page 29: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/29.jpg)
⑷ 插入结点时的实参传递: 调用成员函数 insert 和 append 时,需要传递 c
har*
类型实参,这就是说,将要插入链表的数据强制转 换成 char 类型,并将数据内存地址作为实参值传递 给被调用的函数,例如: my_list.insert((char*)&r);
⑸ 输出时的类型转换: 将存储在链表结点值域中的 char 类型数据强制转换 成所需要的数据类型后输出,例如: r = (float)*(float*)my_list.get();
![Page 30: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/30.jpg)
7.1.3 从类属表中导出栈和队列
类属表设计好以后,我们就可以利用继承机制由它
派生出一些其他类,例如,可以用它派生整数栈类和
整数队列类。在派生中需注意以下几个问题:
1 栈的特点是先进后出。
⑴ 将进和出的操作放在链表的表头进行;
⑵ 栈中的 push 操作可借用类属表中的 insert 函数;
⑶ 栈中的 pop 操作可借用类属表中的 get 函数。
![Page 31: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/31.jpg)
2 队列的特点是先进先出。 ⑴ 将链表设计成一端插入,另一端取出。例如,链
表尾部插入,链表头部取出; ⑵ 队列的 put 操作可借用类属表中的 append
函数; ⑶ 队列的 get 操作可借用类属表中的 get 函数。3 类型的转换。 因为整数栈和队列实际上均是属性链表,为了使用 方便,在数据插入和提取时,其数据都已经确定是 整数,而不应是类属链表中的字符串;所以在调用 类属链表 list 中的成员函数时,不要忘记进行强制 类型转换。实现代码示意如下:
![Page 32: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/32.jpg)
struct node
{
node* next;
char* contents;
};
class list
{
node* head;
int size;
public:
![Page 33: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/33.jpg)
list(int s){ head = 0; size = s; }
void insert(char* a);
void append(char* a);
char* get();
void clear();
~list(){ clear(); }
};
![Page 34: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/34.jpg)
class int_stack : list
{
public:
int_stack() : list(sizeof(int)) { }
void push(int a)
{
list::insert((char*)&a);
}
int pop()
{
return *((int*)list::get());
}
};
![Page 35: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/35.jpg)
class int_queue : list
{
public:
int_queue() : list(sizeof(int)) { }
void put(int a)
{
list::append((char*)&a);
}
int get()
{
return *((int*)list::get());
}
}; 返回
![Page 36: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/36.jpg)
7.2 模板编程7.2.1 模板的概念 模板是实现类属机制的一种工具,模板的功能非常强,既可以实现无约束类属机制,也可以实现约束类属机制。模板允许用户构造模板函数(类属函数),还允许用户构造模板类(类属类)。下图所示意的是类模板或函数模板、类、对象、函数之间的关系:
函数模板或类模板
模板函数
实例化
模板类
实例化
对象实例化
![Page 37: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/37.jpg)
模板定义的一般形式:template < 模板形参表列 >
模板定义体其中:
① template :定义模板形参说明行的关键字,表示定义 一个模板的开始。② 模板形参表列:由若干模板形参组成的。每个模板 形参均是由关键字 class 和类型形参名组成。③ 模板定义体:函数模板的定义,或类模板的定义。注意,模板形参说明行与模板定义体之间不允许有任何其他语句。
![Page 38: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/38.jpg)
7.2.2 函数模板与模板函数1 什麽是函数模板与模板函数 函数模板的声明格式如下: template <class type>
类型名 函数模板名 ( 参数表列 )
{ 函数模板定义体 }
⑴ type 表示模板形参名的一般形式,与说明一般函数形参名相同,可以是用户命名任何合法标识,只不过说明的是数据类型而不是数据值。
⑵ 函数模板的类型名和参数表列中参数类型名可以
是确定的预定义或自定义类型名,也可以是模板
形参名。
![Page 39: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/39.jpg)
例如,将求两个数的最大值函数定义成函数模板: template <class T>
T max(T x, T y) { return (x > y)? x : y; }
如此定义的函数模板 max 代表的是一类函数。若要 使用函数模板 max 进行求最大值运算,必须首先将 模板形参 T 实例化为确定数据类型(如 int 、 flo
at 、 double 等)。 从这个意义上说:函数模板不是一个完全的函数, 将 T 实例化的对象类型称之为模板实参,实例化后 的函数模板称为模板函数。 一个使用函数模板的完整程序如下所示:
![Page 40: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/40.jpg)
#include <iostream.h>
template <class AT>
AT Max(AT x, AT y) { return (x > y)? x : y; }
void 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: " << Max(i1, i2) << endl;
cout << "The max of f1,f2 is: " << Max(f1, f2) << endl;
cout << "The max of d1,d2 is: " << Max(d1, d2) << endl;
cout << "The max of c1,c2 is: " << Max(c1, c2) << endl;
}
![Page 41: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/41.jpg)
主函数中对函数模板 Max 的四次调用: Max(i1,i
2) 、 Max(f1,f2) 、 Max(d1,d2) 、 Max(c1,c2) 分别将模板参数 AT 实例化为 int 、 float 、 double 和 char 。下图给出了 函数模板 Max 和四个模板函数的关系。
函数模板实现了函数参数类型的通用性,作为一种 代码重用机制,可以大幅度地提高程序设计效率和 可维护性。又例如:
函数模板Max(x, y)
模板函数Max(c1, c2)
char实例化
模板函数Max(d1, d2)
double实例化
模板函数Max(f1, f2)
float实例化
模板函数Max(i1, i2)
int实例化
![Page 42: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/42.jpg)
#include <iostream.h>
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 int_array[] = {1,2,3,4,5,6,7,8,9,10};
double double_array[] =
{1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9.9,10.10};
![Page 43: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/43.jpg)
main()
{
int itotal = sum(int_array, 10);
double dtotal = sum(double_array, 10);
cout << "The summary of integer array are: "
<< itotal << endl;
cout << "The summary of double array are: "
<< dtotal << endl;
return 1;
}
![Page 44: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/44.jpg)
几点说明: ⑴ 在函数模板中允许使用多个类型参数。但应注意
每个模板形参前必须有关键字 class 。例如: #include <iostream.h>
template <class type1, class type2>
void myfunc(type1 x, type2 y)
{ cout << x << ' ' << y << endl; }
void main()
{
myfunc(10, "hao");
myfunc(0.123, 10L);
}
![Page 45: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/45.jpg)
程序执行结果: 10 hao
0.123 10
⑵ template 语句与函数模板定义语句之间不允许有
任何其他语句,例如:
template <class T>
int i; // 错误,不允许有别的语句
T Max(T x, T y) { return (x > y) ? x : y; }
![Page 46: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/46.jpg)
⑶ 模板函数与重载函数比较:
函数重载需要通过多个函数重载版本来实现,每
个函数版本可以执行不同的操作。
函数模板是通过模板形参实例化为不同类型数据
提供操作的摸板函数版本,但所有模板函数都必
须执行相同的操作逻辑。因此,下面的重载函数
就不能用模板函数代替。
void outdata(int i) { cout << i; }
void outdata(double d) { cout << "d = " << d << endl;
}
![Page 47: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/47.jpg)
2 重载模板函数 虽然函数模板中的模板形参 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(c, c); // 正确Max(i, c); // 错误Max(c, i); // 错误
}
![Page 48: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/48.jpg)
分析出现错误的原因是:函数模板被调用时,编译
器按最先遇到的实参类型隐含生成一个模板函数,
然后用该模板函数对以后出现的所有模板实参进行
一致性检查。例如,对语句 Max(i, c) ,编译器先按
照实参 i 将模板形参 T 实例化为 int 类型,隐含生成
模板函数 Max(int, int) ,然后用该模板函数检查此后
出现的模板实参,由于第二个模板实参 c 的类型与
模板函数的第二个参数类型 int 不符,便发生错误。
解决这个问题有两种方法:
![Page 49: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/49.jpg)
⑴ 采用强制类型转换, 例如,将调用语句 Max(i, c);
改写为: Max(i, int(c));
⑵ 重载函数扩充函数模板,这种重载有两种表达式: ① 只声明一个函数的原型,而不给出定义体,它的
定义体是借用函数模板的定义体。当执行此重载
版本时,会自动调用函数模板的定义体。例如,可将上面的程序改写如下:
![Page 50: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/50.jpg)
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(c, c);
Max(i, c);
Max(c, i);
}
![Page 51: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/51.jpg)
虽然该重载函数借用了函数模板的定义体,但它
支持数据类型之间的隐式转换。例如: Max(i,
c)
和 Max(c, i) 就是通过重载函数 Max(int, int) 完成了
类型的隐式转换,避免了错误发生。注意,多数
C++ 编译器不支持这种方法,例如 Visual C++ 。
![Page 52: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/52.jpg)
② 定义一个完整的重载函数,函数所带的参数类型
可以随意。例如:char* Max(char* x, char* y)
{ return (strcmp(x,y) > 0)? x : y; }
该函数实际上是为上述函数模板所能实例化的模
板函数增加了一个新的重载函数版本,当出现调
用语句 Max(“abcd”, “efgh”); 时,执行的是这个重载
函数。
在 C++ 中,函数模板与同名的重载函数的调用顺序
遵循下述约定:
![Page 53: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/53.jpg)
⑴ 查询一个参数完全匹配的函数,如果找到了,则查 询结束并调用查询到的函数,否则继续查询。
⑵ 查询一个函数模板,将其实例化,产生一个匹配的 模板函数,如果找到了,则查询结束并调用实例化 后的函数模板,否则继续查询。⑶ 若 ⑴ 和 ⑵ 都失败,再尝试对函数的其他的匹配, 例如,通过类型转换实现参数匹配等,如果成功, 则调用匹配成功的函数。如果 ⑴ ⑵ ⑶ 均未找到匹 配的函数,则是一个失配性调用错误。如果在 ⑴ 中匹配多于一个,则导致二义性调用错误。
![Page 54: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/54.jpg)
7.2.3 类模板与模板类1 类模板与模板类的概念 ⑴ 什麽是类模板: 如果有一族类的结构和行为逻辑都是相同的,不
同之处仅在类的全部或部分数据成员的类型不同。则可以定义一个有确定的结构和行为逻辑,而某些数据成员的类型不确定,某些成员函数
的参数、返回 类型不确定的 “类”,即类模板。
与函数模板相同,类模板定义中未确定的数据类
型是由模板形参说明行声明的。只有当模板形参
被确定的类型实参替代,类模板才被实例化一个
完全确定的类。
![Page 55: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/55.jpg)
⑵ 类模板的定义:
定义一个类模板,一般包括两方面的内容:
① 首先要定义类模板,其一般形式为:
template <class T>
class 类模板名
{ 类数据成员和成员函数定义代码 };
在类模板定义中凡采用模板形参类型声明的
数据成员,成员函数参数和返回类型均可以
使用 T , T* 或 T& 等进行类型声明,例如:
![Page 56: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/56.jpg)
template <class T>
class vector
{
T* data;
int size;
public:
vector(int);
T& operator[] (int);
…
};
![Page 57: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/57.jpg)
② 在类定义体外定义成员函数的实现代码时, 若所定义的成员函数的形参数据、返回
数据 或局部变量数据中包含了模板参数类型,
则 必须在函数体之前进行模板声明,并且
在函 数名所属的类模板名后加缀模板形参名。 例如: template <class T>
vector<T>::vector(int i)
{ … };
template <class T>
T& vector<T>::operator[] (int i)
{ … }
![Page 58: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/58.jpg)
⑶ 类模板的使用:
类模板的使用实际上是将类模板实例化成一个具
体的类或类对象,调用格式如下:
类模板名 < 实参类型 > 实例化对象名 ;
typedef 类模板名 < 实参类型 > 实例化类名 ;
例如:
template <class T>
class vector { … };
![Page 59: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/59.jpg)
main()
{
vector<int> x(5); // 创建一个有 5 个元素的整数向量
for (int i = 0; i < 5; i++)
x[i] = i; // 调用下标运算符为向量元素赋值 for (i = 0; i < 5; i++)
cout << x[i] << " " ; // 显示向量元素值 cout << "\n"
}
在实际应用中,我们可以将向量类模板 vector 实
例化为任何类型的向量对象,例如:vector<complex> v2(30);
vector<myclass> v3(4);
![Page 60: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/60.jpg)
这些实例化的类模板:
vector<int> , vector<complex> 和 vector<myclas
s> 被
称为模板类。模板类是类模板实例化的产物。类
模板和模板类之间的关系如下图所示。类模板
vector<T>
模板类vector<myclass>
myclass实例化
模板类vector<complex>
complex实例化
模板类vector<int>
int实例化
![Page 61: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/61.jpg)
例例 7-17-1 给出一个使用类模板 stack 的完整实例,在
此例中建立了字符型堆栈 cs1 、 cs2 和整型
堆栈 is1 、 is2 。
堆栈类模板 stack 与堆栈 cs1 、 cs2 、 is1 和 is2 之
间的关系如下图所示:
![Page 62: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/62.jpg)
stack-stck[1..*]:Type-tos:int
+init()+pop():Type+push(in ob:Type)
Type
cs2:
<bind>char
is1:
<bind>int
cs1:
<bind>char
is2:
<bind>int
![Page 63: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/63.jpg)
使用类模板的几点说明:
⑴ 在每个类模板定义之前,都需要在前面加上模板形
参说明行, 例如:
template <class Type>
使用类模板时,必须在类模板名字后加缀模板形参
名用于表示类模板,或者加缀模板实参名用于表示
模板类。例如:
stack<Type> 表示一个堆栈类模板
stack<int> 表示一个整型堆栈类
![Page 64: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/64.jpg)
⑵ 模板类可以有多个参数,例如: #include <iostream.h>
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;
}
};
![Page 65: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/65.jpg)
main()
{
myclass <int, double> ob1(12, 0.15);
myclass <char, char*> ob2('*', "This is a test");
ob1.show();
ob2.show();
return 1;
}
运行显示如下结果: i = 12 j = 0.15
i = * j = This is a test
![Page 66: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/66.jpg)
⑶ 在使用类模板的源程序中必须包含类模板的完全定 义,即不仅要包含类模板的定义代码,还要包含类 模板的实现代码。因此,如果类模板的完全定义分 写在头文件 .h 和实现文件 .cpp 两个文件中,则在使 用类模板的源程序中就必须包含能体现类模板完全 定义的实现文件 .cpp ,而不能只包含仅体现类模板 定义的 .h 。例如:程序中有一个类模板 vector ,其 定义代码在 vector.h 中,实现代码在 vector.cpp
中, 则在使用类模板 vector 的程序源文件中使用的包含 应该是: #include “vector.cpp” 而不应是 #include "vector.h" 。
![Page 67: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/67.jpg)
2 类模板使用举例例例 7-27-2 定义一个简单队列类模板,并将该模板实例化一
个对自定义职员类信息进行存储和操作的队列。问题分析: 简单队列是最有代表性的一种队列是一种具有 “先 进先出” 操作机制的数据容器。队列中存放的数 据,可以是预定义或自定义的任何类型。 本例中采用了链表队列模板。首先定义一个结构模 板 quenode 作为队列的结点,它与定义类模板方法 类似,再定义一个队列类模板 queue ,结构模板和 类模板取同样的模板形参名 T 。
![Page 68: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/68.jpg)
另外定义了一个职员类 staff ,并将队列模板 que
ue
实例化为 staff 类型的队列 staffque 。
结构模板 quenode 、队列类模板 queue 、职员类 staff
和实例化后的职员信息队列对象 staffque 之间的关
系如下图所示:
![Page 69: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/69.jpg)
quenode+nodedata:T+next:quenode*
Tqueue
#head:quenode<T>#tail:quenode<T>#quesize:int#allocateerror:bool
#copy(in q:queue<T>&):queue<T>&+pop(in x:T):bool+push(in x:T)+isempty()+operator=(in q:queue<T>&):queue<T>&+getallocateerror():bool
T
head
1
tail
1
staff+name:string+sex:string+age:int+salary:float
+assign(in name:string, in age:int, in salary:float, in sex:string)+print()
staffque:
<bind>staff
quedata
1..*
![Page 70: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/70.jpg)
3 类模板的派生 ⑴ 从类模板派生类模板
与类派生相同,类模板也可以派生出新的类模板。注意,基类模板名必须是未实例化的模板名。类模板派生的一般形式:template < 模板形参表列 >
class 派生类模板名: 继承方式 基类模板名 < 模板形参表列
>
{ 派生类模板新增成员定义代码 }; 例如:template <T>
class derived : public base<T>
{ … };
![Page 71: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/71.jpg)
例例 7-47-4 设计一个链表类模板 list ,并从 list 派生一
个集合类模板 set 。⑴ 由于集合和链表的差别在于集合中的元素
不 允许重复。因此插入操作时要判别集合中
是否 已有要插入的元素。因此,链表类模板 l
ist 的 插入操作 insert 应声明为虚函数,并在
集合类 模板 set 中重新定义;
⑵ list 中的其余成员函数均不需要再重新定义。list 和 set 之间的关系如下:
listT
setT
![Page 72: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/72.jpg)
⑵ 从类模板派生模板类 从模板类派生模板类,必须使用类模板的实例作
为派生模板类的基类。由于模板形参已经被确定
的类型实参替代,因此在模板类派生定义之前不
需要模板形参声明。模板类派生的一般形式:class 派生类名
: 派生方式 基类模板名 < 模板实参表列>
{ 派生类新增成员定义代码 }; 例如:class derived : public base<int>
{ … };
![Page 73: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/73.jpg)
例例 7-57-5设计一个链表类模板 list ,并从 list 派生一
个整数集合类 intset 。
返回
listT
intset
<bind>int
![Page 74: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/74.jpg)
7.3 利用模板工具实现类属数据容器的实例7.3.1 栈 栈是一种线性表数据结构,它的特点是:① 操作在线性表的一端按 "先进后出 " ( FILO )的原则 进行。② 栈中的元素可以是任何类型(预定义类型和自定义 类型)。③ 实现线性表的存储结构可以是数组(矩阵),也可 以是链表。
显然,一个通用栈的实现应该在归纳共性的基础上:
![Page 75: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/75.jpg)
① 定义抽象通用栈类模板 genabstractstack 以便实现不
同存储结构的栈的统一操作接口。
② 从抽象栈类模板派生定义针对某种确定存储结构的
通用栈类模板 genstack 以便适应不同类型栈元素的
操作需要。
③ 用通用栈类模板 genstack 定义确定类型的栈类或实
例。例如,栈元素为学生信息类 student 对象的堆栈
实例 studentstack 。
![Page 76: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/76.jpg)
类层次结构图:
genabstractstackT
genstackT
intstack:
<bind>int
studentstack:
<bind>student
student
1..* nodedata
genstackrecT
top
1
![Page 77: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/77.jpg)
1 建立抽象通用栈类模板 genabstractstack
原则:抽象出栈操作的统一接口(方法)规则,其 中与存储结构有关的方法声明纯虚方法。
模板类图描述如下:genabstractstack
#height:unsigned
+isempty():bool+push(in x:T&){abstract,}+pop(out x:T&):bool{abstract}+rollup(){abstract}+rolldown(){abstract}+clear(){abstract}+deque(in x:T&):bool
T
![Page 78: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/78.jpg)
⑴ 属性:① 栈高 height , unsigned 类型变量,该数
据成员 应该被隐藏(不允许类外访问),但应能
在派 生类中被派生类新增成员函数访问,所以
将该 数据成员的访问属性设定为 protected 。
⑵ 操作:① 判空操作 isempty() ,该操作与存储结构无
关, 因此,可以在模板定义中实现具体定义。
返回值: bool 类型, true 表示栈空, false 表示
栈非空。 算法:
![Page 79: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/79.jpg)
isempty()
BEGIN
if ( 栈高为 0 ) then return true
else return false
endif
END
② 压栈操作 push(T&) ,该操作与存储结构有关,
因此在模板定义中不能确定操作实现,故声明
为纯虚函数。 参数:模板形参的引用。
![Page 80: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/80.jpg)
③ 弹栈操作 pop(T&) ,该操作与存储结构有关,
因此在模板定义中不能确定操作实现,故声明
为纯虚函数。
参数:模板形参的引用。
返回值: bool 类型, true 表示成功, false 表示
失败。
④ 上滚操作 rollup() ,该操作与存储结构有关,
因此在模板定义中不能确定操作实现,故声明
为纯虚函数。无参数且无返回。
![Page 81: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/81.jpg)
⑤ 下滚操作 rolldown() ,该操作与存储结构有
关,因此在模板定义中不能确定操作实现,故
声明为纯虚函数。无参数且无返回。
⑥ 清除操作 clear() ,该操作与存储结构有关,因
此在模板定义中不能确定操作实现,故声明
为纯虚函数。无参数且无返回。
⑦ 取栈底元素操作 deque(T&) ,该操作虽然也与
存储结构有关,但可以通过调用成员函数
rolldown 和 pop 完成,可以定义操作实现。
![Page 82: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/82.jpg)
参数:模板形参的引用。
返回值: bool 类型, true 表示成功, false 表示
失败。
算法:
deque([OUT] T& x)
BEGIN
rolldown();
pop(x);
END
![Page 83: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/83.jpg)
2 建立通用栈类模板 genstack
首先确定保存栈元素的存储结构模板 genstackrec , 然后从 genabstractstack 派生 genstack 。 以链表存储结构为例, genstack 除了继承基类中已 经定义的属性和操作外,必须进行以下定义: · 对基类定义的纯虚函数进行实现定义。 · 新增属性 top 用于指示栈顶、 allocateerror 用于表 示存储分配状态。 · 新增成员函数 copy 完成堆栈复制、 getallocateerro
r
获取存储分配状态和运算符 operator = 。 类模板图描述如下:
![Page 84: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/84.jpg)
genstack
#top:genstackrec<T>#allocateerror:bool
-copy(in g:genstack<T>&):genstack<T>&+push(in x:T&){virtual}+pop(out x:T&):bool{virtual}+rollup(){virtual}+rolldown(){virtual}+clear(){virtual}+operator=(in g:genstack<T>&):genstack<T>&+getallocateerror():bool
T
![Page 85: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/85.jpg)
⑴ 属性:① 栈顶指针 top ,存储结构模板 genstackrec
类 型,该数据成员应该被隐藏(不允许类外
访 问),但应能在派生类中被派生类新增成
员函 数访问,所以将该数据成员的访问属性设
定为 protected 。② 结点存储分配标志 allocateerror , bool 类
型, 表示结点的存储空间分配是否成功。该数
据成 员应该被隐藏(不允许类外访问),但应
能在 派生类中被派生类新增成员函数访问,所
以将 该数据成员的访问属性设定为 protected 。
![Page 86: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/86.jpg)
⑵ 操作:
① 栈复制操作 copy(genstack&) ,该操作只用于内
部操作,因此访问属性可以设定为 protected
或 private 。
参数:栈类模板实例的引用。
返回值:栈类模板实例的引用。
算法:
![Page 87: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/87.jpg)
copy([IN] genstack& g)
BEGIN
if ( 目标栈不为空) then clear() endif
初始化目标栈并从源栈复制栈高 if ( 源栈为空) then return 空目标栈 endif
分配新结点作为目标栈栈顶结点 从源栈复制栈顶结点 while ( 未到源栈底)
分配新结点作为目标栈中下一个结点 ;
从源栈复制中下一个结点 ;
endwhile
return 目标栈 ;
END
![Page 88: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/88.jpg)
② 栈类构造函数 genstack() 。
③ 栈类拷贝构造函数 genstack(genstack&) 。
参数:栈类模板实例引用。
算法:
genstack([IN] genstack& g)
BEGIN
目标栈栈顶指针置空 ;
copy(g);
END
![Page 89: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/89.jpg)
④ 获取结点存储分配标志 getallocateerror() 。
返回值: bool 类型, true 分配成功, false 分配
失败。
算法:直接返回存储分配标志 allocateerr
or 。
⑤ 重新定义基类中的纯虚函数 clear() 。
算法:顺序弹出栈中的所有结点。
⑥ 重新定义基类中纯虚函数 push(T& x) 。
参数:模板形参类型引用。
算法:
![Page 90: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/90.jpg)
push ([IN]T& x)
BEGIN
if ( 栈不为空 ) then if (分配新结点失败)
then 置存储分配错误标志并结束操作 else 为新结点赋值并压入堆栈
修改栈顶指针 endif
else if (分配栈顶结点失败) then 置存储分配错误标志并结束操作 else 新结点赋值并使栈顶指针指向新结
点 endif
endif
END
![Page 91: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/91.jpg)
⑦ 定义基类中纯虚函数 pop(T& x) 。 参数:模板形参类型引用。 返回值: bool 类型, true 操作成
功, false 操作失败。
算法: pop([OUT] T& x)
BEGIN
if ( 栈中有元素) then x = 栈顶结点的值
删除栈顶结点并修改相应属性 return true
else return false
endif
END
![Page 92: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/92.jpg)
⑧ 定义基类中纯虚函数 rollup() 。 算法: rollup()
BEGIN
if ( 栈为空或者栈中元素个数 < 2 )then 结束操作else 将栈顶结点从链表中分离
修改栈顶指针指向次栈顶结点 循环找到栈底结点 将分离的栈顶结点连接在栈底之后
endif
END
![Page 93: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/93.jpg)
⑨ 定义基类中纯虚函数 rolldown() 。 算法: rolldown()
BEGIN
if ( 栈为空或者栈中元素个数 < 2 ) then 结束操作 else 循环找到栈底结点
将栈底结点从链表中分离 将分离的栈底结点连接在栈顶之前 修改栈顶指针指向原栈底结点
endif
END
![Page 94: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/94.jpg)
⑩ 重载赋值运算符成员函数 operator= 。
参数:栈类模板实例引用。
返回值:栈类模板实例引用。
算法:调用栈复制成员函数 copy 。 operator=([IN] gestack& g)
BEGIN
copy(g)
return *this
END
![Page 95: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/95.jpg)
3 定义模板实参类 student :
student
+name:string+sex:string+age:int+mark_average:float
+assign(in name:string, in age:int, in mark_average:float, in sex:string)
![Page 96: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/96.jpg)
4 使用栈模板 使用预定义类型或自定义类型将栈类模板中模板参 数变为实参,方法有两种: ⑴ 实例化为模板类,例如,
typedef genstack<int> intstack;
⑵ 实例化为类对象,例如,genstack<int> intstack;
在本例中的使用: ⑴ 用 student 类将栈类模板实例化为栈类对象。 ⑵ 对 student 类栈对象进行各种操作; ⑶ 在一个全程函数 viewstack 中引用 student 类栈对
象,用于顺序显示栈中所有的 student 信息。
![Page 97: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/97.jpg)
7.3.2 队列 队列也是一种线性表,它的特点:① 操作在线性表的两端按 "先进先出 " ( FIFO )的原则 进行;② 根据操作的差别队列可以分为: 简单队列:数据从队尾插入,队头弹出。 双端队列:数据从队头和队尾都可以插入和弹出。 优先队列:数据根据其优先级可以插入到队列的不
同位置。③ 队列中的元素可以是预定义和自定义的任何类型。④ 实现线性表的存储结构可以是数组(矩阵),也可 以是链表。
![Page 98: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/98.jpg)
显然通用队列的实现应该在归纳共性的基础上:① 定义抽象通用队列类模板 abque 以便实现能适用于 不同存储结构的队列的统一操作接口。② 从抽象通用队列类模板派生定义用于某种确定存储 结构的各种通用队列类模板: 简单队列通用类模板 —— queue1 , 双端队列通用类模板 —— queue2 , 优先队列通用类模板 —— queue3 , 以适应不同类型队列种类和队列元素的需要;③ 使用各种队列通用类模板定义确定类型的队列类或 实例。例如,学生信息类 student 对象的队列实例 studentqueue 。
![Page 99: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/99.jpg)
类层次结构图
abqueT
queue1T
queue2T
queue3T
intqueue1:
<bind>int
intqueue2:
<bind>int
intqueue3:
<bind>int
studentqueue1:
<bind>student
studentqueue2:
<bind>student
studentqueue3:
<bind>student
student1..* nodedata
1..*nodedata
1..*nodedata
quenodeT
head1
1 tail
quenodeT
head1
1 tail
quenodeT
head1
1 tail
![Page 100: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/100.jpg)
1 建立抽象通用队列类模板 abque
原则:抽象出队列操作的统一接口(方法)规则, 其中与存储结构有关,并且适用于所有类型队列的 方法声明纯虚方法。模板类图描述如下:
abque
#qsize:int
+isempty():bool+pushfr(in x:T&){virtual}+pushta(in x:T&){abstract}+popfr(out x:T&):bool{abstract}+popta(out x:T&):bool{virtual}+clear(){abstract}
T
![Page 101: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/101.jpg)
2 定义简单队列通用类模板 queue1
首先确定链表结点存储结构模板 quenode ,然后从 abque 派生 queue1 。 以链表存储结构为例,简单队列通用类模板除了继 承基类中已经定义的属性和操作外,必须进行如下 的定义工作: · 对基类定义的纯虚函数进行实现定义。 · 新增属性 head 指示队列头、 tail 指示队列尾和 allocateerror 表示队列元素结点的内存分配状态。 · 新增操作 copy 实现队列复制、 getallocateerror 获 取队列元素结点的内存分配状态、 put 队列插入 操作、 get 队列提取操作和运算符 operator = 。 类模板图描述如下:
![Page 102: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/102.jpg)
queue1
#head:quenode<T>*#tail:quenode<T>*#allocateerror:bool
-copy(in q:queue1<T>&):queue1<T>&+pushta(in x:T&){virtual}+popfr(out x:T&):bool{virtual}+put(in x:T&)+get(out x:T&):bool+clear(){virtual}+operator=(in q:queue1<T>&): queue1<T>&+getallocateerror():bool
T
![Page 103: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/103.jpg)
3 定义双端队列通用类模板 queue2
从抽象通用队列类模板 abque 派生双端队列通用类 模板 queue2 。 以链表存储结构为例,双端队列通用类模板除了继 承基类中已经定义的属性和操作外,必须进行如下 的定义工作: · 对基类中的全部虚函数进行实现和重载定义。 · 新增属性 head 指示队列头、 tail 指示队列尾和 allocateerror 表示队列元素结点的内存分配状态。 · 新增操作 copy 实现队列复制、 getallocateerror 获 取队列元素结点的内存分配状态、 put 队列插入 操作、 get 队列提取操作和运算符 operator = 。 类模板图描述如下:
![Page 104: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/104.jpg)
queue2
#head:quenode<T>*#tail:quenode<T>*#allocateerror:bool
-copy(in q:queue2<T>&):queue2<T>&+pushfr(in x:T&){virtual}+pushta(in x:T&){virtual}+popfr(out x:T&):bool{virtual}+popta(out x:T&):bool{virtual}+put(in x:T&)+get(out x:T&):bool+clear(){virtual}+operator=(in q:queue2<T>&): queue2<T>&+getallocateerror():bool
T
![Page 105: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/105.jpg)
4 定义优先队列通用类模板 queue3
从抽象通用队列类模板 abque 派生优先队列通用类 模板 queue3 。 以链表存储结构为例,优先队列通用类模板除了继 承基类中已经定义的属性和操作外,必须进行如下 的定义工作: · 对基类中的纯虚函数进行实现定义。 · 新增属性 head 指示队列头、 tail 指示队列尾和 allocateerror 表示队列元素结点的内存分配状态。 · 新增操作 copy 实现队列复制、 getallocateerror 获 取队列元素结点的内存分配状态、 put 队列插入 操作、 get 队列提取操作和运算符 operator = 。 类模板图描述如下:
![Page 106: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/106.jpg)
queue3
#head:quenode<T>*#tail:quenode<T>*#allocateerror:bool
-copy(in q:queue3<T>&):queue3<T>&+pushta(in x:T&){virtual}+popfr(out x:T&):bool{virtual}+put(in x:T&)+put(in x:T&, in a:int)+get(out x:T&):bool+clear(){virtual}+operator=(in q:queue1<T>&): queue1<T>&+getallocateerror():bool
T
![Page 107: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/107.jpg)
5 定义模板实参类 student :
各个类模板成员函数的算法分析作为课后练习,模仿例 7-6 中的分析和描述方法,写出本例每个类模板中完成各种队列主要操作的成员函数的算法。
student+name:string+sex:string+age:int+mark_average:float
+assign(in name:char*, in age:int, in mark_average:float, in sex:char*)
![Page 108: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/108.jpg)
7.3.3 数组 数组是一个分布在连续内存空间上线性表,因此它
最大的优点是可以高效地索引存取表元素,而与表的
大小无关。但使用预定义数组机制定义数组的类型和
大小必须预先确定,并且不能改变。
这里讨论的数组类模板,在定义时并不确定它所包
含元素的类型,因此它的内存大小也只能在数组元素
类型被实例化之后才能被分配,但必须保持数组的区
域连续性特点。
![Page 109: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/109.jpg)
对于一个数组类模板还应具备下列特定操作:
① 按照指定大小进行动态空间的再分配;
② 交换两个指定下标数组元素;
③ 数组中的元素排序;
④ 数组中的元素反序;
⑤ 向数组插入一个指定元素;
⑥ 从数组取出一个指定元素;
⑦ 删除一个指定下标数组元素
⑧ 在数组中查找一个指定元素。
![Page 110: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/110.jpg)
通用数组类模板的实现应该在归纳共性的基础上:
① 定义抽象通用数组类模板 abarray 以便实现适应不同
存储结构的动态数组的统一操作接口;
② 从抽象通用数组类模板 abarray 派生定义通用数组类
模板 array 以适应某种确定的存储结构,而元素类型
不确定的数组的需要;
③ 使用通用数组类模板 array 定义确定元素类型的数组
类或实例。例如,学生信息类 student 对象的数组实
例 studentarray 。
![Page 111: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/111.jpg)
类层次结构图:
abarrayT
arrayT
intarray:
<bind>int
studentarray:
<bind>student
student
1..* *dataptr
![Page 112: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/112.jpg)
1 建立抽象通用数组类模板 abarray
原则:按照需求抽象出数组操作的统一操作接口
(方法)规则,其中与存储结构有关的方法声明纯
虚方法。
模板类图描述如下:
![Page 113: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/113.jpg)
abarray
#size:unsigned#used:unsigned#inorder:order{enum}#errormessage:char[]
+expand(in newsize:unsigned):bool{abstract}+getsize():unsigned+getused():unsigned+geterrormessage():char*+getorder():order+resetorder()+store(in x:T&, in index:unsigned){abstract}+recall(out x:T&, in index:unsigned){abstract}+swapelem(in i:unsigned, in j:unsigned){abstract}+shellsort()+re_order()+linsearch(in key:T&):unsigned+binsearch(in key:T&):unsigned
T
![Page 114: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/114.jpg)
2 定义通用数组类模板 array
从抽象通用数组类模板 abarray 派生通用数组类模 板 array 。通用数组类模板除了继承基类中已经定义 的属性和操作外,必须进行以下定义工作: · 对基类定义的纯虚函数进行实现定义。 · 新增属性 dataptr 指示动态数组空间。 · 新增操作 copy 实现数组复制、 insert 向数组插入 指定数据、 deletes 删除数组元素和重载运算符 operator = 、 operator[] 、 operator<< 。 类模板图描述如下:
![Page 115: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/115.jpg)
array
#dataptr:T*
-copy(in arr:array<T>&):array<T>&+expand(in newsize:unsigned):bool{virtual}
+store(in x:T&, in index:unsigned){virtual}
+recall(in x:T&, in index:unsigned){virtual}
+swapelem(in i:unsigned, in j:unsigned){virtual}
+insert(in x:T&)
+deletes(in x:T&)
+deletes(in index:unsigned)+operator=(in arr:array<T>&): array<T>&+operator[](in i:unsigned):T&+operator<<(out stream:ostream&, in arr:array<T>&):ostream&
T
![Page 116: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/116.jpg)
3 定义类模板实参类 student :
student+name:string+sex:string+age:int+mark_average:float
+assign(in name:char*, in age:int, in mark_average:float, in sex:char*)+operator<(in stu:student&):bool+operator<=(in stu:student&):bool+operator>(in stu:student&):bool+operator>=(in stu:student&):bool+operator==(in stu:student&):bool+operator!=(in stu:student&):bool+operator<<(in output:ostream&, in stu:student&):ostream&
![Page 117: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/117.jpg)
通用数组类模板成员函数的算法分析作为课后练
习,模仿例 7-6 中的分析和描述方法,写出类模板中完
成各种主要操作的成员函数的算法。
![Page 118: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/118.jpg)
在 Java 中如果使用普通的 javac 编译器是无法编译带有参数化类型(例如, List<String> )的代码。但我们
可以使用 gjcr 编译命令来编译带有参数化类型代码的程序(详细内容参阅有关的 Java 书籍和资料)。 使用 Java 的参数化类型编程与 C++ 类似,并且更
为简单。例如:// LinkedListGeneric.java
interface Collection<T>
{
public void add(T x);
public Iterator<T> iterator();
}
![Page 119: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/119.jpg)
interface Iterator<T>
{
public T next();
public boolean hasNext();
}
class NoSuchElementException extends RuntimeException { }
class LinkedList<T> implements Collection<T>
{
protected class Node {
T item;
Node next = null;
Node(T item) { this.item = item; }
}
![Page 120: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/120.jpg)
protected Node head = null, tail = null;
public LinkedList() {}
public void add(T item) // 实现 Collection.add()
{
if(head==null) {head = new Node(item); tail = hea
d;}
else { tail.next = new Node(item); tail = tail.next; }
}
public Iterator<T> iterator() // 实现 Collection.iterator
()
{
return new Iterator<T>()
{
protected Node ptr = head;
![Page 121: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/121.jpg)
public boolean hasNext() // 实现 Iterator.hasNext()
{ return ptr != null; }
public T next() // 实现 Iterator.next()
{
if(ptr != null)
{
T item = ptr.item; ptr = ptr.next;
return item;
}
else thow new NoSuchElementException();
}
}
}
}
![Page 122: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/122.jpg)
class Test
{
public static void main(String[] args)
{
String str = "";
// 实例化一个整数列表 LinkedList<Integer> intList =
new LinkedList<Integer>();
intList.add(new Integer(0));
intList.add(new Integer(1));
intList.add(new Integer(2));
Iterator<Integer> int_it = intList.iterator();
while(int_it.hasNext())
str += int_it.next().intValue() + " ";
![Page 123: 第七章 类属和模板](https://reader038.vdocuments.site/reader038/viewer/2022102414/568154d3550346895dc2d34b/html5/thumbnails/123.jpg)
System.out.println(str); // 显示输出 0 1 2
// 实例化一个字符串列表 LinkedList<String> stringList =
new LinkedList<String>();
stringList.add("zero");
stringList.add ("one");
stringList.add ("two");
str = "";
Iterator<String> string_it = stringList.iterator();
while(string_it.hasNext())
str += string_it.next() + " ";
System.out.println(str); // 显示输出 zero one two
}
}