C Primer Plus之高级数据表示
抽象数据类型(ADT)
类型是由什么组成?一个类型(type)指定两类信息:一个属性集和一个操作集。
所以您想定义一个新的数据类型。首先,您需要提供存储数据的方式,可能是通过设计一个结构。第二个,需要提供操作数据的方式。
计算机科学已经研究出一种定义新类型的成功方法。这种方法使用3个步骤来完成从抽象到具体的过程:
- 为类型的属性和可对类型执行的操作提供一个抽象的描述。这个描述不应受任何特定实现的约束,甚至不应受到任何特定编程语言的约束。这样一种正式的抽象描述被称为抽象数据类型(ADT)
- 开发一个实现该ADT的编程接口(即函数集合)。即说明如何存储数据并描述用于执行所需操作的函数集合。比如,在C中,您可能同时提供一个结构的定义和用来操作该结构 的函数的原型。
- 编写代码来实现这个接口。这一步至关重要,但是使用这种新类型的程序员无须了解实现的细节。
以抽象数据类型——列表为例进行说明
类型名称: | 列表 |
类型属性: | 可保存一个项目序列 |
类型操作: | 把列表初始化为空列表 |
确定列表是否为空 | |
确定列表是否已满 | |
确定列表中项目的个数 | |
向列表末尾添加项目 | |
遍历列表,处理列表中每个项目 | |
清空列表 | |
在列表中的任何位置插入一个项目 | |
从列表中删除一个项目 | |
取出列表中的一个项目(不改变列表) | |
替换列表中的一个项目 | |
在列表中搜索一个项目 |
非正式但抽象的列表定义时:它是一个能够保存项目序列并且可以对其应用任何前面的操作的数据对象。这个定义没有说明什么样的项目才能存储在列表中。它并未指定是否应该使用数组或链接的结构集或其他数据形式来保存这些项目。它并未指定使用何种方法来实现诸如获取列表中的元素个数之类的操作。这些都是留给实现的细节。
构造C语言接口
简单列表的接口有两个部分:①描述数据如何表示,②描述实现ADT操作的函数。接口的设计应和ADT的描述尽可能密切地保持一致。因此,应该使用某种通用的Item类型来进行表达,而不是用诸如int或struct film之类的专用类型。这样做的方法之一是使用C的typedef工具将Item定义为所需类型。如:
#define TSIZE 45 /* 存放片名的数组大小 */ struct film { char title[TSIZE]; int rating; }; typedef struct film Item;
在链表的实现中,每一个链接被称为一个节点(node)。每一个节点包含形成列表内容的信息和指向下一节点的指针。例如:
typedef struct node { Item item; struct node * next; // 指向下一节点的指针 } Node; typedef Node * List;
↓ 等价于
struct node { Item item; struct node * next; }; typedef struct node Node; typedef Node * List; // 为了管理链表,需要一个指向其开始处的指针
还可以加入一个变量来保存列表中项目的数量:
typedef struct list { Node * head; // 指向列表头的指针 int size; // 列表中项目的数量 } List;
考虑如下声明:
List movies;
movies是在建立一个列表,而不是在建立一个指向节点的指针或是建立一个结构。movies的确切数据表示是应该在接口层上不可见的实现细节上。
启动后,程序应该把头指针初始化为NULL。任何使用List类型的人都应无须担心细节,而应能够使用下面的代码:
InitializeList(movies);
程序员只需要知道他们应该使用InitializeList()函数来初始化列表,不需要知道List变量的确切的数据实现。这是数据隐藏的一个例子。数据隐藏是一种对跟高级编程隐藏数据表示细节的艺术。InitializeList()的函数原型为:
/* 操作: 初始化一个列表 */ /* 操作前:plist指向一个列表 */ /* 操作后:该列表被初始化为空列表 */ void InitializeList(List * plist);
C语言把所有的类型和函数信息集成到一个包中的方法是:将类型定义和函数原型(包括“操作前”和“操作后”注释)放入一个头文件中。这个文件将提供程序员使用该类型所需的全部信息。
单链表的实现与分析(以C Primer Plus(第5版) 第17章 高级数据表示——程序清单17.4为例)
抽象数据类型的头文件(list.h):定义数据结构并为用户接口提供原型
/* 简单列表类型的头文件 */ #ifndef LIST_H_ #define LIST_H_ #include <stdbool.h> /* 特定于程序的声明 */ #define TSIZE 45 struct film { char title[TSIZE]; int rating; }; /* 一般类型定义 */ typedef struct film Item; typedef struct node { Item item; struct node * next; } Node; typedef Node * List; // 指向Node(struct node)类型的指针 /* 函数原型 */ /* 操作:初始化一个列表 */ /* 操作前:plist指向一个列表 */ /* 操作后:该列表被初始化为空列表 */ void InitializeList(List * plist); /* 操作:确定列表是否为空列表 */ /* 操作前:plist指向一个已初始化的列表 */ /* 操作后:如果该列表为空则返回true:否则返回true */ bool ListIsEmpty(const List plist); /* 操作:确定列表是否已满 */ /* 操作前:plist指向一个已初始化的列表 */ /* 操作后:如果该列表已满则返回true:否则返回true */ bool ListIsFull(const List * plist); /* 操作:确定列表中项目的个数 */ /* 操作前:plist指向一个已初始化的列表 */ /* 操作后:返回该列表中项目的个数 */ unsigned int ListItemCount(const List * plist); /* 操作:在列表尾部添加一个项目 */ /* 操作前:item是要被增加到列表的项目 */ /* plist指向一个已初始化的列表 */ /* 操作后:如果可能的话,在列表尾部添加一个新项目, */ /* 函数返回true:否则返回false */ bool AddItem(Item item, List * plist); /* 操作:把一个函数作用于列表中的每个项目 */ /* 操作前:plist指向一个已初始化的列表 */ /* pfun指向一个函数,该函数接受 */ /* 一个Item参数并且无返回值 */ /* 操作后:pfun指向的函数被作用于列表中的每个项目一次 */ void Traverse(const List * plist, void (*pfun)(Item item)); /* 释放以分配的内存(如果有) */ /* 操作前:plist指向一个已初始化的列表 */ /* 操作后:为该列表分配的内存已被释放 */ /* 并且该列表被置为空列表 */ void EmptyTheList(List * plist); #endif // LIST_H_
提供函数代码以实现接口
#include <stdio.h> #include <stdlib.h> #include "list.h" // 把一个项目复制到一个节点中 static void CopyToNode(Item item, Node * pnode); void InitializeList(List * plist) { *plist = NULL; } bool ListIsEmpty(const List plist) { if(plist == NULL) return true; else return false; } bool ListIsFull(const List * plist) { Node * pt; bool full; pt = (Node *)malloc(sizeof(Node)); if(pt == NULL) full = true; else full = false; free(pt); return full; } unsigned int ListItemCount(const List * plist) { unsigned int count = 0; Node * pnode = *plist; while(pnode != NULL) { ++count; pnode = pnode->next; } return count; } // 较慢的实现方法 bool AddItem(Item item, List * plist) { Node * pnew; Node * scan = *plist; pnew = (Node *)malloc(sizeof(Node)); if(pnew == NULL) return false; CopyToNode(item, pnew); // pnew->item = item; pnew->next = NULL; if(scan == NULL) *plist = pnew; else { while(scan->next != NULL) scan = scan->next; scan->next = pnew; } return true; } void Traverse(const List * plist, void (*pfun)(Item item)) { Node * pnode = *plist; while(pnode != NULL) { (*pfun)(pnode->item); pnode = pnode->next; } } void EmptyTheList(List * plist) { Node * psave; while(*plist != NULL) { psave = (*plist)->next; free(*plist); *plist = psave; } } static void CopyToNode(Item item, Node * pnode) { pnode->item = item; }
将列表接口应用于具体编程问题的源代码文件
#include <stdio.h> #include <stdlib.h> #include "list.h" void showmovies(Item item); int main(void) { List movies; // 指向链表的指针 Item temp; // 初始化一个列表 InitializeList(&movies); // 确定列表是否已满 if(ListIsFull(&movies)) { fprintf(stderr, "No memory available! Bye!\n"); exit(1); } // 收集并存储 puts("Enter first movie title: "); while(gets(temp.title) != NULL && temp.title[0] != '\0') { puts("Enter your rating <0-10>: "); scanf("%d", &temp.rating); while(getchar() != '\n') continue; // 在列表尾部添加一个项目 if(AddItem(temp, &movies) == false) { fprintf(stderr, "Problem allocating memory\n"); break; } // 每添加一个项目,确定列表是否已满 if(ListIsFull(&movies)) { puts("The list is now full."); break; } puts("Enter next movies title (empty line to stop): "); } // 显示 if(ListIsEmpty(movies)) printf("No data entered."); else { printf("Here is the movie list: \n"); Traverse(&movies, showmovies); } printf("You entered %d movies.\n", ListItemCount(&movies)); // 清除 EmptyTheList(&movies); printf("Bye!\n"); return 0; } void showmovies(Item item) { printf("Movie: %s Rating: %d\n", item.title, item.rating); }