【问题标题】:Is there any error in insert functon of link-list?链接列表的插入功能是否有错误?
【发布时间】:2021-05-15 00:54:37
【问题描述】:

WORD_LIST.H

我只想创建一个链接列表,我逐行从文件中获取数据并存储在链接列表中,但在列表末尾我调用显示函数,所以它不是打印单词而是打印单个单词 CNAA一次又一次

 struct word_node {
      struct word_node * next;
      const char * word;
    };
    struct word_list {
      struct word_node * head;
      long num_words;
    };

    void reading_words(const char * filename);
    void insert(struct word_list *,const char*);
    void display(struct word_list *p);

WORD_LIST.C

 void reading_words(const char * filename){
      struct word_list *p ;
      FILE* file = fopen(filename,"r");
      char *line = NULL;
      size_t len = 0;
      ssize_t read;
      p = (struct word_list*)malloc(sizeof(struct word_list));
      p->head  =NULL;
      p->num_words =0;
      while ((read = getline(&line, &len, file)) != -1) {
        line[strlen(line)-1] = '\0';
        p->num_words++;
        insert(p,line);
       }
      printf("Size : %ld\n",p->num_words);
      display(p);
      return ;
    }
    void insert(struct word_list *p,const char* word){
      struct word_node *test ;
      test = (struct word_node *)malloc(sizeof(struct word_node));
      if(test == NULL){
        printf("Out of Memory!");
       }
      else{
        test->word = word;
        if(p->head == NULL){
           p->head = test
           p->head->next = NULL;
          }
        else{
           test->next = p->head;
           p->head = test;
          }
       }
    }
    void display(struct word_list *p){
      struct word_node *temp;
      temp = p->head;
      while(temp!=NULL){
        printf("%s\n",temp->word);
        temp=temp->next;
       }
    }

根据Word文件,单词是 极客 手提袋 奥维 YELD MTA 一名警察 再见 舞蹈 BSRT 中国国家航空航天局

但插入功能后它只打印 CNAA 10 次

【问题讨论】:

    标签: c linux pointers data-structures


    【解决方案1】:

    让我们尝试更新reading_words函数如下:

    ...
    while ((read = getline(&line, &len, file)) != -1) {
        line[strlen(line)-1] = '\0';
        p->num_words++;
        insert(p,line);
        line = NULL;
        len = 0;
    }
    ...
    

    通过getline(3) (link) 的文档:

    如果在调用前 *lineptr 设置为 NULL 并且 *n 设置为 0,那么 getline() 将分配一个缓冲区来存储该行。

    【讨论】:

      【解决方案2】:

      正如其他人所解释的,您正在向每个单词插入相同的指针,而 getline() 只是碰巧更改了指针指向的缓冲区的内容。

      我建议使用 C99 灵活数组成员而不是指针:

      struct word {
          struct word *next;
          char         word[];
      };
      
      struct word *new_word(const char *);
      void         insert_word(struct word **, struct word *);
      
      struct word *read_words(FILE *);
      void         free_words(struct word *);
      void         show_words(struct word *, FILE *);
      

      new_word() 函数通过将数据复制到新的动态分配结构中,从给定的字符串创建一个新的struct word

      struct word *new_word(const char *src)
      {
          const size_t  srclen = (src) ? strlen(src) : 0;
          struct word  *w;
      
          w = malloc(sizeof (struct word) + srclen + 1);
          if (!w) {
              fprintf(stderr, "Out of memory.\n");
              exit(EXIT_FAILURE);
          }
          w->next = NULL;
      
          if (srclen > 0)
              memcpy(w->word, src, srclen);
      
          w->word[srclen] = '\0';
      
          return w;
      }
      

      即使src == NULL,上述方法也有效(创建一个空词)。因为strlen(NULL)不安全,所以我们使用(expression) ? (if-true) : (if-false)三元表达式来计算数据的长度。同样,如果没有数据,我们也不想使用memcpy()复制数据。

      另一个新函数free_words(),可用于释放整个单词列表,获取指向列表中第一个单词的指针:

      void free_words(struct word *list)
      {
          while (list) {
              struct word *curr = list;
      
              list = list->next;
      
              /* Poison the word, so that if we accidentally
                 use a word after it has been freed, we'll
                 surely notice.  This is purely a measure to
                 help with debugging programming bugs. */
              curr->next = NULL;
              curr->word[0] = '\0';
      
              /* Free this word. */
              free(curr);
          }
      }
      

      因为insert_word() 现在需要一个指向指针的指针(指向列表变量的指针),以及指向要插入的结构的指针,所以它变得微不足道:

      void insert_word(struct word **listptr, struct word *w)
      {
          /* Only do this if neither pointer is NULL. */
         if (listptr && w) {
             w->next = *listptr;
             *listptr = w;
         }
      }
      

      我们甚至可以实现insert_words(),在另一个列表前面插入一个列表:

      void insert_words(struct word **list, struct word *first)
      {
          if (list && first) {
              struct word *last = first;
      
              /* Find last word in first-list */
              while (last->next)
                  last = last->next;
      
              last->next = *list;
              *list = first;
          }
      }
      

      show_words() 几乎没有变化,除了我更喜欢它将应该打印到的流作为第二个参数。通常,它只是stdout

      void show_words(struct word *list, FILE *out)
      {
          if (list && out) {
              while (list) {
                  fputs(list->word, out);
                  fputs("\n", out);
                  list = list->next;
              }
          }
      }
      

      请注意,由于 C 通过值传递参数,因此修改 list 变量仅在函数本身内可见。调用者用来传递给show_words() 的任何变量都不会被修改,即使我们在show_words() 中修改了list

      这会留下read_words()。同样,我更喜欢将流句柄而不是文件名传递给函数。这样你也可以从标准输入中读取输入,只需传递stdin。该函数返回读取的单词列表,以相反的顺序,因为我们将读取的每个单词都添加到列表的开头。

      struct word *read_words(FILE *in)
      {
          struct word *list = NULL;
          struct word *curr;
          char        *line = NULL;
          size_t       size = 0;
          ssize_t      len;
      
          if (!in)
              return NULL;
      
          while (1) {
              len = getline(&line, &size, in);
              if (len == -1)
                  break;
      
              unsigned char *ptr = (unsigned char *)line;
              unsigned char *end = (unsigned char *)line + len;
      
              /* Skip leading whitespace (including newline). */
              while (ptr < end && isspace(*ptr))
                  ptr++;
      
              /* Trim trailing whitespace (including newline). */
              while (end > ptr && isspace(end[-1]))
                  end--;
              *end = '\0';
      
              /* Create the word structure. */
              curr = new_word(ptr);
      
              /* Prepend to the list. */
              insert_word(&list, curr);
          }
      
          /* Line buffer is no longer needed.  Note, free(NULL) is safe to do. */
          free(line);
      
          return list;
      }
      

      unsigned char *ptrunsigned char *end 的原因是isspace() 将字符代码转换为无符号字符。如果我们只使用char *,则必须使用isspace((unsigned char)(*ptr))isspace((unsigned char)(end[-1])) 才能让isspace() 在所有情况下都能正常工作。

      您还应该包括&lt;locale.h&gt;&lt;ctype.h&gt;,并在main() 的开头告诉C 库为运行程序的用户使用当前语言环境,通过

          setlocale(LC_ALL, "");
      

      根据使用的字符集(它是语言环境的一部分),isspace() 可能会将不同的代码视为空格。特别是,如果使用 ISO Latin 1、ISO Latin 15 或 Windows-1252(8 位西欧),则代码 160(不间断空格)也被视为空格。 (如果您在 Linux 中使用这样的语言环境,您通常通过 AltGr+Space 键入它。) 添加它只是一件小事,并且可以使您的代码在许多不同的情况下按照用户的预期工作。

      如果您想要原始文件顺序中的单词列表,那么您需要反转它。幸运的是,这很容易做到:

      struct word *reverse_word_order(struct word *list)
      {
          struct word *newlist = NULL;
      
          while (list) {
              struct word *curr = list;
      
              /* Advance original list pointer */
              list = list->next;
      
              /* Prepend word to newlist */
              curr->next = newlist;
              newlist = curr;
          }
      
          return newlist;
      }
      

      【讨论】:

        【解决方案3】:

        当您插入单词时,您正在调用

        insert(p,line);
        

        并将局部变量 line(指向 char 的指针)作为参数传递。稍后在插入功能中你有

        test->word = word;
        

        这只是复制指针,所以test-&gt;word 将指向与reading_words 函数中的局部变量line 相同的地址。因此,列表的每个元素都指向相同的地址。 相反,您应该使用strcpy 来复制字符串,但在此之前,您必须为字符串分配内存(并稍后在某处释放它)。比如:

        test->word = malloc(strlen(word)+1);
        strcpy(test-word,word);
        

        【讨论】:

        • 不过,您需要 strlen(word)+1 来说明字符串结尾的 nul (\0)。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-08-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多