【问题标题】:adding contents from file to linked lists将文件中的内容添加到链表
【发布时间】:2023-03-21 23:00:01
【问题描述】:

我是 C 编程的新手。

我想将文件中的内容添加到链接列表中。

文件中的内容是名称,每个名称都应该有自己的节点。

但是,当运行代码时,我得到一个无限循环。 我试图解决,但我无法深入了解它。 我感觉是 fscanf 导致了问题。

提前谢谢你。

#include <stdio.h>
#include <stdlib.h> 
#include <string.h>

  typedef struct node {
    char name[50];
    struct node * next;
  }
node;

int main() {

  FILE * file = fopen("content.txt", "r");
  if (file == NULL)
    return 1;

  char buf[50];

  node * first = NULL;

  //read the contents of the file and write to linked list
  while (!feof(file)) {
    fscanf(file, "%s", buf);

    // try to instantiate node
    node * head = malloc(sizeof(node));
    if (head == NULL) {
      return -1;
    }

    head-> next = NULL;

    // add new word to linked list
    strcpy(head-> name, buf);

    //move node to start of linked list
    head-> next = first;
    first = head;

  }

  fclose(file);
  node * ptr = first;
  while (ptr != NULL) {
    printf("%s\n", ptr-> name);
  }

  return 0;

}

这就是输入文件的样子。

REDA
REDB
REDC
REDE
RED 
REDb
REDc
REDpikachu
REDboom

【问题讨论】:

  • 简而言之,我认为问题在于fscanf 将换行符留在缓冲区中,而feof(file) 永远不会正确。
  • @Osiris 您对解决 fscanf 函数中的换行符有何建议?
  • 最好用fgets或POSIXgetline函数逐行读取文件。
  • 打印列表时出现另一个问题:在while (ptr != NULL) ... 中,您永远不会更改ptr,因此也会导致无限循环。

标签: c file input linked-list


【解决方案1】:

您的代码中有许多问题需要纠正(有些问题比其他问题多)。

您的第一个问题可以通过阅读Why is while ( !feof (file) ) always wrong? 来解决。 (简短的回答是EOF 不是在fscanf 读取的最终成功 上生成,因此在读取最后一个“名称”后,您检查!feof(file) 测试TRUE,循环继续, fscanfEOF 失败,您分配然后尝试从 buf 中获取不确定值的 strcpy -- 调用 Undefined Behavior)

虽然在这种情况下,在阅读没有空格的单词时,您可以使用fscanf -- 但不要。早点养成好习惯。从文件的每一行获取输入时,使用面向行的 输入函数(例如fgets 或POSIX getline)来确保输入缓冲区中剩余的内容不依赖于scanf 使用了转换说明符。

例如,您可以:

#define MAXN 50     /* if you need a constant, #define one (or more) */

typedef struct node {
    char name[MAXN];
    struct node *next;
}
node;

... /* 读取文件内容并写入链表 */ while (fgets (buf, MAXN, file)) {

        /* valdiating the last char is '\n' or length < MAXN - 1 omitted */

        /* allocate new node */
        node *head = malloc (sizeof(node));
        if (head == NULL) {         /* validate allocation */
            perror ("malloc-node"); /* issue error message */
            return 1;   /* don't return negative values to the shell */
        }

        /* trim '\n' from end of buf */
        buf[strcspn(buf, "\n")] = 0;    /* overwrite with nul-character */

        /* initialize node values */
        strcpy (head->name, buf);
        head->next = NULL;

(注意: 不要将 值返回到您的 shell,并且不要在 -&gt; 运算符之后包含 空格引用结构的成员)

此时,您已成功将“名称”读入buf,为您的新节点分配,验证分配成功,将buf中包含的尾部'\n'修剪为面向行的输入函数,将修剪后的“名称”复制到head-&gt;name,并将head-&gt;next初始化为NULL。但是,你有一个问题。你所有的名字都以reverse-order的形式存储在你的列表中(有时可能是你想要的)。但这通常不是所需的行为。通过简单地声明一个更新为指向最后一个节点的附加指针,您可以按顺序插入到列表中,而无需迭代查找最后一个节点。

例如,简单地声明一个 last 指针并检查两种情况 (1) last=NULL 指示循环为空,并使 firstlast 指向 first 节点允许您 (2)在末尾添加所有其他节点,例如:

    node *first = NULL, *last = NULL;   /* use last for in-order chaining */
    ...
        /* initialize node values */
        strcpy (head->name, buf);
        head->next = NULL;

        /* in-order add at end of list */
        if (!last)
            first = last = head;
        else {
            last->next = head;
            last = head;
        }
    }

@CS Pei 正确解决了您的“无限循环”,您只是忘记在输出循环中将当前指针前进到 ptr-&gt;next

您还未能free 分配的内存。是的,它在程序退出时被释放,但养成跟踪您分配的所有内存然后在不再需要时释放该内存的习惯。 (随着代码的增长,这成为防止内存泄漏的重要技能)。例如,在你的输出循环中,你可以这样做:

    node *ptr = first;              /* declare/set pointer to first */
    while (ptr != NULL) {           /* loop while ptr */
        node *victim = ptr;         /* declare/set pointer to victim */
        printf ("%s\n", ptr->name); /* output name */
        ptr = ptr->next;            /* advance pointer to next */
        free (victim);              /* free victim */
    }

总而言之,您可以执行类似于以下的操作:

#include <stdio.h>
#include <stdlib.h> 
#include <string.h>

#define MAXN 50     /* if you need a constant, #define one (or more) */

typedef struct node {
    char name[MAXN];
    struct node *next;
}
node;

int main (int argc, char **argv) {

    FILE *file = argc > 1 ? fopen (argv[1], "r") : stdin;
    if (file == NULL)
        return 1;

    char buf[MAXN];

    node *first = NULL, *last = NULL;   /* use last for in-order chaining */

    /* read the contents of the file and write to linked list */
    while (fgets (buf, MAXN, file)) {

        /* valdiating the last char is '\n' or length < MAXN - 1 omitted */

        /* allocate new node */
        node *head = malloc (sizeof(node));
        if (head == NULL) {         /* validate allocation */
            perror ("malloc-node"); /* issue error message */
            return 1;   /* don't return negative values to the shell */
        }

        /* trim '\n' from end of buf */
        buf[strcspn(buf, "\n")] = 0;    /* overwrite with nul-character */

        /* initialize node values */
        strcpy (head->name, buf);
        head->next = NULL;

        /* in-order add at end of list */
        if (!last)
            first = last = head;
        else {
            last->next = head;
            last = head;
        }
    }

    if (file != stdin)  /* close file if not stdin */
        fclose(file);

    node *ptr = first;              /* declare/set pointer to first */
    while (ptr != NULL) {           /* loop while ptr */
        node *victim = ptr;         /* declare/set pointer to victim */
        printf ("%s\n", ptr->name); /* output name */
        ptr = ptr->next;            /* advance pointer to next */
        free (victim);              /* free victim */
    }

    return 0;
}

使用/输出示例

使用您的输入文件,您将获得以下输出:

$ ./bin/ll_chain_last <dat/llnames.txt
REDA
REDB
REDC
REDE
RED
REDb
REDc
REDpikachu
REDboom

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此 (2) 当不再需要它时可以释放

您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。

对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

$ valgrind ./bin/ll_chain_last <dat/llnames.txt
==24202== Memcheck, a memory error detector
==24202== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==24202== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==24202== Command: ./bin/ll_chain_last
==24202==
REDA
REDB
REDC
REDE
RED
REDb
REDc
REDpikachu
REDboom
==24202==
==24202== HEAP SUMMARY:
==24202==     in use at exit: 0 bytes in 0 blocks
==24202==   total heap usage: 9 allocs, 9 frees, 576 bytes allocated
==24202==
==24202== All heap blocks were freed -- no leaks are possible
==24202==
==24202== For counts of detected and suppressed errors, rerun with: -v
==24202== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认您已释放已分配的所有内存并且没有内存错误。

检查一下,如果您还有其他问题,请告诉我。

【讨论】:

  • 非常感谢您抽出宝贵时间。我真的不明白为什么我们用空字符和输出循环覆盖 buf 。我不明白设置指针的使用。 node* prt= frist 是否指向第一个节点? node *victim = ptr 指向什么?
  • 同时使用fgetsgetline 将在每行输入的末尾读取(并包含)'\n'。为防止将其存储为字符串的一部分(您不想在末尾悬挂'\n'),您只需用'\0' 覆盖'\n'(与0 相同)。是的,node *ptr = first; 只是将ptr 初始化为first 持有的相同地址(然后您可以使用ptr 进行迭代,例如ptr=ptr-&gt;next 而不会搞砸first)。与victim 相同,在释放节点之前将其设置为当前节点并增加ptr=ptr-&gt;next;(否则ptr-&gt;next 将失败)
  • 我现在明白了。非常感谢
  • 很好,很高兴它有帮助。您还可以使用strlen 修剪'\n',例如size_t len = strlen(buf); if (len &amp;&amp; buf[len-1] == '\n') buf[--len] = 0; 不是使用strcspn,而是strcspn 调用(返回buf 中不包含"\n" 的初始字符数)是一种简写方式。祝你编码顺利。
【解决方案2】:

我认为问题在于输出部分。

  while (ptr != NULL) {
    printf("%s\n", ptr-> name); // here is the infinite loop
    ptr = ptr->next; // add this 
  }

您可以通过添加一行来更新ptr 的值来修复它,如上所示。

【讨论】:

    【解决方案3】:
    #include <stdio.h>
    #include <stdlib.h> 
    #include <string.h>
    
    typedef struct node {
        char name[50];
        struct node * next;
    } node;
    
    void list_insert(node **head,char *data)
    {
        node *new;
    
        new = malloc(sizeof(node));
        new->next = *head;
        strcpy(new->name,data);
        *head = new;
    }
    
    int main() {
        char buf[50];
        node * first = NULL;
    
        FILE * file = fopen("content.txt", "r");
        if (file == NULL) {
            return 1;
        }
        while (!feof(file)) {
          fscanf(file, "%s", buf);
          list_insert(&first,buf);
        }
        fclose(file);
    
        node * ptr = first;
        while (ptr != NULL) {
            printf("%s\n", ptr-> name);
            ptr = ptr->next;
        }
        return 0;
    }
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-03-19
    • 1970-01-01
    • 2017-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-06
    相关资源
    最近更新 更多