c学习笔记 第17章 高级数据表示 20210408

建立接口

简单链表有两个部分第一部分是如何表示数据, 第二部分是描述实现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;

进行修改时, 只需修改相应的实现代码, 不用修改使用实现的程序;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值