有很多错误,不幸的是。正如 WhozCraig 所说,关于这个主题还有很多其他帖子,所以你应该在发帖前多搜索一下。但是既然你有,让我们一起来解决一些问题。
nodeptr i;
nodeptr q;
nodeptr p;
nodeptr *plist;
在这里,您要声明大量全局变量,其中大多数名称都不好。是什么i
?是什么p
?是什么q
?再往下,您重新声明具有相同名称的变量。有的类型相同,有的类型不同。这使您很难知道您引用的是哪个变量。
一般来说,避免使用全局变量并选择描述性名称。在这种情况下,您可以摆脱i
,p
和q
。
此外,您永远不会初始化plist
任何东西;您应该养成将变量初始化为一些合理的默认值的习惯。在这种情况下,NULL
可能是合适的,但由于您根本不使用该变量,因此可以将其删除。
nodeptr getnode(void)
{
nodeptr p;
p = (nodeptr) malloc(sizeof(struct node));
return p;
}
这很好,但是在 C 中,您不应该将结果malloc
转换为特定类型,因为这被认为是错误的形式,并且可能导致微妙且难以检测的错误。直接指定return frommalloc
即可。
其次,您永远不会检查以确保malloc
成功。当然,在您的简单程序中它不太可能会失败,但您应该养成检查可能失败的函数的返回值的习惯。
而且您可能应该将分配的内存初始化为某个默认值,因为返回给您的内存malloc
充满了垃圾。在这种情况下,这样的事情似乎很合适:
if(p) /* only if we allocated memory. */
memset(p, 0, sizeof(struct node));
有时您可以跳过此操作,但清除内存是一种明智的默认做法。
void freenode(nodeptr p)
{
free(p);
}
这也很好,但您应该考虑在调用之前验证它p
不是 NULL free
。同样,这归结为健壮性,这是一个养成的好习惯。
int main()
{
int i;
nodeptr *k;
int a;
int *px;
int r;
nodeptr end;
nodeptr s;
nodeptr start;
同样,这里我们有很多未初始化的变量,但至少其中一些名称要好一些。但请注意会发生什么:
您声明一个名为i
type的变量int
。但是您已经声明了一个名为i
that 的全局变量,其类型为nodeptr
。所以现在,局部作用域中的变量(int
)隐藏(即隐藏它)全局变量。所以里面main
的名字i
指的是int
。当有人阅读您的程序时,这只会增加混乱。
p = getnode();
q = getnode();
好的...所以,在这里您分配两个新节点并生成p
并q
指向这些节点。到目前为止,一切都很好。
q = start;
p = end;
哎呀...现在这是一个问题。我们现在 makep
和q
指向任何地方start
和end
分别指向。
这些指向哪里?谁知道。两者start
和end
都被统一化了,所以它们可以指向任何东西。从这一点开始,您的程序表现出未定义的行为:这意味着任何事情都可能发生。在这种情况下,它很可能会崩溃。
不幸的是,从这里开始,事情变得更加混乱。与其试图解释一切,我只会给出一些一般性的评论。
for (i = 0; i < 6; i++) {
printf("enter value");
scanf("%d", &r);
p = getnode();
p->info = r;
q->next = p;
q = q->next;
}
这个循环应该读取 6 个整数并将它们放入我们的链表中。这似乎是一件简单的事情,但也有问题。
首先,你永远不会检查返回scanf
来知道输入操作是否成功。正如我之前所说,您应该始终检查可能失败的函数的返回值并相应地处理失败。但在这种情况下,让我们忽略该规则。
一个大问题是q
指向内存中的随机位置。所以我们处于未定义的行为领域。
另一个大问题是有两种情况需要考虑:当列表为空时(即我们第一次向列表中添加数字时i == 0
)和列表不为空时(即每隔一次)。这两种情况下的行为不同。当i == 0
我们不能盲目地设置q->next
时,因为即使q
不指向随机位置,从概念上讲,也不会q
像这里使用的那样。
我们需要的是一些额外的逻辑:如果这是我们创建的第一个节点,则设置q
为指向该节点。否则,设置q->next
为该节点,然后执行q = q->next
.
另请注意,您从未在任何地方设置p->next
,这将导致您的列表不会以 NULL 结尾(您在此处和其他循环中依赖的东西)。中的memset
修复解决了getnode
这个问题,但通常您应该确保如果您的代码需要特定的行为(“未链接节点的next
指针指向 NULL;列表以 NULL 结尾”),您应该有代码来确保该行为。
q = start;
同样,在这里,我们重新q
设置指向尚未初始化并指向垃圾的指向start
。
while ((q->next) != NULL) {
printf("n%d", (q->next)->info);
q = q->next;
}
这是一个经典的打印循环。就其本身而言,这里没有错,尽管我认为从文体上讲,这些括号q->next
是多余的,并且使阅读代码比必须的要困难一些。我的指导方针是仅在需要覆盖 C 的默认评估顺序时添加括号,或者当括号有助于在视觉上向读者解释如何在精神上解析代码时在他的脑海中对表达式进行分组。
scanf("%d", &a);
end = getnode();
end->info = a;
end->next = NULL;
这很好,除了 的错误检查问题scanf
,尽管您不提示用户输入数字。但是您正确而明确地end->next
指出NULL
哪个很棒。
for (q = start; q->next != NULL; q = q->next)
;
同样,这里的问题是q
,start
不幸的是,它仍然指向垃圾。
q->next = end;
q = start;
while ((q->next) != NULL) {
printf("n%d", (q->next)->info);
q = q->next;
}
这是您第二次必须键入此代码来打印列表。通常,您应该避免代码重复。如果您发现在多个地方需要特定代码块,则将其拆分为一个函数并使用该函数是有意义的。这使得理解和维护代码更容易。
for (q = start; q->next->next != NULL; q = q->next)
;
由于这个位,这个循环很难理解q->next->next
。问问自己“如果我正在阅读这篇文章,我是否立即确定它q->next
永远不会为 NULL?” 如果你不是,那么你真的应该重写这个循环。
freenode(q->next);
q->next = NULL;
q = start;
再次q
指出,start
哪个是单化的。但是,嘿,如果我们还没有坠毁... ;)
while (q->next != NULL) {
printf("n%d", (q->next)->info);
q = q->next;
}
再一次......这应该是一个真正的功能。
return 0;
}
为了更好的实现,我建议您参考这里提出的许多其他问题之一(只需搜索“链表删除”。Khalid Waseem 在这个问题中的实现也可能会有所帮助,但它的文档记录并不多,所以你会有仔细研究和分析代码以确保您理解它。