建立接口
简单链表有两个部分第一部分是如何表示数据, 第二部分是描述实现ADT操作的函数。
接口设计应尽量与ADT描述一致
#define TSIZE 45
struct film
{
char title[TSIZE];
int rating;
};
typedef struct film Item;
定义完毕Item后, 需确定如何储存这种类型的项
typedef struct node
{
Item item;
struct node * next;
} Node;
typedef Node * List;
也可以创建一个变量记录项数
typedef struct List
{
Node * head; // 指向链表头的指针
int size; //链表的项数
}List;
List movies;
程序启动后应该把头指针初始化为NULL;
movies.head = NULL;
movies.size = 0;
可以通过写一个InitializeLIst()函数来初始化链表。尽可能** 数据隐藏 **
为指导用户, 可提供如下注释:
/*操作:初始化一个链表*/
/*前提条件: plist指向一个链表*/
/*后置条件: 该链表初始化为空*/
void InitializeList (List * plist);
前提条件(precondition)是调用该函数前应具备的条件
后置条件(postcondition)是执行完该函数后的情况
只有通过传递指向该变量的指针才能更改主调函数传入的变量。
/*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;
// 函数原型
// 操作:初始化一个链表
//前提条件: plist指向一个链表
//后置条件: 链表初始化为空
void InitializeList(List* plist);
//操作: 确定链表是否为空定义, plist指向一个以确定的链表
//后置条件: 链表为空返回true, 否则返回false
bool ListIsEmpty(const List* plist);
//操作:确定链表是否已满, plist指向一个已确定链表
//后置条件: 满则返回true否则返回false
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类型的变量
创建一个Item类型的变量
初始化链表为空
当链表未满且有输入时:
把输入读取到Item类型变量中
在链表末尾添加项
访问链表中的每个项并显示他们
该程序利用list.h中描述的接口
/* film3.c ----使用抽象数据类型(ADT)风格的链表*/
/*与list.c一起编译*/
#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 (s_gets(temp.title, TSIZE) != 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 movie 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("Movies: %s Rating: %d\n", item.title, item.rating);
}
实现接口
实现List接口的c方法是把函数统一放在list.c文件中, 整个程序由list.h(定义数据结构和提供用户接口的原型), list.c(提供函数代码实现接口)和film3.c(把链表接口应用于特定编程问题的源代码文件)组成。
/*list.c -- 支持链表操作的函数*/
#include <stdio.h>
#include <stdlib.h>
#include "list.h"
static void CopyToNode(Item item, Node* pnode);
// 接口函数
//链表设置为空
void InitializeList(List* plist)
{
*plist = NULL;
}
//如果链表为空, 返回true
bool ListIsEmpty(const List* plist)
{
if (*plist == NULL)
return true;
else
return false;
}
//如果链表已满, 返回true
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;
}
//创建储存项的节点, 将其添加至plist指向的链表的结尾
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->next = NULL;
if (scan == NULL)
*plist = pnew;
else
{
while (scan->next != NULL)
scan = scan->next;
scan->next = pnew;
}
return true;
}
// 访问每个节点并执行pfun指向的函数
void Traverse(const List* plist, void(*pfun)(Item item))
{
Node* pnode = *plist;
while (pnode != NULL)
{
(*pfun)(pnode->item);
pnode = pnode->next;
}
}
//释放由malloc()分配的内存
//设置链表指针为NULL
void EmptyTheList(List* plist)
{
Node* psave = (*plist)->next;
while (*plist != NULL)
{
psave = (*plist)->next;
free(*plist);
*plist = psave;
}
}
//局部函数定义
//把一个项拷贝至节点中
static void CopyToNode(Item item, Node* pnode)
{
pnode->item = item;
}
在实现接口时, 有时编写一个辅助函数很方便, 由于该函数是实现的一部分, 但不是接口的一部分, 所以需要使用static 存储类别说明符将其隐藏在list.c文件中。
** const的限制: 多个链表处理函数将const List * plist作为形参, const确实提供了保护, but **
*plist = (*plist) ->next; //如果*plist是count, 不允许这么做
(*plist) ->item.rating = 3; //即使plist是count, 这么做也可行
当前AddItem()的效率不高, 可以通过保存链表结尾处地址以解决该问题:
typedef struct list
{
Node * head;
Node * end;
}List;
进行修改时, 只需修改相应的实现代码, 不用修改使用实现的程序;