上一篇文章,已经初步会构造“链表”这种数据数据结构,本文主要讲一下构造的链表如何进行优化。
链表代码如下:
#include <stdio.h> #include <stdlib.h> #include "node.h" /* run this program using the console pauser or add your own getch, system("pause") or input loop */ int main(int argc, char *argv[]) { Node *head=NULL; int number; do{ scanf("%d",number); if(number!=-1){ //add to linked-list Node *p=(Node*)malloc(sizeof(Node)); p->value=number; p->next=NULL; //Find the last Node *last=head; if(last){ while(last->next){ last=last->next; } //attach last->next=p; } else{ head=p; } } }while(number!=-1); return 0; }
首先,链表结点已经在“node.h”的头文件中,通过对主函数的分析,我们可以看到,可以将在链表中增加结点这一行为提取出来,作为一个函数,命名为add()。要在一个链表中增加结点,因此add函数传入的参数就应该包含两个部分:链表的头结点和插入链表的数字。当然,如果改成“链表的头结点和新插入的结点”也是可以的,只不过需要将创建新结点单独拿出去在作为一个函数。因此修改之后的函数如下:
#include <stdio.h> #include <stdlib.h> #include "node.h" /* run this program using the console pauser or add your own getch, system("pause") or input loop */ void add(Node *head,int number); int main(int argc, char *argv[]) { Node *head=NULL; int number; do{ scanf("%d",number); if(number!=-1){ add(head,number); }while(number!=-1); return 0; } } void add(Node *head,int number) { //add to linked-list Node *p=(Node*)malloc(sizeof(Node)); p->value=number; p->next=NULL; //Find the last Node *last=head; if(last){ while(last->next){ last=last->next; } //attach last->next=p; } else{ head=p; } }
红色部分是修改的程序。这里要注意每次传进去的head仍然是一个null,add程序无法修改head。
这里,有些教科书会说到用全局变量head,将head定义在主函数外面。有两个原因导致我们不愿意这样使用:1、要尽可能避免使用全局变量,全局变量是有害的,很可能在程序的某个地方就会修改全局变量;2、全局变量head我们现在设想的是只在这个链表中使用,如果存在多个链表,那多个链表的head就无法使用这个全局变量。
另外一个方法是可以在add函数里面将head作为返回值,然后将add函数定义为一个结点型指针(Node* add(Node *head,int number)),并将函数返回值赋给head。
程序修改如下:
#include <stdio.h> #include <stdlib.h> #include "node.h" /* run this program using the console pauser or add your own getch, system("pause") or input loop */ void add(Node *head,int number); int main(int argc, char *argv[]) { Node *head=NULL; int number; do{ scanf("%d",number); if(number!=-1){ head=add(head,number); }while(number!=-1); return 0; } } Node* add(Node *head,int number) { //add to linked-list Node *p=(Node*)malloc(sizeof(Node)); p->value=number; p->next=NULL; //Find the last Node *last=head; if(last){ while(last->next){ last=last->next; } //attach last->next=p; } else{ head=p; } return head; }
由于add函数没有使用全局变量,因此add函数可以针对不同的链表,是一个应用范围变广的函数,这看起来像是一个进步。但是这里存在一个接口函数设计的问题。这个函数最终要给其他程序员使用,程序员很难注意到
head=add(head,number);
程序员使用add函数只是在链表上增加一个结点而已,实在是很难想到竟然通过add函数的返回值去修改head。因此,站在接口设计的角度,依然是存在问题。如果程序员忘了这个add赋给head的操作,那么对于空链表的add操作就是错的。
第三种方案:在add函数的输入参数中不再传入head结点,而是传入头结点的指针。即add(&head,number)。程序的其他部分也需要进行修改。
#include <stdio.h> #include <stdlib.h> #include "node.h" /* run this program using the console pauser or add your own getch, system("pause") or input loop */ void add(Node *head,int number); int main(int argc, char *argv[]) { Node *head=NULL; int number; do{ scanf("%d",number); if(number!=-1){ add(&head,number); }while(number!=-1); return 0; } } Node* add(Node **pHead,int number) { //add to linked-list Node *p=(Node*)malloc(sizeof(Node)); p->value=number; p->next=NULL; //Find the last Node *last=*pHead; if(last){ while(last->next){ last=last->next; } //attach last->next=p; } else{ *pHead=p; } }
这里有一个比较高级的方法:先定义一个结构体,这个结构体包含Node的指针head
#include <stdio.h> #include <stdlib.h> #include "node.h" /* run this program using the console pauser or add your own getch, system("pause") or input loop */ typedef struct _list{ Node* head; }List; void add(Node *head,int number); int main(int argc, char *argv[]) { int number; List list; list.head=NULL; do{ scanf("%d",number); if(number!=-1){ add(&list,number); }while(number!=-1); return 0; } } void add(List *pList,int number) { //add to linked-list Node *p=(Node*)malloc(sizeof(Node)); p->value=number; p->next=NULL; //Find the last Node *last=pList->head; if(last){ while(last->next){ last=last->next; } //attach last->next=p; } else{ pList->head=p; } }这个改进的好处是用了一个自己定义的结构体代表整个链表,而不再是之前仅有节点的概念。从结点层次的操作提升到链表层次的操作,很多事情就会变得简单。比如链表结构体中不仅可以包含结构体头结点,还可以包含尾结点,节点数量等等。