第七章 类属和模板

123

Click here to load reader

Upload: ally

Post on 27-Jan-2016

133 views

Category:

Documents


6 download

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 Presentation

TRANSCRIPT

Page 1: 第七章  类属和模板

第七章 类属和模板

Page 2: 第七章  类属和模板

在程序设计中,我们总会发现程序的某些组成模块所实现的逻辑功能是相同的,而不同的只是被处理对象(数据)的类型,例如下列函数模块: 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: 第七章  类属和模板

在 C++ 中,类属编程有两种实现方式:

⑴ 传统的采用创建类属数据结构的编程方式;

⑵ 采用 C++ 提供的类属工具 —— 参数化模板进行编

程的方式。

本章的重点是模板编程,但对传统类属编程的了解

将有助于对模板的理解。

Page 4: 第七章  类属和模板

本章要点1 类属编程

类属编程的必要性,类属表的编制和应用实例。

2 模板编程

模板编程的概念,函数模板与模板函数,类模板与模板类,类模板的派生。

3 利用模板工具实现类属数据容器的实例

栈,队列,数组。

Page 5: 第七章  类属和模板

7.1 类属7.1.1 为什麽要引入类属编程 为什麽要引入类属编程呢?可以通过一个实例来说

明。若有一个整数链表,可以将它定义成一个类,此类具有将整数插入链表、在链表中查找指定整数、从链表中删除指定整数等操作,类定义和使用如下:#include <iostream.h>

struct node { // 链表结点的数据结构int val; // 结点值node* next; // 结点链值

};

Page 6: 第七章  类属和模板

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: 第七章  类属和模板

intlist::~intlist()

{

node* temp; // 定义一个结点型指针用于指向被删结点

for (node* p = head;p;) // 循环删除链表中的所有结点

{

temp = p; // 另时指针指向当前结点

p = p->next; // 修改链表中的当前结点指针

delete temp; // 删除当前结点

}

}

Page 8: 第七章  类属和模板

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: 第七章  类属和模板

bool intlist::deletes(int x)

{

node* temp; // 定义临时指针用于指向被删结点

if (head->val == x) // 判别链头结点值是否等于指定值

{

temp = head->next; // 临时指针指向下一个结点

delete head; // 删除链头结点

size--; // 链表中结点个数减 1

head = temp; // 修改头指针指向下一个结点

return true; // 返回成功标志

}

Page 10: 第七章  类属和模板

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: 第七章  类属和模板

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: 第七章  类属和模板

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: 第七章  类属和模板

假设又需要设计一个相似的链表,所不同的是新链表要保存的数据是 double 类型的。显然,新链表除了

与整数链表中每一个结点的数据类型不同外,其余均完全相同。但即使如此,实现新链表的程序代码仍需要重写一遍,即在上述程序中所有出现 int 的地方,均

改为 double ,并将类名改为 dublist 。修改如下:struct node

{

double val;

node* next;

};

Page 14: 第七章  类属和模板

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: 第七章  类属和模板

在相应的函数定义中,也需要将所有 int 参变量改为

double 类型(具体修改不再重复)。

假如现在又需要定义一个 float 类型链表,则要另建

立一套适应 float 类型的新程序代码。按照这样的解决

方法,需要为每一种使用链表结构保存的预定义或用

户自定义类型对象定义适应相应类型的相同逻辑功能

的不同程序代码。显然,这样的方法不但使程序代码

的重用性差,而且维护起来非常困难。类属编程是解

决这类问题的好方法。

Page 16: 第七章  类属和模板

类属的概念来源于 ALGOL68 。在 Ada 语言中,类属

是它的一种最重要的核心编程概念,类属也就是软件

处理数据的类型参数化,它表现了一个软件元素能处

理多种类型数据的能力。类属的概念后来有了进一步

的扩展。

类属可分为无约束类属机制和约束类属机制,其中

无约束类属机制是指对类属参数没有施加任何特殊的

限制,而约束类属机制则意味着类属参数需要一定的

辅助条件。

Page 17: 第七章  类属和模板

7.1.2 类属表

与其它抽象数据类型一样,类属表是由一个值的集

合和此集合上的可执行操作关联在一起的数据类型。

不同之处在于该值集合的类型不确定,因此不能被预

先定义,只有用确定类型的数据表导出类属表时,值

集合才被建立。

在 C++ 中,要定义对一个数据表的操作时,必须预

先确定表的值集合,即必须确定值集合的类型。如何

来处理这一矛盾,实现一个类属表定义呢?

Page 18: 第七章  类属和模板

⑴ 定义一个类型不预先确定值集合的方法是将值集合

中的每个数据元素定义为字符类型指针,即可以用

动态创建的字节串表示值集合中将要存储的任意类

型数据。

⑵ 由导出类属表的具体数据值集合中的数据类型确定

动态创建字节串的长度。例如,一个整型数占两个

字节,则应该用长度为 2 的字节串来表示集合元素

值;又如一个浮点型数占四个字节,则可以长度为

4 的字节串来表示集合元素值。

Page 19: 第七章  类属和模板

⑶ 确定的类型数据与动态创建的字节串的关系是通过 共享相同的内存单元,而使用不同的类型表示和操 作实现的。而字节串长度是通过类属表值集合对象 被创建时所执行的类属表构造函数来传递的。 下面给出一个类属表的具体定义,分析程序的构成

和执行逻辑,可以加深理解类属表的实现机制和意义。程序代码如下:#include <iostream.h>

struct node { // 链表中结点的数据结构 node* next; // 结点的链域 char *contents; // 结点的值域(字符串型指针)};

Page 20: 第七章  类属和模板

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: 第七章  类属和模板

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: 第七章  类属和模板

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: 第七章  类属和模板

while(current != 0)

{ // 查找链尾 ( 循环结束时, previous 指向表的最后结点 )

previous = current;

current = current->next;

}

previous->next=newnode; // 尾结点的链域指向新结点

}

else

{ // 为空 head = newnode; // 加入新结点

}

}

Page 24: 第七章  类属和模板

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: 第七章  类属和模板

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: 第七章  类属和模板

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: 第七章  类属和模板

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: 第七章  类属和模板

在本程序中应注意下面几个问题:⑴ 结点的存储分配: 成员函数 insert 和 append 需要为结点 newnode

和结 点的数据值域 newnode->contents 动态分配内存。结 点数据值的大小被保存在私有数据成员 size 中。

⑵ 结点的数据内容的赋值: 根据 size 中的数据长度,逐字节传送。 ⑶ main 函数中类属对象的创建: 创建 list 类属表对象 my_list 时,须使用 sizeof(t

ype)

正确传递结点的数据的字节数,例如: sizeof(float) 。

Page 29: 第七章  类属和模板

⑷ 插入结点时的实参传递: 调用成员函数 insert 和 append 时,需要传递 c

har*

类型实参,这就是说,将要插入链表的数据强制转 换成 char 类型,并将数据内存地址作为实参值传递 给被调用的函数,例如: my_list.insert((char*)&r);

⑸ 输出时的类型转换: 将存储在链表结点值域中的 char 类型数据强制转换 成所需要的数据类型后输出,例如: r = (float)*(float*)my_list.get();

Page 30: 第七章  类属和模板

7.1.3 从类属表中导出栈和队列

类属表设计好以后,我们就可以利用继承机制由它

派生出一些其他类,例如,可以用它派生整数栈类和

整数队列类。在派生中需注意以下几个问题:

1 栈的特点是先进后出。

⑴ 将进和出的操作放在链表的表头进行;

⑵ 栈中的 push 操作可借用类属表中的 insert 函数;

⑶ 栈中的 pop 操作可借用类属表中的 get 函数。

Page 31: 第七章  类属和模板

2 队列的特点是先进先出。 ⑴ 将链表设计成一端插入,另一端取出。例如,链

表尾部插入,链表头部取出; ⑵ 队列的 put 操作可借用类属表中的 append

函数; ⑶ 队列的 get 操作可借用类属表中的 get 函数。3 类型的转换。 因为整数栈和队列实际上均是属性链表,为了使用 方便,在数据插入和提取时,其数据都已经确定是 整数,而不应是类属链表中的字符串;所以在调用 类属链表 list 中的成员函数时,不要忘记进行强制 类型转换。实现代码示意如下:

Page 32: 第七章  类属和模板

struct node

{

node* next;

char* contents;

};

class list

{

node* head;

int size;

public:

Page 33: 第七章  类属和模板

list(int s){ head = 0; size = s; }

void insert(char* a);

void append(char* a);

char* get();

void clear();

~list(){ clear(); }

};

Page 34: 第七章  类属和模板

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: 第七章  类属和模板

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: 第七章  类属和模板

7.2 模板编程7.2.1 模板的概念 模板是实现类属机制的一种工具,模板的功能非常强,既可以实现无约束类属机制,也可以实现约束类属机制。模板允许用户构造模板函数(类属函数),还允许用户构造模板类(类属类)。下图所示意的是类模板或函数模板、类、对象、函数之间的关系:

函数模板或类模板

模板函数

实例化

模板类

实例化

对象实例化

Page 37: 第七章  类属和模板

模板定义的一般形式:template < 模板形参表列 >

模板定义体其中:

① template :定义模板形参说明行的关键字,表示定义 一个模板的开始。② 模板形参表列:由若干模板形参组成的。每个模板 形参均是由关键字 class 和类型形参名组成。③ 模板定义体:函数模板的定义,或类模板的定义。注意,模板形参说明行与模板定义体之间不允许有任何其他语句。

Page 38: 第七章  类属和模板

7.2.2 函数模板与模板函数1 什麽是函数模板与模板函数 函数模板的声明格式如下: template <class type>

类型名 函数模板名 ( 参数表列 )

{ 函数模板定义体 }

⑴ type 表示模板形参名的一般形式,与说明一般函数形参名相同,可以是用户命名任何合法标识,只不过说明的是数据类型而不是数据值。

⑵ 函数模板的类型名和参数表列中参数类型名可以

是确定的预定义或自定义类型名,也可以是模板

形参名。

Page 39: 第七章  类属和模板

例如,将求两个数的最大值函数定义成函数模板: template <class T>

T max(T x, T y) { return (x > y)? x : y; }

如此定义的函数模板 max 代表的是一类函数。若要 使用函数模板 max 进行求最大值运算,必须首先将 模板形参 T 实例化为确定数据类型(如 int 、 flo

at 、 double 等)。 从这个意义上说:函数模板不是一个完全的函数, 将 T 实例化的对象类型称之为模板实参,实例化后 的函数模板称为模板函数。 一个使用函数模板的完整程序如下所示:

Page 40: 第七章  类属和模板

#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: 第七章  类属和模板

主函数中对函数模板 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: 第七章  类属和模板

#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: 第七章  类属和模板

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: 第七章  类属和模板

几点说明: ⑴ 在函数模板中允许使用多个类型参数。但应注意

每个模板形参前必须有关键字 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: 第七章  类属和模板

程序执行结果: 10 hao

0.123 10

⑵ template 语句与函数模板定义语句之间不允许有

任何其他语句,例如:

template <class T>

int i; // 错误,不允许有别的语句

T Max(T x, T y) { return (x > y) ? x : y; }

Page 46: 第七章  类属和模板

⑶ 模板函数与重载函数比较:

函数重载需要通过多个函数重载版本来实现,每

个函数版本可以执行不同的操作。

函数模板是通过模板形参实例化为不同类型数据

提供操作的摸板函数版本,但所有模板函数都必

须执行相同的操作逻辑。因此,下面的重载函数

就不能用模板函数代替。

void outdata(int i) { cout << i; }

void outdata(double d) { cout << "d = " << d << endl;

}

Page 47: 第七章  类属和模板

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: 第七章  类属和模板

分析出现错误的原因是:函数模板被调用时,编译

器按最先遇到的实参类型隐含生成一个模板函数,

然后用该模板函数对以后出现的所有模板实参进行

一致性检查。例如,对语句 Max(i, c) ,编译器先按

照实参 i 将模板形参 T 实例化为 int 类型,隐含生成

模板函数 Max(int, int) ,然后用该模板函数检查此后

出现的模板实参,由于第二个模板实参 c 的类型与

模板函数的第二个参数类型 int 不符,便发生错误。

解决这个问题有两种方法:

Page 49: 第七章  类属和模板

⑴ 采用强制类型转换, 例如,将调用语句 Max(i, c);

改写为: Max(i, int(c));

⑵ 重载函数扩充函数模板,这种重载有两种表达式: ① 只声明一个函数的原型,而不给出定义体,它的

定义体是借用函数模板的定义体。当执行此重载

版本时,会自动调用函数模板的定义体。例如,可将上面的程序改写如下:

Page 50: 第七章  类属和模板

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: 第七章  类属和模板

虽然该重载函数借用了函数模板的定义体,但它

支持数据类型之间的隐式转换。例如: Max(i,

c)

和 Max(c, i) 就是通过重载函数 Max(int, int) 完成了

类型的隐式转换,避免了错误发生。注意,多数

C++ 编译器不支持这种方法,例如 Visual C++ 。

Page 52: 第七章  类属和模板

② 定义一个完整的重载函数,函数所带的参数类型

可以随意。例如:char* Max(char* x, char* y)

{ return (strcmp(x,y) > 0)? x : y; }

该函数实际上是为上述函数模板所能实例化的模

板函数增加了一个新的重载函数版本,当出现调

用语句 Max(“abcd”, “efgh”); 时,执行的是这个重载

函数。

在 C++ 中,函数模板与同名的重载函数的调用顺序

遵循下述约定:

Page 53: 第七章  类属和模板

⑴ 查询一个参数完全匹配的函数,如果找到了,则查 询结束并调用查询到的函数,否则继续查询。

⑵ 查询一个函数模板,将其实例化,产生一个匹配的 模板函数,如果找到了,则查询结束并调用实例化 后的函数模板,否则继续查询。⑶ 若 ⑴ 和 ⑵ 都失败,再尝试对函数的其他的匹配, 例如,通过类型转换实现参数匹配等,如果成功, 则调用匹配成功的函数。如果 ⑴ ⑵ ⑶ 均未找到匹 配的函数,则是一个失配性调用错误。如果在 ⑴ 中匹配多于一个,则导致二义性调用错误。

Page 54: 第七章  类属和模板

7.2.3 类模板与模板类1 类模板与模板类的概念 ⑴ 什麽是类模板: 如果有一族类的结构和行为逻辑都是相同的,不

同之处仅在类的全部或部分数据成员的类型不同。则可以定义一个有确定的结构和行为逻辑,而某些数据成员的类型不确定,某些成员函数

的参数、返回 类型不确定的 “类”,即类模板。

与函数模板相同,类模板定义中未确定的数据类

型是由模板形参说明行声明的。只有当模板形参

被确定的类型实参替代,类模板才被实例化一个

完全确定的类。

Page 55: 第七章  类属和模板

⑵ 类模板的定义:

定义一个类模板,一般包括两方面的内容:

① 首先要定义类模板,其一般形式为:

template <class T>

class 类模板名

{ 类数据成员和成员函数定义代码 };

在类模板定义中凡采用模板形参类型声明的

数据成员,成员函数参数和返回类型均可以

使用 T , T* 或 T& 等进行类型声明,例如:

Page 56: 第七章  类属和模板

template <class T>

class vector

{

T* data;

int size;

public:

vector(int);

T& operator[] (int);

};

Page 57: 第七章  类属和模板

② 在类定义体外定义成员函数的实现代码时, 若所定义的成员函数的形参数据、返回

数据 或局部变量数据中包含了模板参数类型,

则 必须在函数体之前进行模板声明,并且

在函 数名所属的类模板名后加缀模板形参名。 例如: template <class T>

vector<T>::vector(int i)

{ … };

template <class T>

T& vector<T>::operator[] (int i)

{ … }

Page 58: 第七章  类属和模板

⑶ 类模板的使用:

类模板的使用实际上是将类模板实例化成一个具

体的类或类对象,调用格式如下:

类模板名 < 实参类型 > 实例化对象名 ;

typedef 类模板名 < 实参类型 > 实例化类名 ;

例如:

template <class T>

class vector { … };

Page 59: 第七章  类属和模板

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: 第七章  类属和模板

这些实例化的类模板:

vector<int> , vector<complex> 和 vector<myclas

s> 被

称为模板类。模板类是类模板实例化的产物。类

模板和模板类之间的关系如下图所示。类模板

vector<T>

模板类vector<myclass>

myclass实例化

模板类vector<complex>

complex实例化

模板类vector<int>

int实例化

Page 61: 第七章  类属和模板

例例 7-17-1 给出一个使用类模板 stack 的完整实例,在

此例中建立了字符型堆栈 cs1 、 cs2 和整型

堆栈 is1 、 is2 。

堆栈类模板 stack 与堆栈 cs1 、 cs2 、 is1 和 is2 之

间的关系如下图所示:

Page 62: 第七章  类属和模板

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: 第七章  类属和模板

使用类模板的几点说明:

⑴ 在每个类模板定义之前,都需要在前面加上模板形

参说明行, 例如:

template <class Type>

使用类模板时,必须在类模板名字后加缀模板形参

名用于表示类模板,或者加缀模板实参名用于表示

模板类。例如:

stack<Type> 表示一个堆栈类模板

stack<int> 表示一个整型堆栈类

Page 64: 第七章  类属和模板

⑵ 模板类可以有多个参数,例如: #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: 第七章  类属和模板

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: 第七章  类属和模板

⑶ 在使用类模板的源程序中必须包含类模板的完全定 义,即不仅要包含类模板的定义代码,还要包含类 模板的实现代码。因此,如果类模板的完全定义分 写在头文件 .h 和实现文件 .cpp 两个文件中,则在使 用类模板的源程序中就必须包含能体现类模板完全 定义的实现文件 .cpp ,而不能只包含仅体现类模板 定义的 .h 。例如:程序中有一个类模板 vector ,其 定义代码在 vector.h 中,实现代码在 vector.cpp

中, 则在使用类模板 vector 的程序源文件中使用的包含 应该是: #include “vector.cpp” 而不应是 #include "vector.h" 。

Page 67: 第七章  类属和模板

2 类模板使用举例例例 7-27-2 定义一个简单队列类模板,并将该模板实例化一

个对自定义职员类信息进行存储和操作的队列。问题分析: 简单队列是最有代表性的一种队列是一种具有 “先 进先出” 操作机制的数据容器。队列中存放的数 据,可以是预定义或自定义的任何类型。 本例中采用了链表队列模板。首先定义一个结构模 板 quenode 作为队列的结点,它与定义类模板方法 类似,再定义一个队列类模板 queue ,结构模板和 类模板取同样的模板形参名 T 。

Page 68: 第七章  类属和模板

另外定义了一个职员类 staff ,并将队列模板 que

ue

实例化为 staff 类型的队列 staffque 。

结构模板 quenode 、队列类模板 queue 、职员类 staff

和实例化后的职员信息队列对象 staffque 之间的关

系如下图所示:

Page 69: 第七章  类属和模板

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: 第七章  类属和模板

3 类模板的派生 ⑴ 从类模板派生类模板

与类派生相同,类模板也可以派生出新的类模板。注意,基类模板名必须是未实例化的模板名。类模板派生的一般形式:template < 模板形参表列 >

class 派生类模板名: 继承方式 基类模板名 < 模板形参表列

>

{ 派生类模板新增成员定义代码 }; 例如:template <T>

class derived : public base<T>

{ … };

Page 71: 第七章  类属和模板

例例 7-47-4 设计一个链表类模板 list ,并从 list 派生一

个集合类模板 set 。⑴ 由于集合和链表的差别在于集合中的元素

不 允许重复。因此插入操作时要判别集合中

是否 已有要插入的元素。因此,链表类模板 l

ist 的 插入操作 insert 应声明为虚函数,并在

集合类 模板 set 中重新定义;

⑵ list 中的其余成员函数均不需要再重新定义。list 和 set 之间的关系如下:

listT

setT

Page 72: 第七章  类属和模板

⑵ 从类模板派生模板类 从模板类派生模板类,必须使用类模板的实例作

为派生模板类的基类。由于模板形参已经被确定

的类型实参替代,因此在模板类派生定义之前不

需要模板形参声明。模板类派生的一般形式:class 派生类名

: 派生方式 基类模板名 < 模板实参表列>

{ 派生类新增成员定义代码 }; 例如:class derived : public base<int>

{ … };

Page 73: 第七章  类属和模板

例例 7-57-5设计一个链表类模板 list ,并从 list 派生一

个整数集合类 intset 。

返回

listT

intset

<bind>int

Page 74: 第七章  类属和模板

7.3 利用模板工具实现类属数据容器的实例7.3.1 栈 栈是一种线性表数据结构,它的特点是:① 操作在线性表的一端按 "先进后出 " ( FILO )的原则 进行。② 栈中的元素可以是任何类型(预定义类型和自定义 类型)。③ 实现线性表的存储结构可以是数组(矩阵),也可 以是链表。

显然,一个通用栈的实现应该在归纳共性的基础上:

Page 75: 第七章  类属和模板

① 定义抽象通用栈类模板 genabstractstack 以便实现不

同存储结构的栈的统一操作接口。

② 从抽象栈类模板派生定义针对某种确定存储结构的

通用栈类模板 genstack 以便适应不同类型栈元素的

操作需要。

③ 用通用栈类模板 genstack 定义确定类型的栈类或实

例。例如,栈元素为学生信息类 student 对象的堆栈

实例 studentstack 。

Page 76: 第七章  类属和模板

类层次结构图:

genabstractstackT

genstackT

intstack:

<bind>int

studentstack:

<bind>student

student

1..* nodedata

genstackrecT

top

1

Page 77: 第七章  类属和模板

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: 第七章  类属和模板

⑴ 属性:① 栈高 height , unsigned 类型变量,该数

据成员 应该被隐藏(不允许类外访问),但应能

在派 生类中被派生类新增成员函数访问,所以

将该 数据成员的访问属性设定为 protected 。

⑵ 操作:① 判空操作 isempty() ,该操作与存储结构无

关, 因此,可以在模板定义中实现具体定义。

返回值: bool 类型, true 表示栈空, false 表示

栈非空。 算法:

Page 79: 第七章  类属和模板

isempty()

BEGIN

if ( 栈高为 0 ) then return true

else return false

endif

END

② 压栈操作 push(T&) ,该操作与存储结构有关,

因此在模板定义中不能确定操作实现,故声明

为纯虚函数。 参数:模板形参的引用。

Page 80: 第七章  类属和模板

③ 弹栈操作 pop(T&) ,该操作与存储结构有关,

因此在模板定义中不能确定操作实现,故声明

为纯虚函数。

参数:模板形参的引用。

返回值: bool 类型, true 表示成功, false 表示

失败。

④ 上滚操作 rollup() ,该操作与存储结构有关,

因此在模板定义中不能确定操作实现,故声明

为纯虚函数。无参数且无返回。

Page 81: 第七章  类属和模板

⑤ 下滚操作 rolldown() ,该操作与存储结构有

关,因此在模板定义中不能确定操作实现,故

声明为纯虚函数。无参数且无返回。

⑥ 清除操作 clear() ,该操作与存储结构有关,因

此在模板定义中不能确定操作实现,故声明

为纯虚函数。无参数且无返回。

⑦ 取栈底元素操作 deque(T&) ,该操作虽然也与

存储结构有关,但可以通过调用成员函数

rolldown 和 pop 完成,可以定义操作实现。

Page 82: 第七章  类属和模板

参数:模板形参的引用。

返回值: bool 类型, true 表示成功, false 表示

失败。

算法:

deque([OUT] T& x)

BEGIN

rolldown();

pop(x);

END

Page 83: 第七章  类属和模板

2 建立通用栈类模板 genstack

首先确定保存栈元素的存储结构模板 genstackrec , 然后从 genabstractstack 派生 genstack 。 以链表存储结构为例, genstack 除了继承基类中已 经定义的属性和操作外,必须进行以下定义: · 对基类定义的纯虚函数进行实现定义。 · 新增属性 top 用于指示栈顶、 allocateerror 用于表 示存储分配状态。 · 新增成员函数 copy 完成堆栈复制、 getallocateerro

r

获取存储分配状态和运算符 operator = 。 类模板图描述如下:

Page 84: 第七章  类属和模板

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: 第七章  类属和模板

⑴ 属性:① 栈顶指针 top ,存储结构模板 genstackrec

类 型,该数据成员应该被隐藏(不允许类外

访 问),但应能在派生类中被派生类新增成

员函 数访问,所以将该数据成员的访问属性设

定为 protected 。② 结点存储分配标志 allocateerror , bool 类

型, 表示结点的存储空间分配是否成功。该数

据成 员应该被隐藏(不允许类外访问),但应

能在 派生类中被派生类新增成员函数访问,所

以将 该数据成员的访问属性设定为 protected 。

Page 86: 第七章  类属和模板

⑵ 操作:

① 栈复制操作 copy(genstack&) ,该操作只用于内

部操作,因此访问属性可以设定为 protected

或 private 。

参数:栈类模板实例的引用。

返回值:栈类模板实例的引用。

算法:

Page 87: 第七章  类属和模板

copy([IN] genstack& g)

BEGIN

if ( 目标栈不为空) then clear() endif

初始化目标栈并从源栈复制栈高 if ( 源栈为空) then return 空目标栈 endif

分配新结点作为目标栈栈顶结点 从源栈复制栈顶结点 while ( 未到源栈底)

分配新结点作为目标栈中下一个结点 ;

从源栈复制中下一个结点 ;

endwhile

return 目标栈 ;

END

Page 88: 第七章  类属和模板

② 栈类构造函数 genstack() 。

③ 栈类拷贝构造函数 genstack(genstack&) 。

参数:栈类模板实例引用。

算法:

genstack([IN] genstack& g)

BEGIN

目标栈栈顶指针置空 ;

copy(g);

END

Page 89: 第七章  类属和模板

④ 获取结点存储分配标志 getallocateerror() 。

返回值: bool 类型, true 分配成功, false 分配

失败。

算法:直接返回存储分配标志 allocateerr

or 。

⑤ 重新定义基类中的纯虚函数 clear() 。

算法:顺序弹出栈中的所有结点。

⑥ 重新定义基类中纯虚函数 push(T& x) 。

参数:模板形参类型引用。

算法:

Page 90: 第七章  类属和模板

push ([IN]T& x)

BEGIN

if ( 栈不为空 ) then if (分配新结点失败)

then 置存储分配错误标志并结束操作 else 为新结点赋值并压入堆栈

修改栈顶指针 endif

else if (分配栈顶结点失败) then 置存储分配错误标志并结束操作 else 新结点赋值并使栈顶指针指向新结

点 endif

endif

END

Page 91: 第七章  类属和模板

⑦ 定义基类中纯虚函数 pop(T& x) 。 参数:模板形参类型引用。 返回值: bool 类型, true 操作成

功, false 操作失败。

算法: pop([OUT] T& x)

BEGIN

if ( 栈中有元素) then x = 栈顶结点的值

删除栈顶结点并修改相应属性 return true

else return false

endif

END

Page 92: 第七章  类属和模板

⑧ 定义基类中纯虚函数 rollup() 。 算法: rollup()

BEGIN

if ( 栈为空或者栈中元素个数 < 2 )then 结束操作else 将栈顶结点从链表中分离

修改栈顶指针指向次栈顶结点 循环找到栈底结点 将分离的栈顶结点连接在栈底之后

endif

END

Page 93: 第七章  类属和模板

⑨ 定义基类中纯虚函数 rolldown() 。 算法: rolldown()

BEGIN

if ( 栈为空或者栈中元素个数 < 2 ) then 结束操作 else 循环找到栈底结点

将栈底结点从链表中分离 将分离的栈底结点连接在栈顶之前 修改栈顶指针指向原栈底结点

endif

END

Page 94: 第七章  类属和模板

⑩ 重载赋值运算符成员函数 operator= 。

参数:栈类模板实例引用。

返回值:栈类模板实例引用。

算法:调用栈复制成员函数 copy 。 operator=([IN] gestack& g)

BEGIN

copy(g)

return *this

END

Page 95: 第七章  类属和模板

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: 第七章  类属和模板

4 使用栈模板 使用预定义类型或自定义类型将栈类模板中模板参 数变为实参,方法有两种: ⑴ 实例化为模板类,例如,

typedef genstack<int> intstack;

⑵ 实例化为类对象,例如,genstack<int> intstack;

在本例中的使用: ⑴ 用 student 类将栈类模板实例化为栈类对象。 ⑵ 对 student 类栈对象进行各种操作; ⑶ 在一个全程函数 viewstack 中引用 student 类栈对

象,用于顺序显示栈中所有的 student 信息。

Page 97: 第七章  类属和模板

7.3.2 队列 队列也是一种线性表,它的特点:① 操作在线性表的两端按 "先进先出 " ( FIFO )的原则 进行;② 根据操作的差别队列可以分为: 简单队列:数据从队尾插入,队头弹出。 双端队列:数据从队头和队尾都可以插入和弹出。 优先队列:数据根据其优先级可以插入到队列的不

同位置。③ 队列中的元素可以是预定义和自定义的任何类型。④ 实现线性表的存储结构可以是数组(矩阵),也可 以是链表。

Page 98: 第七章  类属和模板

显然通用队列的实现应该在归纳共性的基础上:① 定义抽象通用队列类模板 abque 以便实现能适用于 不同存储结构的队列的统一操作接口。② 从抽象通用队列类模板派生定义用于某种确定存储 结构的各种通用队列类模板: 简单队列通用类模板 —— queue1 , 双端队列通用类模板 —— queue2 , 优先队列通用类模板 —— queue3 , 以适应不同类型队列种类和队列元素的需要;③ 使用各种队列通用类模板定义确定类型的队列类或 实例。例如,学生信息类 student 对象的队列实例 studentqueue 。

Page 99: 第七章  类属和模板

类层次结构图

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: 第七章  类属和模板

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: 第七章  类属和模板

2 定义简单队列通用类模板 queue1

首先确定链表结点存储结构模板 quenode ,然后从 abque 派生 queue1 。 以链表存储结构为例,简单队列通用类模板除了继 承基类中已经定义的属性和操作外,必须进行如下 的定义工作: · 对基类定义的纯虚函数进行实现定义。 · 新增属性 head 指示队列头、 tail 指示队列尾和 allocateerror 表示队列元素结点的内存分配状态。 · 新增操作 copy 实现队列复制、 getallocateerror 获 取队列元素结点的内存分配状态、 put 队列插入 操作、 get 队列提取操作和运算符 operator = 。 类模板图描述如下:

Page 102: 第七章  类属和模板

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: 第七章  类属和模板

3 定义双端队列通用类模板 queue2

从抽象通用队列类模板 abque 派生双端队列通用类 模板 queue2 。 以链表存储结构为例,双端队列通用类模板除了继 承基类中已经定义的属性和操作外,必须进行如下 的定义工作: · 对基类中的全部虚函数进行实现和重载定义。 · 新增属性 head 指示队列头、 tail 指示队列尾和 allocateerror 表示队列元素结点的内存分配状态。 · 新增操作 copy 实现队列复制、 getallocateerror 获 取队列元素结点的内存分配状态、 put 队列插入 操作、 get 队列提取操作和运算符 operator = 。 类模板图描述如下:

Page 104: 第七章  类属和模板

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: 第七章  类属和模板

4 定义优先队列通用类模板 queue3

从抽象通用队列类模板 abque 派生优先队列通用类 模板 queue3 。 以链表存储结构为例,优先队列通用类模板除了继 承基类中已经定义的属性和操作外,必须进行如下 的定义工作: · 对基类中的纯虚函数进行实现定义。 · 新增属性 head 指示队列头、 tail 指示队列尾和 allocateerror 表示队列元素结点的内存分配状态。 · 新增操作 copy 实现队列复制、 getallocateerror 获 取队列元素结点的内存分配状态、 put 队列插入 操作、 get 队列提取操作和运算符 operator = 。 类模板图描述如下:

Page 106: 第七章  类属和模板

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: 第七章  类属和模板

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: 第七章  类属和模板

7.3.3 数组 数组是一个分布在连续内存空间上线性表,因此它

最大的优点是可以高效地索引存取表元素,而与表的

大小无关。但使用预定义数组机制定义数组的类型和

大小必须预先确定,并且不能改变。

这里讨论的数组类模板,在定义时并不确定它所包

含元素的类型,因此它的内存大小也只能在数组元素

类型被实例化之后才能被分配,但必须保持数组的区

域连续性特点。

Page 109: 第七章  类属和模板

对于一个数组类模板还应具备下列特定操作:

① 按照指定大小进行动态空间的再分配;

② 交换两个指定下标数组元素;

③ 数组中的元素排序;

④ 数组中的元素反序;

⑤ 向数组插入一个指定元素;

⑥ 从数组取出一个指定元素;

⑦ 删除一个指定下标数组元素

⑧ 在数组中查找一个指定元素。

Page 110: 第七章  类属和模板

通用数组类模板的实现应该在归纳共性的基础上:

① 定义抽象通用数组类模板 abarray 以便实现适应不同

存储结构的动态数组的统一操作接口;

② 从抽象通用数组类模板 abarray 派生定义通用数组类

模板 array 以适应某种确定的存储结构,而元素类型

不确定的数组的需要;

③ 使用通用数组类模板 array 定义确定元素类型的数组

类或实例。例如,学生信息类 student 对象的数组实

例 studentarray 。

Page 111: 第七章  类属和模板

类层次结构图:

abarrayT

arrayT

intarray:

<bind>int

studentarray:

<bind>student

student

1..* *dataptr

Page 112: 第七章  类属和模板

1 建立抽象通用数组类模板 abarray

原则:按照需求抽象出数组操作的统一操作接口

(方法)规则,其中与存储结构有关的方法声明纯

虚方法。

模板类图描述如下:

Page 113: 第七章  类属和模板

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: 第七章  类属和模板

2 定义通用数组类模板 array

从抽象通用数组类模板 abarray 派生通用数组类模 板 array 。通用数组类模板除了继承基类中已经定义 的属性和操作外,必须进行以下定义工作: · 对基类定义的纯虚函数进行实现定义。 · 新增属性 dataptr 指示动态数组空间。 · 新增操作 copy 实现数组复制、 insert 向数组插入 指定数据、 deletes 删除数组元素和重载运算符 operator = 、 operator[] 、 operator<< 。 类模板图描述如下:

Page 115: 第七章  类属和模板

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: 第七章  类属和模板

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: 第七章  类属和模板

通用数组类模板成员函数的算法分析作为课后练

习,模仿例 7-6 中的分析和描述方法,写出类模板中完

成各种主要操作的成员函数的算法。

Page 118: 第七章  类属和模板

在 Java 中如果使用普通的 javac 编译器是无法编译带有参数化类型(例如, List<String> )的代码。但我们

可以使用 gjcr 编译命令来编译带有参数化类型代码的程序(详细内容参阅有关的 Java 书籍和资料)。 使用 Java 的参数化类型编程与 C++ 类似,并且更

为简单。例如:// LinkedListGeneric.java

interface Collection<T>

{

public void add(T x);

public Iterator<T> iterator();

}

Page 119: 第七章  类属和模板

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: 第七章  类属和模板

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: 第七章  类属和模板

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: 第七章  类属和模板

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: 第七章  类属和模板

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

}

}