21天C语言代码训练营(第十三天)

上一篇中,我们说到录入的数据需要保存在一个双向链表中,今天我们就来说说这个数据结构该如何实现。

链表

每一种数据结构都有各自的特点。链表的特点是:任意位置的添加和删除速度快,遍历速度快,查询速度慢。

我们选择链表的目的是方便我们随时在任意位置进行增、删、改、查工作。其实一个单项链表就能够满足我们的要求,但为了提高训练难度,我们选择使用双向链表。

双向链表

数据结构设计

#ifndef __LIST_NODE_H__
#define __LIST_NODE_H__

#include "Record.h"

typedef struct _tagListNode
{
    Record* _pR;
    struct _tagListNode* _pPre;
    struct _tagListNode* _pNext;
}ListNode;

ListNode* g_pL;
ListNode* g_pCur;
int g_ListCnt;

void ListsInit();
void ListDestroy();
void ListAdd(Record* pR);
void ListDel(ListNode* pNode);
int ListCnt();
void ListTraverShow();


#endif

这个是我们上一篇中给出的头文件ListNode.h的定义。主要分为三个部分:

  • 结构体

结构体ListNode用来定义链表中每个节点,其中,_pPre和_pNext两个ListNode类型指针分别用来指向这个节点的前驱节点和后继节点,_pR用来指向节点中保存的记录内容。

注意:在定义结构体时,两个指针不能使用ListNode这个关键字定义,因为此时这个关键字还没有被定义完成。

  • 全局变量

指针g_pL始终用来指向链表的头结点,有了它我们就能够让整个链表随时在掌握中。指针g_pCur指向的是当前节点,其实就是链表的尾节点。每次添加新记录时,我们都在这个指针所指向的节点后面追加一个新节点,把内容保存在这个节点中,之后让这个指针指向这个新节点。

gListCnt用来记录链表中保存了多少条记录。

  • 数据结构功能

最下边的这一组函数用来提供各种功能。我们可以通过调用这些函数来完成数据在内存中的存储。

功能实现

接下来我们新建文件ListNode.c来实现链表的各种功能函数。

1. 初始化

为了体现“高内聚,低耦合”的设计思想,我们希望设计出的双向链表只需要调用一个ListInit()就能完成初始化,不需要任何其他数据类型的单独声明。这就要求我们在函数内部完成各个全局变量的管理。

双向链表的初始化实际上是头结点的创建和各个变量的初始化过程。请看下面这个函数:

void ListInit()
{
    g_pL = (ListNode*)malloc(sizeof(ListNode));

    g_pCur = g_pL;

    g_pCur->_pR = NULL;
    g_pCur->_pPre = NULL;
    g_pCur->_pNext = NULL;
    g_ListCnt = 0;
}

这就是初始化函数。首先创建一个结点,用g_pL指针指向它,之后让尾部指针g_pCur也指向这个节点。之后把所有相关的变量全部初始化。是不是很简单呢。

链表的存储空间是由每一个节点来提供的,而每次添加内容时都需要申请一个新节点空间去保存数据。

2. 添加记录

默认的添加方法是在链表尾部增加一个新节点,如下图:

添加节点

过程如下:

  • 创建一个新ListNode变量,申请堆空间
  • 将新记录数据保存到新ListNode中
  • g_pCur的_pNext指针指向新节点
  • 新节点的_pPre指针指向g_pCur节点
  • g_pCur指针指向新节点

完成这几个动作之后,新加入的节点成为了链表新的尾节点。代码如下:

void ListAdd(Record* pR)
{
    g_pCur->_pR = pR;

    ListNode* pNode = (ListNode*)malloc(sizeof(ListNode));

    g_pCur->_pNext = pNode;
    pNode->_pNext = NULL;
    pNode->_pPre = g_pCur;
    g_pCur = g_pCur->_pNext;
    
    g_pCur->_pR = NULL;
    g_pCur->_pNext = NULL;
    
    g_ListCnt++;
}

3. 删除记录

删除节点的动作相对复杂一些,当外部传入某一个节点时,将这个节点从链表中删除。具体操作如下图:

删除节点

如图所示,具体的删除流程如下:

  • 找到输入节点的前后节点,由于我们的设计方法,需要考虑删除的是首节点或尾节点的特殊情况。
  • 让前面节点的_pNext指针指向后面节点
  • 让后面节点的_pPre指针指向前面节点
  • 释放需要删除节点的空间

代码如下:

void ListDel(ListNode* pNode)
{
    if (g_ListCnt < 1)
    {
        return;
    }

    if (pNode->_pPre == NULL)
    {
        g_pL = g_pL->_pNext;
        g_pL->_pPre = NULL;
    }
    else if (pNode->_pNext == NULL)
    {
        pNode->_pPre->_pNext = NULL;
    }
    else
    {
        pNode->_pPre->_pNext = pNode->_pNext;
        pNode->_pNext->_pPre = pNode->_pPre;
    }

    RecordDestroy(pNode->_pR);
    free(pNode);
    
    g_ListCnt--;
}

注意两种特殊情况的处理。

4. 遍历打印

数据都保存进链表之后,我们需要依次把这些内容打印出来。这个功能由两部分组成:

  • 遍历链表
  • 打印数据

打印数据这部分,我们已经把它封装成一个独立的函数了,在Record.h中声明了。我们只需要实现遍历功能就好。

链表的遍历其实就是让一个ListNode指针从首节点开始依次像后访问每一个节点。代码如下:

void ListTraverShow()
{
    ListNode* pNode;
    for (pNode = g_pL; pNode->_pR != NULL; pNode = pNode->_pNext)
    {
        RecordPrint(pNode->_pR);
    }
}

创建一个pNode指针,让它从首节点开始依次通过_pNext指向每一个节点,之后通过RecordPrint打印这个节点的内容。

5. 释放

这是最后一个功能了,我们在程序退出前需要手动释放链表中的每一个节点。方法很简单,遍历这个链表,依次释放每一个节点的空间。

void ListDestroy()
{
    for (g_pCur = g_pL; g_pCur != NULL;)
    {
        RecordDestroy(g_pCur->_pR);
        g_pL = g_pCur->_pNext;
        free(g_pCur);
        g_pCur = g_pL;
    }

    g_ListCnt = 0;
}

现在我们已经实现了一个双向循环链表的基本功能了。下一篇我们开始讨论如何用这个链表为程序提供服务。

留下一个练习题,如何在链表的任意位置添加一个新节点呢?请自己实现一个方法。

我是天花板,让我们一起在软件开发中自我迭代。
如有任何问题,欢迎与我联系。


上一篇:21天C语言代码训练营(第十二天)
下一篇:21天C语言代码训练营(第十四天)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容