C_Primer_Plus17.advanced_representation
高级数据表示
- 要点
函数:深入 malloc()
使用 C 表示不同类型的数据
新的算法,从概念上增强开发程序的能力
抽象数据类型(ADT, abstract data type)
C 语言只是工具,设计和创建项目来完成想做的事情才是真正的目的。
通常,程序开发最重要的是找到程序中表示数据的好方法,正确的表示数据可以更容易地编写程序的其余部分。设计一种数据类型包括设计如何存储该数据类型和设计一系列管理该数据的函数。
设计好了数据类型后,还要对它进行操控,即有一些对应的算法。设计数据类型的过程还包括把算法和数据表示相匹配的过程。这会用到一些常见的数据形式,比如队列,列表和二叉树。
抽象数据类型(ADT)是以面向问题而不是面向语言的方式,把解决问题的方法和数据表示结合起来。设计一个 ADT 后,可以在不同的环境中复用。理解 ADT 可以为将来学习 OOP 和 C++ 做好准备。
链表
运行时才能确定需要多少存储空间的情况下,链表比数组更方便。链表中的元素用结构实现。比如,要设计一个用于存储电影名字和评分的结构:
#define TSIZE 45
struct film{
char title[TSIZE]; // 名字
int rating; // 评分
struct film* next; // 指向下一个元素的指针
};
链表的最后一个元素的 next 指针为 NULL,表明该元素是链表最后一个元素。第一个元素的地址由一个单独的指针存储,这个指针叫做头指针。
对于大型程序,不要试图一下子计划好一切。很多成功的大型程序是由许多成功的小程序逐步发展来的。
如果要修改程序,首先应该强调最初的设计,并简化其他细节,尽量做到模块化,接口化和封装。
抽象数据类型(ADT)
设计数据类型时,应当考虑两个方面的信息:属性和操作。属性指包含哪些数据,包括数据本身、状态和指向关系;操作指对数据的运算,包括加减乘除,增减元素,改变状态和指向关系等。
开发一种新类型的步骤:
- 提供类型属性和相关操作的抽象描述。这些描述不能依赖特定的实现,也不能依赖特定的编程语言。这种正式的抽象描述被称为抽象数据类型。
- 开发一个实现 ADT 的编程接口。即指明如何储存数据和执行所需操作的函数。
- 例如在C中,提供结构定义和操控该结构的函数原型。
- 这些作用域用户定义类型的函数相当于作用域C基本类型的内置运算符。需要使用该新类型的程序员可以使用这个接口进行编程。
- 编写代码实现接口。使得使用者无需了解具体的实现细节。
建立抽象
以创建链表为例,说明如何创建一种数据结构及其操作
链表:一个存储一系列项目且可以对其进行所需操作的数据对象。该定义未说明链表可以存储什么项,也未指定是用数组、结构还是其他数据形式存储,也未规定用什么方法实现操作。
基本操作:
- 初始化一个空链表
- 在链表末尾添加一个新项
- 确定链表是否为空
- 确定链表是否已满
- 确定链表中的项数
- 访问链表中的每一项执行某些操作,如显示该项
其他可选操作:
- 链表的任意位置插入一个项
- 一处链表中的一个项
- 在链表中检索一个项(不改变链表)
- 用另一个项替换链表中的一个项
- 在链表中搜索一个项
以保存电影及评分为例,说明创建链表的过程,该类型总结如下:
- 类型名
- 简单链表
- 类型属性
- 可以存储一系列项
- 类型操作
- 初始化链表为空
- 确定链表是否为空
- 确定链表已满
- 确定链表中的项数
- 在链表末尾添加项
- 遍历链表,处理链表中的项
- 清空链表
建立接口
接口分两部分,第一部分是描述如何表示数据,第二部分是描述实现 ADT 操作的函数。应该用通用的类型来描述数据,而不是一些特殊类型,比如 int 或 struct film,比如用统一的名字 Item 代替 struct film,如果以后更改 Item,只需要重新定义 Item类型,不必更改其余接口定义:
#define TSIZE 45
struct film
{
char title[TSIZE];
int rating;
};
typedef struct film Item;
然后定义链表中的项的存储类型,加入 next 指针:
typedef struct node
{
Item item;
struct node* next;
} Node;
再定义一个 list 的结构,把链表大小 size 信息包含进去:
typedef struct list
{
Node* head;
int size;
} List;
// 使用链表,并初始化:
List movies;
movies.head = NULL;
movies.size = 0;
可以使用函数,让使用者不必关心内部细节:
void InitializeList(List * plist);
InitializeList(&movies);
然后依次定义其他接口。list.h 接口头文件:
/** list.h 文件 */
#ifndef LIST_H_
#define LIST_H_
#include <stdbool.h> // C99
#define TSIZE 45 // 电影名字符串长度
struct film
{
char title[TSIZE];
int rating;
};
typedef struct film Item;
typedef struct node
{
Item item;
struct node* next;
} Node;
typedef struct list
{
Node* head;
int size;
} 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 (* func)(Item item));
/* 操作: 释放已分配的内存 */
/* plist 指向一个已初始化的链表 */
/* 后置条件: 释放链表的所有内存,链表设置为空 */
void EmptyTheList(List* plist);
#endif
实现接口
对接口的实现
/** list.c 文件 */
#include <stdio.h>
#include <stdlib.h>
#include "list.h"
void ShowList(List* plist);
void InitializeList(List* plist)
{
plist->head = NULL;
plist->size = 0;
}
bool ListIsEmpty(const List* plist)
{
return plist->size == 0;
}
bool ListIsFull(const List* plist)
{
bool full = false;
Node* node = malloc(sizeof(Node));
if (NULL == node)
{
full = true;
}
free(node);
return full;
}
unsigned int ListItemCount(const List* plist)
{
return (unsigned int) plist->size;
}
bool AddItem(Item item, List* plist)
{
Node* node = malloc(sizeof(Node));
if (NULL == node)
{
printf("memory is full\n");
return false;
}
if (NULL == plist)
{
printf("plist is NULL\n");
return false;
}
node->item = item;
node->next = NULL;
if (NULL == plist->head)
{
plist->head = node;
++plist->size;
return true;
}
Node* listNode = plist->head;
if (NULL == listNode)
{
plist->head = node;
++(plist->size);
return true;
}
while (NULL != listNode->next)
{
listNode = listNode->next;
}
listNode->next = node;
++(plist->size);
return true;
}
void Traverse(const List* plist, void(* func)(Item item))
{
Node* listNode = plist->head;
while (NULL != listNode)
{
func(listNode->item);
listNode = listNode->next;
}
}
void EmptyTheList(List* plist)
{
Node* listNode = plist->head;
Node** pnode = &listNode;
while (NULL != listNode)
{
pnode = &(listNode->next);
printf("free: %s|%d(%p)\n", listNode->item.title, listNode->item.rating, listNode);
free(listNode);
listNode = * pnode;
}
}
void ShowList(List* plist)
{
if (NULL == plist)
{
printf("list is NULL.\n");
return;
}
printf("list(%p): ", plist);
Node* listNode = plist->head;
if (NULL == listNode)
{
printf("Empty\n");
return;
}
Item item = listNode->item;
printf("%s|%d(%p)", item.title, item.rating, &listNode->item);
listNode = listNode->next;
while (NULL != listNode)
{
item = listNode->item;
printf(" - %s|%d(%p)\n", item.title, item.rating, &listNode->item);
listNode = listNode->next;
}
}
使用接口
/** film.c 文件 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "list.h"
extern void ShowList(const List* plist);
void show_item(Item item);
char* s_gets(char* st, int n);
int main(){
List movies;
Item item;
// 初始化
InitializeList(&movies);
if (ListIsFull(&movies))
{
fprintf(stderr, "No memery available! Bye!\n");
exit(1);
}
// 输入并存储
printf("Enter a movie title:\n");
while (NULL != s_gets(item.title, TSIZE) && '\0' != item.title[0])
{
printf("Enter your rating (0-10):\n");
while (scanf("%d", &item.rating) != 1)
{
printf("Enter your rating (0-10):\n");
while (getchar() != '\n')
continue;
}
while (getchar() != '\n')
continue;
if (false == AddItem(item, &movies))
{
fprintf(stderr, "Problem allocating memory\n");
break;
}
if (ListIsFull(&movies))
{
printf("The list is now full.\n");
break;
}
printf("Enter next movie title:\n");
}
// 显示
if (ListIsEmpty(&movies))
{
printf("No data entered.\n");
}
else
{
printf("------------------\n"
"movie list:\n");
ShowList(&movies);
}
printf("You have entered %d movies.\n", ListItemCount(&movies));
printf("------------------\n"
"Traverse:\n");
Traverse(&movies, &show_item);
// 清理
printf("------------------\n"
"Free:\n");
EmptyTheList(&movies);
printf("Bye!\n");
return 0;
}
char* s_gets(char* st, int n)
{
char* ret;
char* find;
ret = fgets(st, n, stdin);
if (ret)
{
find = strchr(st, '\n');
if (find)
* find = '\0';
else
while (getchar() != '\n')
continue;
}
return ret;
}
void show_item(Item item)
{
printf("item: %s, %d\n", item.title, item.rating);
}
output:
Enter a movie title:
冰与火之歌
Enter your rating (0-10):
8
Enter next movie title:
星球大战
Enter your rating (0-10):
7
Enter next movie title:
笑傲江湖
Enter your rating (0-10):
9
Enter next movie title:
Anonymous
Enter your rating (0-10):
9
Enter next movie title:
------------------
movie list:
list(0x7ffff4ec94e0): 冰与火之歌|8(0x647830) - 星球大战|7(0x647880)
- 笑傲江湖|9(0x6478d0)
- Anonymous|9(0x647920)
You have entered 4 movies.
------------------
Traverse:
item: 冰与火之歌, 8
item: 星球大战, 7
item: 笑傲江湖, 9
item: Anonymous, 9
------------------
Free:
free: 冰与火之歌|8(0x647830)
free: 星球大战|7(0x647880)
free: 笑傲江湖|9(0x6478d0)
free: Anonymous|9(0x647920)
Bye!
const 的限制
const List* plist的形参保证*plist不会被修改,即不允许这样的代码:plist->head = plist->head->next;但是并不意味着
plist指向的数据是 const 的,比如,这样的代码是允许的:++plist->head->item.rating;
队列 ADT
队列(queue)是具有两个特殊属性的链表,是一种“先进先出(FIFO)”的数据形式:
- 新项只能添加到链表的末尾
- 只能从链表的开头移除项
定义队列抽象数据类型
数据类型可以参考链表,并具有有以下操作:
- 初始化队列为空
- 确定队列为空
- 确定队列已满
- 确定队列中的项数
- 在队列末尾添加项
- 在队列开头删除或恢复项
- 清空队列
定义接口
/* queue.h 文件 */
#ifndef QUEUE_H_
#define QUEUE_H_
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAXQUEUE 10
typedef int Item
typedef struct node
{
Item item;
struct node* next;
} Node;
typedef struct queue
{
Node* head; // 指向队列头
Node* tail; // 指向队列尾
int count;
} Queue;
/* 队列接口定义 */
/* 操作: 初始化队列 */
/* 前置条件: pq指向一个队列 */
/* 后置条件: 队列被初始化为空 */
void InitializeQueue(Queue* pq);
/* 操作: 检查队列是否已满 */
/* 前置条件: pq 指向之前被初始化的队列 */
/* 后置条件: 如果队列已满则返回true,否则返回false */
bool QueueIsFull(const Queue* pq);
/* 操作: 检查队列是否为空 */
/* 前置条件: pq 指向之前被初始化的队列 */
/* 后置条件: 如果队列为空则返回true,否则返回false */
bool QueueIsEmpty(const Queue* pq);
/* 操作: 确定队列中的项数 */
/* 前置条件: pq 指向之前被初始化的队列 */
/* 后置条件: 返回队列中的项数 */
int QueueCount(const Queue* pq);
/* 操作: 在队列尾添加项 */
/* 前置条件: pq 指向之前被初始化的队列 */
/* item 是要被添加在队列的末尾 */
/* 后置条件: 如果队列不为空,item将被添加在队列末尾 */
/* 成功返回true,否则队列不改变,并返回false */
bool EnQueue(Item* pitem, Queue* pq);
/* 操作: 从队列的开头删除项 */
/* 前置条件: pq 指向之前被初始化的队列 */
/* 后置条件: 如果队列不为空,队列首端的item将被拷贝到 *pitem 中 */
/* 然后从队列中删除item,返回true */
/* 若该操作使得嘟列为空,则重置队列为空 */
/* 如果队列在操作前为空,则返回false */
bool DeQueue(Item* pitem, Queue* pq);
/* 操作: 清空队列 */
/* 前置条件: pq 指向之前被初始化的队列 */
/* 后置条件: 队列被清空 */
void EmptyTheQueue(Queue* pq);
#endif
接口的实现
/* queue.c 文件 */
#include <stdio.h>
#include <stdlib.h>
#include "queue.h"
void show_item(Item* item);
void InitializeQueue(Queue* pq)
{
pq->head = NULL;
pq->tail = NULL;
pq->count = 0;
}
bool QueueIsFull(const Queue* pq)
{
return pq->count == MAXQUEUE;
}
bool QueueIsEmpty(const Queue* pq)
{
return pq->count == 0;
}
int QueueCount(const Queue* pq)
{
return pq->count;
}
bool EnQueue(Item* pitem, Queue* pq)
{
if (QueueIsFull(pq))
return false;
Node* node = malloc(sizeof(Node));
if (NULL == node)
return false;
node->item = * pitem;
if (NULL == pq->head)
pq->head = node;
else
pq->tail->next = node;
pq->tail = node;
pq->count++;
return true;
}
bool DeQueue(Item* pitem, Queue* pq)
{
if (QueueIsEmpty(pq))
return false;
Node* pt = pq->head;
* pitem = pt->item;
pq->head = pt->next;
free(pt);
pq->count--;
if (0 == pq->count)
pq->tail = NULL;
return true;
}
void EmptyTheQueue(Queue* pq)
{
Item itmp;
while (!QueueIsEmpty(pq))
{
DeQueue(&itmp, pq);
show_item(&itmp);
}
}
void show_item(Item* item)
{
printf("item: %d\n", * item);
}

浙公网安备 33010602011771号