C问题 - 不知道如何将指针分配给列表的开头

4

我有一个简单的任务,教授要求我们从文本文件中提取一些数字并加载到链表中。 我不想深入细节,但我有一个基本问题。

他给我们提供了一个如下的函数:

INTLIST* init_intlist( int n ) 
{
INTLIST *lst;
lst = (INTLIST *)malloc(sizeof(INTLIST));
lst->datum = n;
lst->next = NULL;
return lst;
}

这个函数用来初始化链表的第一个元素。然后他要求我们根据这个签名定义一个函数:

int insert_intlist( INTLIST *lst, int n )

所以我认为他只是希望我们将此添加到链表中,因此我尝试了这个:
int insert_intlist( INTLIST *lst, int n )
 {
 INTLIST* lstTemp;
 lstTemp = (INTLIST *)malloc(sizeof(INTLIST));
 lstTemp->datum = n;
 lstTemp->next = lst;
 lst = lstTemp;       
 free(lstTemp);          
 }

我的思路是创建一个临时节点,将数据值(Datum)赋值并将下一个指针指向当前指针指向的位置。然后我重新分配主指针到这个新创建的临时节点。

这样我们就有了例如两个节点:

[新的临时节点] -> [之前初始化的节点]

当我跟踪代码时,看起来很棒...

然后在主函数中我只需要编写一个打印列表的函数:

                   while (lst!=NULL)
                      {
                       printf("The value is:%d", lst->datum);
                       lst=lst->next;
                      }

问题在于这似乎只打印了一个数字(即我从文件中读取的第一个数字,我认为它是列表中的最后一个数字,或者至少我以为它是列表中的最后一个数字)。
但是它应该继续进行,因为我在文件中有10个数字。 我知道代码非常混乱,我会整理它……以下是我的整个主函数,如果有人需要更多信息:
#include <stdio.h>
#include <stdlib.h>
#include "intlist.h"

int main(int argc, char *argv[])
{
  char c;    /* Character read from the file. */
  FILE* ptr;   /* Pointer to the file. FILE is a
       structure  defined in <stdio.h> */
  int index=0;
  //INTLIST* aList[10]; //will use later

    /* Open the file - no error checking done */
  ptr = fopen("1.txt","r");
    /* Read one character at a time, checking 
       for the End of File. EOF is defined 
      in <stdio.h>  as -1    */

  if(ptr==NULL) {
    printf("Error: can't open file.\n");
    /* fclose(file); DON'T PASS A NULL POINTER TO fclose !! */
    return 1;
  }

  //aList[index] = malloc(sizeof(INTLIST)); WE NEED THIS LATER ON....
  INTLIST *lst=NULL;

  while ((c = fgetc(ptr)) != EOF)
  {
        if (c != ' ') 
        {
         //make sure it isnt a space
         int i = c - '0'; //get the value from the text file
             if(c=='\n') 
                 {
                      // aList[index]=lst;
                      // index++;
                      // aList[index] = malloc(sizeof(INTLIST));

                           while (lst!=NULL)
                              {
                               printf("The value is:%d", lst->datum);
                               lst=lst->next;
                              }

                           free(lst);
                           free(aList[index]);
                           return 0;
                          //new line in the file 
                         //create another linked list
                 }

            if (lst==NULL)
             lst = init_intlist(i);
            else
             insert_intlist( lst, i); 
        }
  }

  fclose(ptr);
  system("PAUSE"); 
  return 0;
}

以下是 intlist.h 文件,供需要的人使用:

#ifndef __intlist_h__
#define __intlist_h__
/* each entry in the list contains an int */
typedef struct intlist {
int datum;
struct intlist *next;
} INTLIST;
INTLIST *init_intlist( int n ); /* initializes the intlist with initial datum n */
int insert_intlist( INTLIST *lst, int n ); /* Inserts an int (n) into an intlist from the beginning*/
void list_append(INTLIST *list, void *datum); /* Inserts entry to the end of the list */
INTLIST* list_front(INTLIST *list); /*return the element at the front of the list, and remove it 
from the list*/
void list_map( INTLIST *list, void (*f)(void *) ); /*Applies a function to each element of the list */
void list_delete( INTLIST *list ); /* Deletes (and frees) all entries in the list */
#endif

你的教授提供的原型头文件是这样的吗?int insert_intlist( INTLIST *lst, int n ); 并且注释说将一个整数(n)从开头插入到intlist中,这是错误的:根据原型,这是不可能实现的。请查看我的答案以获取详细信息。 - Alok Singhal
@Alok:你不能自然地做到这一点。Jerry明白了,把新节点放在第二个位置,并交换有效载荷。我没有仔细阅读,以为是在后面添加。 - dmckee --- ex-moderator kitten
@dmckee:啊哈!我喜欢那种打破规则的方式,尽管我不确定这是否是教练想要的。另一个可能性是返回值为“int”,因此该函数只返回旧头部的有效载荷,并用新值替换它!这将是一个非常简单的函数。 - Alok Singhal
6个回答

4
这里有几个问题。
首先是一个严重的错误:

我将从一个严重的错误开始:

int insert_intlist( INTLIST *lst, int n )
 {
 INTLIST* lstTemp;
 lstTemp = (INTLIST *)malloc(sizeof(INTLIST));
 lstTemp->datum = n;
 lstTemp->next = lst;
 lst = lstTemp;       
 free(lstTemp);             //   <<<<<  NO!
 }

你仍在使用那个内存,所以你不能释放它。


其次,提供给您插入的原型没有办法返回列表的新前端,所以您无法更改列表的前端。这意味着您必须将新节点添加到后面,而不是像您做的那样添加到前面。

此外,提供的返回类型int可能意味着他期望输出列表中的节点数,这并不成问题,因为您将不得不遍历列表以找到后面。

再试一次,你做得还不错。


好的,谢谢您的赞美之词。让我看看能否在列表头部添加内容? - oJM86o
抱歉,我刚刚在阅读这个内容...关于这个问题,如果文本文件包含1 9 7 2,他将1添加到链表中,然后添加9...我看到的问题是在头部之后添加并交换值,最终导致链表倒序,变成了2 7 9 1。因为你说要在头部旁边添加并交换数据值...所以他真的应该这样做吗?似乎将其添加到末尾才是正确的方法?我只是问一下,因为我不是C开发人员,但我是这么看的? - JonH
void * 是 C 语言中的通用指针类型。您可以将任何对象指针转换为 void *,并在不丢失信息的情况下进行转换。在这种情况下,其余代码处理的是 INTLIST 而不是通用列表,因此 void *datum 可以写成 INTLIST *datum。但是,在 C 中,void * 用于表示“通用指针类型”。例如,malloc() 返回一个 void *,因为它不知道要为哪种类型的对象分配内存。 - Alok Singhal
假设我有一个类型为T的变量。那么,T data; T *pdata = &data; void *vp = pdata;会给我一个名为vp的指向void的指针。现在,我可以在函数中将此指针转换回T *。因此,给定上述声明和void f(void *p) { T *tp = p; },我可以调用f(vp);。正如dmckee所说,使用void *的原型在上面并不是很有用,因为代码的其余部分不是类型无关的。 - Alok Singhal
@Alok,我理解它有点像C#对象object。我可以看出你是如何“转换”它的。但我的问题是函数定义是void list_append(INTLIST* list, void datum)。从你所说的来看,void datum实际上是INTLIST* datum。我的问题是,如果教授想要插入一个节点到列表中,该如何调用此函数?在主函数中创建一个节点并将其传递给此追加函数吗?函数原型的编写方式似乎很奇怪。还有其他什么方法可以调用此函数吗? - JonH
显示剩余12条评论

4

处理类似以下代码:

int insert_intlist( INTLIST *lst, int n )
 {
 INTLIST* lstTemp;
 lstTemp = (INTLIST *)malloc(sizeof(INTLIST));
 lstTemp->datum = n;
 lstTemp->next = lst;
 lst = lstTemp;       
 free(lstTemp);          
 }

这有几个问题。首先,free(lstTemp) 似乎释放了您刚刚插入到列表中的节点,这可能不是您想要的。
其次,您将指向列表的指针传递到函数中 - 这意味着函数无法修改该指针,因此当您赋值给指针时,只会更改您的本地副本。
您有两个选择:您可以传递指向该指针的指针(以便您可以修改原始指针),或者您可以聪明地找出一种避免需要这样做的方法(但我不会立即透露秘密...)。

我不能修改函数签名,因为教授说我们不能这样做。我还注释了free(lstTemp),但问题仍然存在。 - oJM86o
1
是的 - 虽然那是一个错误,但它不是你注意到的错误。如果你必须使用相同的函数签名,仍然有一种方法,但它会更加棘手。基本思路是在当前列表头之后添加一个新节点,然后交换节点之间的值以使它们按顺序返回。 - Jerry Coffin
@JonH:这取决于他想要什么顺序。他最初尝试将项目添加为新的头项,所以我告诉他一种可以保持顺序的方法。也可以将项目添加到末尾,但它会给出相反的顺序,并且需要更长时间。如果您真的不关心顺序,可以在列表头之后立即添加每个项目,而不必交换。这会给出一个相当奇怪的顺序:首先是第一个项目,其余项目按相反顺序排列。另一方面,如果您不关心顺序,那么一开始就不应该使用链接列表。 - Jerry Coffin
@Jerry 感谢您的澄清,我只是想更熟悉C语言。我是一名C#开发人员,在看到这篇文章时遇到了困难。我以为当你说交换值时,顺序会使它相同,但实际上并不是这样。但这是因为我没有理解,所以你的解释没有问题。我注意到Alok在他的帖子中提到了这一点:“在C中,所有东西都是按值传递的。如果你想让函数改变某些东西,你需要将其地址传递给函数。”我的问题是参数* lst不就是一个指针吗?我不明白为什么需要**lst? - JonH
@JonH:是的,它是一个指针。这使您可以更改指针指向的内容--但在这种情况下,您需要更改的是指针本身,为此,您需要一个指向该指针的指针。 - Jerry Coffin
显示剩余3条评论

3
这行代码:
lst = lstTemp;  

只有在函数内部更改 lst 的值。它不会传递回调用者拷贝的指针。

您可以使用指向指针的指针,或者如果无法更改函数签名,则插入到列表的头之外的某个位置。

尽管通常处理此问题的方式是指向列表中的第一个元素 - 而是有一些列表结构,它持有对第一个元素的指针以及列表的其他信息(比如,它有多少个元素)。然后您可以在该结构上传递指针。


好的,我理解函数执行完后该变量就会失效,因此我无法更改函数签名。你能否给我更多关于在链表的头部之外插入元素的信息? - oJM86o
1
你可以遍历整个列表直到结尾(while(listNode->next) listNode = listNode->next;),然后将新节点添加到末尾(listNode->next = newNode;)。 - Anon.

2
在C语言中,所有内容都是按值传递的。如果你想让一个函数改变某些东西,你需要将其地址传递给该函数。因此,在int insert_intlist(INTLIST *lst, int n)中,你想要更改列表头部,你需要将一个指向它的指针传递给函数,即第一个参数应该是INTLIST **lst(下面也可以看到)。但是函数原型已经给出,不能更改。

这意味着您无法添加数字到列表开头,调用者不知道您是否这样做了。因此,您必须遍历由lst指向的列表,然后在链条的任何位置添加新节点。教授可能希望您将节点添加到末尾,但他可能还要求其他条件。
有了这些信息,让我们看一下原型的注释:
/* Inserts an int (n) into an intlist from the beginning*/
int insert_intlist( INTLIST *lst, int n );

评论或函数原型有误。如果这个文件是你的教授给你的,insert_intlist()不能被写成满足该评论的形式,因为它无法返回新头指针给调用者。应该使用以下任意一个原型:
/* Inserts an int (n) into an intlist from the beginning
   and returns the new head */
INTLIST *insert_intlist( INTLIST *lst, int n );

或者:

/* Inserts an int (n) into an intlist from the beginning */
int insert_intlist( INTLIST **lst, int n );

(请注意**。)
头部还包括:
/*return the element at the front of the list, and remove it from the list*/
INTLIST* list_front(INTLIST *list);

这是正确的。请注意,在list_front()中需要修改列表的头,以便返回新的头部。

最后,在insert_intlist()中不要进行任何free()操作。您想要将新节点保留在列表中,对吗?一旦调用者完成了对列表的操作,他将必须调用list_delete(),该函数会遍历链表并释放每个节点。


很好,表述非常清晰。会帮助任何人理解C语言 :)。 - JonH

2
在C语言中,函数的参数是“按值传递”的,这意味着当你进入函数时它们被复制了一份,任何你对它们所做的更改都不会反映回调用者。这意味着当你修改lst指向新分配的内存时,它实际上并没有修改调用者对列表的指针。
编辑:正如dmckee指出的那样,在插入函数中你不应该释放内存,因为你仍在使用它。那肯定是个bug,但它不是导致你问题的原因。

我想我的问题是,既然我不能更改函数签名..如何处理这种情况。 - oJM86o
正如其他人所指出的,你需要在列表中的其他位置插入新节点。它可以放在最后(从而保持数字的顺序),或者你可以将其添加为第二个节点。如果要将其添加到末尾,则需要到达列表的末尾,然后使最后一个节点指向新分配的节点。 如果添加第二个,则必须确保新节点指向先前的第二个节点,然后使第一个节点指向新节点。 - Tal Pressman

0

我同意Alok的观点。我也有同样的问题/教授。我是C编程的新手,一直在网上寻找论坛和C网页以获取帮助。我找到了一个支持Alok观点的来源。

我使用了

INTLIST *list_add(INTLIST **p, int i){

INTLIST *n;
    n = (INTLIST *) malloc(sizeof(INTLIST)); 
        if (n == NULL) 
    return NULL;   
    n->next = *p; /* the previous element (*p) now becomes the "next" element */

     *p = n;       /* add new empty element to the front (head) of the list */

      n->datum = i;
    return p; }

从我的主函数中,我能够传入

INTLIST *list

list_add(&list, 1); list_add(&list, 2);

所以当我打印列表时,它会输出2 1

教授建议如下:

INTLIST *mylist[N];

其中N是您输入文件的行数。然后,mylist[i] 是指向第i个链表的指针。

好的,为了测试目的,创建INTLIST * mylist [2];

我调用相同的函数:

list_add(&list[0], 1); list_add(&list[0], 2);

这将输出2 1... 很好,

但是当我这样做时:

list_add(&list[1], 3); list_add(&list[1], 4);

我得到了一个分段错误。


当我尝试打印列表而不是在list_add()期间,出现分段错误。 - MRP

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接