C Primer+Plus(十七)高级数据表示(一)
第十七章 高级数据表示(一)
一、抽象数据类型
抽象数据类型(abstract data type:ADT)是指由用户依据实际需求所创建的某种数据类型,它可以是C语言中的任何数据类型,甚至是基本类型,或数组,复杂的就会用到结构。
为何说是抽象?是因为ADT并不会是固定某种数据类型,而是依据实际应用需求中提炼出来的某种数据类型的表达方式。
那么,如何定义一种ADT呢?
在C语言里,定义一种数据类型,包括两方面:一方面是数据存储方式的描述(对取值的限定),另外一方面是对该类型的可使用的操作的定义。
比如:定义一个int型数据a,则表示a取值范围是-32768~32767中的整数;同时a可以使用所有对整型数据的操作。
依据上述,创建一个ADT,包括以下两大方面:1、描述该ADT的存储方式;2、定义其可使用的操作。
具体可分为一下两个步骤:
1、定义数据类型和并描述该数据类型可进行的操作。
//假设ADT为typea typedef something typea; //类型定义 //可使用的操作集定义 //操作1定义 //操作1描述 void f1(typea *a); //操作2定义 //操作2描述 void f2(typea *a); ....
2、定义操作(编写代码实现)。
对于上例即是编写代码实现f1和f2函数功能。
二、利用ADT来实现一个关于电影列表的工程
1、数据的定义和描述
为简化代码,设置每个电影项目有两个成员:影片名称和打分。则对于每个项目的定义描述如下:
struct film { char name[20]; int rating; };
接下来要做的工作是,如何将所有项目链接组合起来,或者叫“形成一个数据堆”?可以通过数组的方式:
struct film filmpro[100];
数组的实现有其优势:可随机访问,但也有其缺点:先前就限定了数据堆的空间大小,可能造成浪费也可能空间不足(数组空间的分配是在编译时候就进行了),同时若要向数组中间添加新项目,则会使其后面的数组元素都进行调整(除非数组非满且在最后添加)。
因此这里我们选用单链表方式去实现这个“数据堆”,首先描述这个“数据堆”中的每一个元素,成为“节点”:
struct node { struct film filmpro; struct node *pnode; };
接下来描述这个数据堆,该如何描述?本例中数据堆是一个单链表形式,那么要形容这个数据堆,必须描述其起始的位置,即首节点的地址。
struct node *list;
当然,依据实际需求,也可以增加其他成员,比如增加描述这个数据堆的项目数量:
struct list { struct node *plist; int node_number; };
那么至此,一个完整对该电影工程的抽象数据定义如下:
struct film { char name[20]; int rating; }; struct node { struct film filmpro; struct node *pnode; }; struct node *list;
这里要明确理解这三个定义之间的关系,首先film是依据实际应用提炼出的数据的定义表示;再次在这个数据定义表示之后,要用一种方式将所有的数据组合成一个“数据堆”,那要能表示这数据堆中每一个元素,而这时这每一个元素不仅仅是先前的film数据,它要能包含film,同时又能表示其与数据堆中其他元素的关系,即定义一个node数据;最后我要对这整个数据堆进行定义,这里定义描述选择了最简单的一个情况:即描述指向数据堆首节点的地址,因此表示数据堆的数据类型为node *型。
我们可以通过修改第一个定义,依据实际情况改变我们对提炼数据的定义;可以通过修改第二个node定义,修改我们组合数据的方式;可通过修改第三个list修改我们描述这数据堆的方式。
但上面的代码还不算最完善,我们可以使用typedef,同时通过有含义的类型名使其各部分定义更清晰一些。
typedef struct item { char name[20]; int rating; }Item; typedef struct node { struct film filmpro; struct node *pnode; }Node; typedef Node *List;
这里一定要清楚:List虽然定义上是指向Node的指针类型,但实质上按我们的需求,List是形容一个“数据堆”的,这里即是指向一个列表的,只不过我们通过指向列表首节点来描述它。
接下来,创建一个依据该ADT定义的电影列表的实例:
List movies; //简直是简单而清爽啊
2、创建完这个电影实例的ADT模型List后,接下来考虑实际我们需要哪些操作?先去描述这些操作,再通过函数去实现他们。而且这些操作对于所有List型数据都应该是通用的。
(1)初始化List:列表通过描述首节点地址来描述整个列表,那么直接使其指向空,即可实现,因为该函数要改变列表,所以用指针传递:
void InitializeList(List *plist) { *plist=NULL; }
(2)判断列表是否为空:若列表指向首节点地址为空,则列表为空。这里仅是判断,不改变列表,因此不应该传递指针,而直接用列表值传递:
int ListIsEmpty(List list) { if(list==NULL) return 1; else return 0; }
这里也可以使用const保护,来通过传递指针实现函数功能:
int ListIsEmpty(const List *plist) { if(*plist==NULL) return 1; else return 0; }
(3)判断列表是否为满:这里因为列表List的定义中只有首节点地址,而没有限定列表最大节点数目。因此这里判断列表是否为满,实质是判断系统可否为列表继续分配内存空间来存放其节点。所以这里函数也是不改变列表本身的,甚至是不涉及列表,因此下面代码其实也是可以不传参的。
int ListIsFull(const List *plist) { Node *newnode; newnode=(Node*)malloc(sizeof(Node)); if(newnode==NULL) //malloc false return 1; else //malloc succeed
return 0;
free(newnode); }
(4)计算列表中节点数量
int ListItemCount(const List *plist) { Node *look; int n=0; look=*plist; while(look!=NULL) { look=look->next; n++; } return n; }
(5)在列表尾部添加一个项目
int AddItem(Item item,List *plist) { Node *look; Node *newnode; look=*plist; newnode=(Node*)malloc(sizeof(Node)); if(newnode==NULL) return 0; while(look!=NULL) look=look->next; look=newnode; look->item=item; look->next=NULL; return 1; }
(6)释放列表内存空间
void EmptyList(List *plist) { Node *look; while((*plist)!=NULL) { look=*plist; *plist=*plist->next; free(look); } }