C中的结构/链接列表/指针/字符串

时间:2018-03-29 03:20:33

标签: c string pointers struct linked-list

所以我在使用C语言中的链接列表时遇到了一些麻烦。总的来说,我得到了概念和算法,因为我已经学习了Java中的想法。但它似乎是C中的一个不同的故事,因为我们也考虑了内存分配。

无论如何,我在这里有这个代码:

while (curr != NULL) {
    printf("%s\n", (*curr).name);
    curr = (*curr).next;
}

其中currstruct Student,其中Student的“属性”之一是name。所以假设我已经将我的节点添加到列表中。当我执行上述操作时,我似乎完全得到了相同的名字。下面是我将代码添加到链表中的代码:

void insertNode(char *name, int idNum, char sex) {

    struct Student *s;
    s = malloc(sizeof(struct Student));

        //not entirely sure if this is the right way to do it
    if (s == NULL) { 
        printf("Memory allocation failed.");
        return;
     }

    (*s).name = name;
    (*s).idNum = idNum;
    (*s).sex = sex;

    (*s).next = head;        //head is the start of the list
    head = s;         //inserting the node at the beginning of the list

    curr = head;     //place the current pointer at the start of list
}

基本上我似乎对intchar没有任何问题。如果我的列表中有{"Alice", 1000, 'F'} -> {"Bob", 1001, 'M'} -> {"Charlie", 1002, 'M'},我注意到添加到列表中的姓氏Alice将是正在打印的名称。所以打印输出将是:

Alice
Alice
Alice

我在这里做错了吗?有什么我想念的吗?我是C的初学者,还在自学。非常感谢您的帮助和建议!

3 个答案:

答案 0 :(得分:2)

您需要跟踪头部指针。

以下是完整的工作代码:

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


struct Student
{
    char * name;
    int idNum;
    char sex;
    struct Student * next;
};

struct Student * head=NULL;

struct Student *
insertNode (char *name, int idNum, char sex)
{

  struct Student *s;
  s = malloc (sizeof (struct Student));

  if (s == NULL)
    {
      printf ("Memory allocation failed.");
      return NULL;
    }

  (*s).name = name;
  (*s).idNum = idNum;
  (*s).sex = sex;


  (*s).next = head;     //head is the start of the list

  head = s;

  return head;          
}


int
main ()
{
    insertNode("Alice", 1, 'F');
    insertNode("Peter", 2, 'M');
    insertNode("Mike", 3, 'M');


  while (head != NULL)
    {
      printf ("%s\n", (*head).name);
      head = (*head).next;
    }

}

输出:

Mike
Peter
Alice

但是,在代码中仍然可以进行大量改进。特别是,符号(*s).name在C中有一个简写,s->name更简洁。您还可以避免使用全局头变量,而是传递其指针,以便在调用之间保持其更改的值。

答案 1 :(得分:1)

(*s).name = name;将结构中的名称设置为局部变量名称,该名称在insertNode函数末尾将变为无效。

您需要复制该名称。

您可以使用s->name = strdup(name);(请记住,删除节点时需要释放s->name

或许是一个更简单的方法,你可以在学生节点中使name成为一个数组char name[32];或类似的。然后你会strcpy()进入它,但要少一些东西可以自由。

答案 2 :(得分:1)

关于问题的确切来源,您的问题有点不清楚。 Syed的答案在确定解决方案方面做得很好,但在学习链接列表时需要注意一些细微之处。

首先,s->name = name;的分配仅在名称为字符串文字时才有效,其中您将地址分配给存储在只读中的字符串的开头记忆到s->name

如果您正在从文件(或stdin)读取值并将包含name的指针传递给insertnode,那么所有节点s->name都将保持指针地址,如果它仍然在范围内,则指向包含姓氏读取的存储,如果用于将name传递给insertnode的指针已经用完范围,您将调用未定义的行为尝试访问s->name

正如评论中正确指出的那样,处理这种情况的方法是为传递给name的每个insertnode分配存储空间,将起始地址分配给s->name并复制{{1到name。如果有可用的话,可以使用s->name,否则只需要一个简单的分配和strdup

strcpy

注意:,因为 size_t len = strlen (name); /* get length of name */ /* allocate/validate storage for name */ if ((s->name = malloc (len + 1)) == NULL) { perror ("malloc - name"); free (s); return NULL; } strcpy (s->name, name); /* copy name to s->name */ 分配内存(例如在strdup中),您仍应在通话后验证s->name = strdup(name);不是s->name另请注意,字符串C是以 nul-characer NULL终止的。因此,为了分配足够的存储来保存'\0'加上 nul-终止字符,您必须分配name个字节。)

接下来,100%确定,单个链接列表中的 insert-node-before strlen (name) + 1将影响切换插入列表的节点的顺序。虽然这对您的实现可能无关紧要,但是当您打印列表时可能会感到惊讶。这会将迭代保存到插入时的下一个空闲节点,但会牺牲输入顺序来执行此操作。

另一种方法是,通过简单地检查head是否为head来按顺序插入节点,如果是,则插入第一个节点,否则,迭代直到NULL并插入新节点为list->next = NULL。 (这也要求您在为它们分配存储时初始化所有list->next个节点s->next。一个简单的实现(为方便起见,NULL使用typedef),看起来类似于:

struct student

此外,您应该避免声明全局变量。没有理由不在/* returns pointer to new node on success, or NULL on failure */ student *insertnode (student **head, char *name, int idnum, char sex) { student *s = malloc (sizeof *s); /* allocate new node */ if (s == NULL) { /* validate allocation */ perror ("malloc - s"); return NULL; } /* populate new node */ size_t len = strlen (name); /* get length of name */ /* allocate/validate storage for name */ if ((s->name = malloc (len + 1)) == NULL) { perror ("malloc - name"); free (s); return NULL; } strcpy (s->name, name); /* copy name to s->name */ // s->name = name; /* only works for string literals */ s->idnum = idnum; s->sex = sex; s->next = NULL; /* always initialize ->next NULL */ if (*head == NULL) { /* handle add 1st node */ *head = s; } else { /* handle add rest */ student *iter = *head; /* declare pointer to head */ while (iter->next != NULL) /* while ->next not null */ iter = iter->next; /* get next node */ iter->next = s; /* set iter->next to new node */ } return s; /* head never changes, return current node to indicate success/failure of insert. */ } 中声明您的列表,并且如果列表地址可以更改,则指针(或指针的地址)作为参数传递给需要在列表上操作的任何函数。只需在main()内移动head的声明,然后添加指向list的指针作为main()的参数(如上所述)。

您应该insertnode分配所有内存。 (是的,这发生在free(),但现在建立良好的习惯,保留指向所分配的所有内存的起始地址的指针,并在不再需要时释放该内存将带来红利,并且您的项目规模会增长

最后,当您遍历列表时,必须使用单独的指针。否则,如果您使用exit进行迭代,则它是一条单行道。当你迭代到head时,你已经失去了对你分配的所有内存的唯一引用,并且无法将它们取回。只需使用临时指针,例如

head == NULL

(当然,你最后一次通过列表来释放所有列表内存 - 你使用什么并不重要 - 当你完成后将不再有任何列表)

完全放弃,您可以执行以下操作:

    student *iter = head;   /* use separate pointer to iterate list */
    while (iter != NULL) {
        printf ("%-8s  %4d  %c\n", iter->name, iter->idnum, iter->sex);
        iter = iter->next;
    }

注意:虽然不是错误,但C的标准编码样式避免使用#include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct student { /* typedef makes declarations easier */ struct student *next; /* pointers 1st limits size to 24-bytes */ char *name, sex; int idnum; } student; /* returns pointer to new node on success, or NULL on failure */ student *insertnode (student **head, char *name, int idnum, char sex) { student *s = malloc (sizeof *s); /* allocate new node */ if (s == NULL) { /* validate allocation */ perror ("malloc - s"); return NULL; } /* populate new node */ size_t len = strlen (name); /* get length of name */ /* allocate/validate storage for name */ if ((s->name = malloc (len + 1)) == NULL) { perror ("malloc - name"); free (s); return NULL; } strcpy (s->name, name); /* copy name to s->name */ // s->name = name; /* only works for string literals */ s->idnum = idnum; s->sex = sex; s->next = NULL; /* always initialize ->next NULL */ if (*head == NULL) { /* handle add 1st node */ *head = s; } else { /* handle add rest */ student *iter = *head; /* declare pointer to head */ while (iter->next != NULL) /* while ->next not null */ iter = iter->next; /* get next node */ iter->next = s; /* set iter->next to new node */ } return s; /* head never changes, return current node to indicate success/failure of insert. */ } int main (void) { student *head = NULL; insertnode (&head, "Alice", 1000, 'F'); /* insert nodes */ insertnode (&head, "Peter", 1001, 'M'); insertnode (&head, "Mike", 1002, 'M'); student *iter = head; /* use separate pointer to iterate list */ while (iter != NULL) { printf ("%-8s %4d %c\n", iter->name, iter->idnum, iter->sex); iter = iter->next; } /* free allocated memory */ while (head != NULL) { /* freeing list, pointer used doesn't matter */ student *victim = head; /* save pointer to node to delete */ head = head->next; /* move to next node */ free (victim->name); /* free storage for name */ free (victim); /* free storage for node */ } } camelCase变量名来支持所有 lower- case 同时保留大写名称以供宏和常量使用。这是一个风格问题 - 所以它完全取决于你,但没有遵循它可以导致某些圈子里的第一印象错误。)

示例使用/输出

MixedCase

内存使用/错误检查

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

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

对于Linux $ ./bin/ll_head_next Alice 1000 F Peter 1001 M Mike 1002 M 是正常的选择。每个平台都有类似的记忆检查器。它们都很简单易用,只需通过它运行程序即可。

valgrind

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