不幸的是,有很多错误。正如 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 的结果转换为特定类型,因为这被认为是错误的形式,并且可能导致微妙且难以检测的错误。只需直接从malloc 分配返回即可。
其次,您永远不会检查以确保malloc 成功。当然,在您的简单程序中它不太可能会失败,但您应该养成检查可能失败的函数的返回值的习惯。
您可能应该将分配的内存初始化为某个默认值,因为malloc 返回给您的内存充满了垃圾。在这种情况下,这样的事情似乎很合适:
if(p) /* only if we allocated memory. */
memset(p, 0, sizeof(struct node));
有时您可以跳过此操作,但清除内存是明智的默认做法。
void freenode(nodeptr p)
{
free(p);
}
这也可以,但是在调用free 之前,您应该考虑验证p 不为NULL。同样,这归结为稳健性,这是一个养成的好习惯。
int main()
{
int i;
nodeptr *k;
int a;
int *px;
int r;
nodeptr end;
nodeptr s;
nodeptr start;
同样,这里我们有很多未初始化的变量,但至少其中一些名称要好一些。但请注意会发生什么:
您声明了一个名为i 的变量,其类型为int。但是您已经声明了一个名为i 的全局变量,其类型为nodeptr。所以现在,局部范围内的变量(int)shadows(也就是说,它隐藏了它)全局变量。所以在main 内部,名称i 指的是int。当有人阅读您的程序时,这只会增加混乱。
p = getnode();
q = getnode();
好的...所以,在这里您分配两个新节点并使p 和q 指向这些节点。到目前为止一切顺利。
q = start;
p = end;
糟糕...现在这是个问题。我们现在让p 和q 指向start 和end 分别指向的任何地方。
那些指向哪里?谁知道。 start 和 end 都被统一化了,所以它们可以指向任何东西。从此时起,您的程序显示undefined behavior:这意味着任何事情都可能发生。在这种情况下,它很可能会崩溃。
不幸的是,从这里开始,事情变得更加混乱。与其试图解释一切,我只会给出一些一般性的评论。
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 结尾(您在此处和其他循环中依赖的东西)。 getnode 中的 memset 修复解决了这个问题,但通常您应该确保如果您的代码需要特定行为(“未链接节点的 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 在这个问题中的实现也可能会有所帮助,但它的文档记录不多,所以您必须仔细研究和分析代码以确保您理解它。