正如其他人所解释的,您正在向每个单词插入相同的指针,而 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 *ptr 和unsigned char *end 的原因是isspace() 将字符代码转换为无符号字符。如果我们只使用char *,则必须使用isspace((unsigned char)(*ptr)) 和isspace((unsigned char)(end[-1])) 才能让isspace() 在所有情况下都能正常工作。
您还应该包括<locale.h> 和<ctype.h>,并在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;
}