所以我在使用C语言中的链接列表时遇到了一些麻烦。总的来说,我得到了概念和算法,因为我已经学习了Java中的想法。但它似乎是C中的一个不同的故事,因为我们也考虑了内存分配。
无论如何,我在这里有这个代码:
while (curr != NULL) {
printf("%s\n", (*curr).name);
curr = (*curr).next;
}
其中curr
是struct 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
}
基本上我似乎对int
和char
没有任何问题。如果我的列表中有{"Alice", 1000, 'F'} -> {"Bob", 1001, 'M'} -> {"Charlie", 1002, 'M'}
,我注意到添加到列表中的姓氏Alice
将是正在打印的名称。所以打印输出将是:
Alice
Alice
Alice
我在这里做错了吗?有什么我想念的吗?我是C的初学者,还在自学。非常感谢您的帮助和建议!
答案 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
始终确认已释放已分配的所有内存并且没有内存错误。