C语言链表实战总结

来源:互联网 发布:ai人工智能 开源 编辑:程序博客网 时间:2024/04/27 13:25

近期项目里遇到瓶颈,我最后选择的方案是用链表解决又重新拾起了多年没用的链表。搞了一个星期,总算是把功能都实现了,并考虑了各种突发状况,基本上不会有程序挂掉的结果。不过过程中还是遇到了很多问题,这些问题在不断的翻书回顾中被解决委屈

背景是这样的,要求在液晶显示屏上创建一个窗口,里面至少有200个EDIT控件,用户可以在EDIT控件上编辑一些特定的语句,然后点击”运行“按钮时,保存现有的EDIT的内容并顺序执行各语句。当时抓耳挠腮,想卧槽这怎么搞,受做软件工程师学长的启发,我觉得用链表来操作应该没问题,编辑了几行EDIT就保存对应个数结点的链表,既节省空间而且方便顺序执行,执行到下一节点为NULL的时候结束就完了。而且链表结构的插入、删除时间复杂度都为O(1),很是方便。

首先是定义链表结构:

typedef struct _INSTRUCTOR {uint8_t        index; //该节点位于链表中的索引uint8_t        _flag; //该条程序的指令类别char        *EditContent; //该条指令的文本内容struct _INSTRUCTOR* next; //指向下一条指令的指针}_Instructor,*_Listptr;
_Listptr 就是_Instructor结构的指针类型了,就等于_Instructor *。注意咯,我这里的结构体没有用__attribute__定义它的属性,也就是说它是默认对齐的,一共占16bytes即0x10。为了省去考虑插入结点、删除结点时关于第一个结点的特殊情况问题,我创建了一个带有表头结点的链表:

_Listptr Ins_List_Head;//程序链表的头指针int Create_List(void){Ins_List_Head = (_Listptr)malloc(sizeof(_Instructor));if( !Ins_List_Head)return -1;Ins_List_Head -> index = 0;Ins_List_Head->next = (void*)0;return 0;}
根据调试发现,每次链表申请到的头结点位置都在0X20004320大笑.每增加一个结点,地址增加0x10,咦?大家发现问题没有,地址连续了!当然,这是我们希望看到的现象,不连续的话会造成内存碎片,还要进行碎片回收工作。所以我觉得可能是编译器优化的结果(敲打如果有大神知道原因的话请指正!)  

下面就要每编辑完一个EDIT,就要插入一个结点了,那么是前插还是后插呢?当然是后插了,不然让我怎么顺序执行啊。

int Add_Node(int index, enum _FLAG flag, char *content){int i = 0;_Listptr    q = (_Listptr)malloc(sizeof(_Instructor));_Listptr    p = Ins_List_Head;if(index <= 0 || !q)return -1;q -> index = index;q -> EditContent = content;q -> _flag = flag;while(p && i < index-1){p = p ->next ;//此时p即为下标为index的结点的前驱节点i ++;}if(!p)return -1;else{q -> next = p->next ;//使前驱的后继成为新增结点的后继p->next  = q ;  //使新增结点的称为前驱的后继}return 0;}
这里要提一提  while循环里的循环次数为 index-1,这个-1很关键啊。道理当然很简单,插入的Node要把自己的头和尾都连接在链表上啊。我把新来的Node下标定为index,所以要在index-1处的结点就开始操作:将index-1的后继(其实就是NULL)成为新Node的后继,然后把新Node成为Index-1 的后继。一开始我粗心大意,直接 i<index ,而且没有检查每个节点的指针是否不为空(程序不健壮!),所以添加的第一个结点时程序就挂掉了,还傻了吧唧的说为什么,现在想想真实可笑!

经过修改后添加结点总算没问题了,然后就是删除结点的操作,我直接盗用书上的源码,很开心他可以直接用:

int Delete_Node(int index){int i;_Listptr p = Ins_List_Head;_Listptr q;if(index <= 0)return -1;for(i = 0; i< index - 1;i++)p = p -> next;//此时p为index结点的前驱结点if(!p){return -1;}else{q = p->next ;//q结点即为Index结点p -> next = q -> next;//使index结点的后继成为p的后继free(q);  //释放index结点的空间return 0;}}
道理更简单,就是把一个Node从链表上拆下来的过程:只要把他的前驱Node的next指针指向它的后继Node就ok了

别以为做完这几步就可以做关于顺序执行方面的工作了,还要考虑有的用户编到一半,突然选择放弃不编了,点击"返回"按钮回到上一窗口时,难道我还保留着这些链表结点?当然不行啦,这样的话如果用户真正再开始编辑时,然后执行程序,发现有几步操作是上次编辑的没用的命令,这不是瞎搞吗!所以还要做的一个工作就是:点击 “返回”按钮就清空所有结点,当然头结点要保留啊。

void Clear_List(void){_Listptr p = Ins_List_Head ;_Listptr q ;int      i = GetListLength() ;while(i--){q = p -> next;p -> next = q -> next;free(q);}}
你能想象这段短短的小程序我调了整整一下午大哭。所以人家说,一个好的程序员不在于他一天能写多少行代码,而是写多少行高效的代码,当然我不是说我是一个优秀的程序员啊。清空链表的思想跟删除结点的思想大致一样,只不过是一直操作头指针的next。一开始我的循环体没用while,而是用的for(i =0 ;i<GetListLength();i++)  ,这样执行有个问题,我每次点击"返回"按钮后,只清空了一半的Node。我静静的想了想生气,这样写的话每次i<当前的结点长度,每删除一个结点长度都减一啊,瞎搞啊!!!真想抽自己大耳光。

谨以此文几年这一周蛋疼的链表生活。。。。。。




0 0