这一篇对应课程的第四次实验。
网易云课堂:http://mooc.study.163.com/learn/USTC-1000002006?tid=1000081002#/learn/announce
实验楼:https://www.shiyanlou.com/courses/122
代码库:http://git.shiyanlou.com/chenxu/shiyanlou_cs122
实验思路
实现一个通用链表,并实现创建链表以及增、删、查找节点等功能,在menu.c中使用这个链表保存命令信息, 通过链表提供的方法实现数据节点的初始化和查找。
实验过程
- 首先在linktable.h文件中定义链表节点和链表,再声明初始化链表函数、增加节点函数、删除节点函数、返回下一个节点的函数(用于遍历)等;
- 初始化链表函数CreateLinkTable,创建一个链表头指针并开辟内存空间,把它的头节点和尾节点设为空,返回链表指针;
- 插入节点函数AddLinkTableNode,将给定节点插入到链表尾部;
- 删除节点函数DelLinkTableNode,先查找节点的前置节点,如果找到则利用前置节点将待删除节点从链表上去除,并释放节点空间;
- 在menu.c中,先初始化命令链表,将命令节点强转成链表节点逐个添加到链表中,在遍历链表时需要将链表节点强转成命令节点。然后读取节点中信息。
实验难点
双指针
在 int initMenu(tLinkTable** ppLinkTable)
中使用了双指针,具体调用时 initMenu(&head);
使用了head的地址。原因如下:
- head在声明时
tLinkTable* head = NULL;
是NULL,不能直接使用,但是head本身的地址不为空(使用&取地址符获得); - 在initMenu函数中用head接受CreateLinkTable函数返回的指针(
*ppLinkTable = CreateLinkTable()
);
CreateLinkTable的函数定义:
tLinkTable * CreateLinkTable()
{
tLinkTable * head = (tLinkTable*)malloc(sizeof(tLinkTable));
if (head == NULL)
{
return NULL;
}
head->pHead = head->pTail = NULL;
head->SumOfNode = 0;
//head->mutex = PTHREAD_MUTEX_INITIALIZER;
return head;
}
head指向新分配的内存空间,然后将空间首地址返回。
指针转换
在 initMenu 添加节点时,先创建 tDataNode 类型的节点,存上数据,再强转成 tLinkTableNode 类型的节点加入到链表中:
tDataNode* pNode = (tDataNode*)malloc(sizeof(tDataNode));
pNode->cmd = "help";
pNode->desc = "Menu List:";
pNode->handler = Help;
AddLinkTableNode(*ppLinkTable, (tLinkTableNode *)pNode);
在遍历链表获取节点信息时,再将 tLinkTableNode 类型转化为 tDataNode 类型:
int ShowAllCmd(tLinkTable *head)
{
tDataNode *pNode = (tDataNode*)GetLinkTableHead(head);
while (pNode != NULL)
{
printf("%s - %s\n", pNode->cmd, pNode->desc);
pNode = (tDataNode*)GetNextLinkTableNode(head, (tLinkTableNode*)pNode);
}
return 0;
}
其实转化的只是节点的指针类型(节点声明的struct里要把pNext放在第一项),在访问时根据地址和数据长度可以读取到对应的数据。
函数指针
DataNode的数据结构:
typedef struct DataNode
{
tLinkTableNode *pNext;
char* cmd;
char* desc;
int (*handler)();
} tDataNode;
其中handler是指向返回值为 int 、参数为空的函数的指针,通过函数名可以调用的对应的函数。
实验结论
这次实验的链表更具有通用性和复用性,所有与命令小程序有关的操作都在menu.c中实现。本次实验的难点在于链表函数的编写,以及将命令节点和链表节点之间进行转换的理解。