我一直在尝试制作一个简单的程序来接受用户输入,直到他们按完成。当他们这样做时,程序将打印出他们键入的所有内容。我觉得我已经完成了大部分工作,程序编译得很好,但是当我输入输入然后按完成时,它将输出与我输入输入的行一样多的行。我把它画出来,觉得这段代码应该可以工作。我也对 C 很陌生。因此,如果有人可以让我知道出了什么问题,甚至给我建议。
#include <stdio.h>
#include <stdlib.h>
struct llist {
struct llist* nxt;
char* string;
};
void add(struct llist **tail, char* str) {
struct llist* n_ptr = (struct llist*)malloc(sizeof(struct llist));
(**tail).string = str;
(**tail).nxt = n_ptr;
(*tail) = n_ptr;
n_ptr->nxt = (struct llist*)0;
};
void print(struct llist *Head) {
struct llist* ptr;
ptr = Head;
while(ptr->nxt){
printf("%sn", ptr->string);
fflush(stdout);
(ptr = (ptr->nxt)); }
}
int main() {
char* line = NULL;
size_t size = 100;
char* done = "done";
struct llist head;
struct llist* tail = (struct llist*)malloc(sizeof(struct llist));
tail = &head;
do {
getline(&line, &size, stdin);
add( &tail , line ) ;
} while ( strncmp(line, done, 4) != 0 );
print(&head);
return 0;
}
列表仅包含指向数据的指针,而不包含数据本身。因此,如果添加到列表中的数据发生更改,列表中的数据也会更改。对于每个调用add
,str
都是相同的。因此,您只是一遍又一遍地将相同的指针添加到列表中。
为了快速、丑陋地修复,请更改:
(**tail).string = str;
自:
(**tail).string = strdup(str);
struct llist head;
struct llist* tail = (struct llist*)malloc(sizeof(struct llist));
tail = &head;
我还想指出,这里的malloc是不必要的。无论如何,您都会丢弃它,第三行的赋值会导致内存泄漏。
给你一些注意事项。
-
getline()
采用指向缓冲区的指针。 文本将存储在指针所说的任何位置。 你的文字会去哪里? 您应该执行以下操作:
line = malloc(size);
或者也许
char line[100];
其中任何一个都可以解决问题。 但是,由于如果缓冲区不够大,getline()
会动态增长缓冲区,因此malloc()
解决方案可能更可取。 (我不知道getline()
在动态增长缓冲区时是否会尝试调用free()
;如果是这样,那么malloc()
的缓冲区是非常可取的。
编辑:您可以忽略上述要点! 事实证明,getline()
足够聪明,如果你从一个空指针开始,它会为你分配一个缓冲区。 所以你的代码是正确的。 对此感到抱歉;我不熟悉getline()
.
-
您的链表代码主要调用
malloc()
来创建新节点。 但是出于某种原因,您声明了一个节点,以静态head
。 对于像这样的小程序来说,这没关系,但是如果你写了一个大程序,它会令人困惑;当你去释放链表时,你需要小心不要释放第一个节点(因为它不是使用malloc()
分配的)。 就个人而言,我会将head
和tail
都设置为指针,并将它们都设置为NULL
(对于长度为零的链表)。 最简单的方法是让你的add()
函数接受head
指针的参数,并在将第一个结构添加到链表时head
设置它。 您还需要小心一点,因为当您将 fist 结构添加到链表中时,您的tail
尚未设置,因此您不应该尝试在有新节点之前将新节点链接到前一个节点。 因此,对add()
的第一次调用应将head
和tail
设置为指向结构的全新实例,并且新实例的next
指针应设置为 null;然后,对add()
的其他调用应将新结构链接到现有链表。 -
在实际程序中,您将始终检查函数的返回值。
malloc()
有可能失败;你不应该只是假设它总是有效。 但是,如果您为一个类执行此操作,并且您不必进行这些错误检查,我想您可以跳过它。 不过,尽早学习谨慎的习惯并没有什么坏处。 -
如 @David Schwartz 的回答中所述,您应该调用
strdup()
以获取每个字符串的副本。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef enum {FAILURE, SUCCESS} ReturnType;
typedef struct node_t Node;
struct node_t {
Node* next;
char* data;
};
Node* nodeCreate(char* str) {
Node* res = (Node*) malloc(sizeof(Node));
if (res == NULL) {
return NULL;
}
res->data = (char*) malloc(strlen(str) + 1);
if (res->data == NULL) {
free(res);
return NULL;
}
strcpy(res->data, str);
res->next = NULL;
return res;
}
void nodeDestroy(Node* node) {
free(node->data);
free(node);
}
typedef struct list_t {
Node* head;
Node* tail;
} List;
List* listCreate() {
List* res = (List*) malloc(sizeof(List));
if (res == NULL) {
return NULL;
}
res->head = (Node*) malloc(sizeof(Node));
if (res->head == NULL) {
free(res);
return NULL;
}
res->head->next = NULL;
res->head->data = NULL;
res->tail = res->head;
return res;
}
void listDestroy(List* list) {
if (list == NULL) {
return;
}
Node* curr = list->head;
Node* next = NULL;
while (curr != NULL) {
next = curr->next;
nodeDestroy(curr);
curr = next;
}
free(list);
}
ReturnType listAdd(List* list, char* str) {
if (list == NULL) {
return FAILURE;
}
Node* newNode = nodeCreate(str);
if (newNode == NULL) {
return FAILURE;
}
list->tail->next = newNode;
list->tail = list->tail->next;
return SUCCESS;
};
void listPrint(List* list) {
if (list == NULL) {
return;
}
Node* curr = list->head->next;
while(curr != NULL){
printf("%sn", curr->data);
curr = curr->next;
}
}
#define MAX_LINE_SIZE 100
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
char line[MAX_LINE_SIZE];
char* done = "done";
List* list = listCreate();
if (list == NULL) {
fprintf(stderr, "Failure!n");
return 0;
}
do {
scanf("%s", line);
if (listAdd(list , line) == FAILURE) {
fprintf(stderr, "Failure!n");
listDestroy(list);
return 0;
}
} while (strncmp(line, done, 4) != 0);
listPrint(list);
listDestroy(list);
return 0;
}