下午马基课,实在是无聊,就打开手机看了看qq,发现群里有人在问程序题,一看是C语言的题,就想着自己应该能解决,就看了看程序。
程序的主要目的是利用单向链表来玩一个所谓的“杀人”游戏,就是从某个人开始数数,数到第几个人这个人就over,然后从下一个人开始。
一开始还觉得题挺简单的(事实上也是如此),可是她的bug隐藏的比较深,其实总的思路是对的,而且代码也比较精炼。我找了有一会儿(从中看出我的水平不高:))。
这是她的源代码:
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 typedef struct lnode 5 { 6 int number; 7 char name[10]; 8 char sex[5]; 9 int age; 10 struct lnode *next; 11 }lnode,*LinkList; 12 LinkList L; 13 int main() 14 { 15 int N; 16 printf("请输入总人数N:\n"); 17 scanf("%d",&N); 18 int number; 19 char name[5]; 20 char sex[5]; 21 int age; 22 lnode *p,*r;int i; 23 L=(lnode *)malloc(sizeof(lnode));//创建头结点 24 L->next=NULL;//链表初始化 25 L->number=-1; 26 r=L;//r始终指向终端结点,开始时指向头结点 27 for (i=1;i<=N;i++) 28 { 29 printf("请输入成员信息:编号,姓名,性别,年龄\n"); 30 scanf("%d",&number); 31 scanf("%s",&name); 32 scanf("%s",&sex); 33 scanf("%d",&age); 34 p=(lnode *)malloc(sizeof(lnode));//创建新结点 35 if(!p) 36 {printf("存储分配失败!"); 37 return 1; } 38 //将成员信息传入结点 39 p->number=number; 40 strcpy(p->name,name); 41 strcpy(p->sex,sex); 42 p->age=age; 43 r->next=p;//将*p插入*r之后 44 r=p; 45 46 } 47 r->next=L;//建立循环链表 48 p=L->next; 49 for (i=1;i<=N;i++) 50 { 51 if(p->number)//避免输出头结点 52 {printf("%d,",p->number); 53 printf("%s,",p->name); 54 printf("%s,",p->sex); 55 printf("%d\n",p->age); 56 p=p->next;} 57 } 58 int S,M,X; 59 printf("\n请分别输入开始报号人的编号S,间隔的个数M以及剩余人数X:"); 60 scanf("%d%d%d",&S,&M,&X); 61 printf("\n被杀的人的编号为:"); 62 p=L; 63 while(p->number!=S)p=p->next;//查找编号为S的人 64 // printf("%d",p->number); 65 lnode *h;//将用来保存即将被删除的结点的地址 66 while(N>X)//判断是否继续循环 67 { 68 i=1; 69 while(i!=M)//判断谁将被杀(删除) 70 { 71 if(p->next==L)p=p->next->next; 72 else p=p->next; 73 i++; 74 //printf("%d,%d,",p->number,i); 75 } 76 printf("%7d\n",p->number);//输出被删除的人的编号 77 p->number=0;//便于后续查找该结点的前一个结点 78 // h=p->next;//保存将被删除的人的编号 79 p=L;//从头查找将被删除的结点的前一个结点 80 while(p->next->number>0)p=p->next; 81 //p=p->next->next;//从链表中删除结点 82 h=p->next->next; 83 free(p->next); 84 p=h; 85 if(p->number==-1)p=p->next;//防止头结点报1 86 // free(h);//释放结点,该结点 代表的人退出游戏 87 88 N--;//幸存总人数-1 89 } 90 /* printf("\n最后幸存的人的信息是: "); 91 p=L->next; 92 for(i=1;i<=N;i++,p=p->next) 93 if(p->number>0) 94 { 95 96 printf("%7d,",p->number); 97 printf("%s,",p->name); 98 printf("%s,",p->sex); 99 printf("%d\n",p->age);}*/ 100 return 0; 101 }
一开始是在找她的链表是不是处理细节的时候出了错误,但是并没有发现有什么问题。之后用vs2013debug了一下,依旧没有发现具体错误,可就是错了。(看来debug能力也有待提高:()于是从头开始看,看到后面直觉告诉我错误应该在while (N>X)的那一 部分。接着对于这段代码看了好几遍,终于发现了原来有逻辑错误,问题的根源就是:以为把number标记成0就可以了,结果忽略了之前的number=0也是会对后面造成干扰的,而且这种干扰只会在从尾到头实现第二次循环的时候才有可能会发生,因此,我的修改 方案是直接把over的节点free掉,省得麻烦(如果一定要保存他们并且尝试到时候跳过他们的话实现起来比直接free掉要繁琐)。
下面是我对于 while(N>X)部分的修改(其实也就改了一点点):
1 while(N>X)//判断是否继续循环 2 { 3 i=1; 4 while(i!=M)//判断谁将被杀(删除) 5 { 6 if(p->next==L)p=p->next->next; 7 else p=p->next; 8 i++; 9 //printf("%d,%d,",p->number,i); 10 } 11 printf("%7d\n",p->number);//输出被删除的人的编号 12 p->number=0;//便于后续查找该结点的前一个结点 13 // h=p->next;//保存将被删除的人的编号 14 p=L;//从头查找将被删除的结点的前一个结点 15 while(p->next->number>0)p=p->next; 16 //p=p->next->next;//从链表中删除结点 17 h=p->next->next; 18 lnode *temp=p->next; 19 p->next=p->next->next; 20 free(temp); 21 p=h; 22 if(p->number==-1)p=p->next;//防止头结点报1 23 // free(h);//释放结点,该结点 代表的人退出游戏 24 25 N--;//幸存总人数-1 26 }
主要是第17,18,19,20行有了修改。
其实事后看看这个错误貌似挺明显的,实则不然。现在的自己是以上帝视角来俯瞰这段代码的,但当时自己身在其中,不能够较好的把握全局,清晰代码结构,所以有种“身局者迷,旁观者清”的意思。
这件事对于自己的启发就是在完成一段代码之前要对其整个框架有一个完整的清晰的了解,并且要充分保证逻辑的严谨性,不能出现一点点的漏洞,不然一个小漏洞将会对整个程序造成极大的影响。