在C语言中将文件数据读入链表

3

我希望您能够帮助翻译一个关于IT技术的文本,该文本与创建通讯录程序有关,需要从文件中读取数据并将其存储到列表中的特定节点中。如果我使用静态数据(例如使用addEntry函数),则可以正常工作,例如:

addEntry("First", "Last", "555-555-5555");

如果我尝试从文件中读取超过1个条目,每个条目都只显示文件中的最后一个条目。例如,如果我的文件包含以下内容:
First1
Last1
123-456-7890
First2
Last2
987-654-3210

在将数据存储在列表中并打印后,输出如下:
First2
Last2
987-654-3210

First2
Last2
987-654-3210

与其打印每个具体的名称和数字,不如使用函数调用。这让我感到困惑,因为当我从文件中读取数据时才会出现此问题,而手动输入名称和数字时则没有问题。以下是main和addEntry的定义,提前感谢您。

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

struct bookNode
{
    char * firstName;
    char * lastName;
    char * phoneNumber;
    struct bookNode * next;
} * head;

FILE * fpointer;

void addEntry(char * fn, char * ln, char * pn);
void display();
int numEntries();
void writeBookData(struct bookNode * selection);

int main()
{
    head = NULL;
    addEntry("Test", "Name", "111-111-1111");
    addEntry("Test2", "Name2", "222-222-2222"); // These entries will work as intended

    int i;
    fpointer = fopen("addressbook.dat", "a+");
    if(fpointer == NULL)
    {
        printf("Error: addressbook.dat could not be opened.\n");
    }

    char first[20];
    char last[20];
    char num[20];

    while (!feof(fpointer))
    {
        fgets(first, 20, fpointer);
        fgets(last, 20, fpointer);
        fgets(num, 20, fpointer);

        //Removes newline characters from the ends of the names
        i = 0;
        while(first[i] != '\n')
        {
            i++;
        }
        first[i] = '\0';
        i = 0;
        while(last[i] != '\n')
        {
             i++;
        }
        last[i] = '\0';

        // Adds the entry from the strings with the file data in them
        addEntry(first, last, num);
    }
    fclose(fpointer);

    display(); // typical linked list display function

    int entryCount = numEntries();
    printf("There are %d entries in this Address Book\n", entryCount);

    return EXIT_SUCCESS;
}

void addEntry(char * fn, char * ln, char * pn)
{
    struct bookNode * tempNode, * iterator;
    tempNode = (struct bookNode *)malloc(sizeof(struct bookNode));
    tempNode->firstName = fn;
    tempNode->lastName = ln;
    tempNode->phoneNumber = pn;
    iterator = head;

    // If the list is empty
    if (head == NULL)
    {
        head = tempNode;
        head->next = NULL;
    }

    // The list is not empty
    else
    {
        while(iterator->next != NULL)
        {
            iterator = iterator->next;
        }
        tempNode->next = NULL;
        iterator->next = tempNode;
    }
}
3个回答

2
你需要将字符串值复制到每个新节点中。你只存储了指向每个字符串的指针,但它们始终是相同的指针(在主函数中声明的first、last和num),因此它们都指向同一块内存。
因此,在你的addEntry方法中,你需要首先分配内存来存储字符串,然后将字符串复制到新的内存中。
你手动添加条目的示例有效,因为char指针指向静态字符串。
因此,在你的addEntry方法中,你应该这样做:
tempNode = (struct bookNode *)malloc(sizeof(struct bookNode));
tempNode->firstName = (char *)malloc(strlen(fn)+1);
strcpy(tempNode->firstName, fn);

然后同样的步骤处理姓氏和电话号码。请记住,您需要遍历列表并释放每个字符串以及列表中节点的内存。


3
可以更简洁地写成 tempNode->firstName = strdup(fn); - R Samuel Klatchko
@R Samuel Klatchko:你说得对。我是一个C++程序员,所以我已经尽力忘记了C语言中的字符串处理函数... - villintehaspam
1
strdup 不是 ISO C 标准函数,但它是 POSIX 标准函数。 - Alok Singhal

1

你的程序存在一些问题。

你所做的相当于:

char data[SIZE];
char *p;
/* get some useful value in data */
p = data;

最后一行的上下文中,data指向数组data的第一个元素的指针(即该行等同于p = &data[0];)。因此,您刚刚所做的是将指针p赋值为data中第一个字符的地址。稍后,当您更改data的内容时,指向data第一个元素的指针仍然相同(data仍存在于相同的内存位置)。因此,所有指针都指向相同的存储空间,并且您不断覆盖存储空间中的内容。
但是,为什么在提供文字字符串时您的程序可以工作呢?因为C中的每个文字字符串都保证在整个程序的生命周期内存在,并具有唯一的地址。(有一个小例外:如果您在程序中多次使用文字字符串,则它可能引用相同的内存,也可能不是。)
所以,你应该为节点的firstNamelastNamephoneNumber成员动态分配内存,并在使用完毕后记得释放它们。
void addEntry(char *fn, char *ln, char *pn)
{
    struct bookNode *tempNode, *iterator;

    tempNode = malloc(sizeof *tempNode);
    tempNode->firstName = malloc(strlen(fn) + 1); /* +1 for terminating 0 */
    tempNode->lastName = malloc(strlen(ln) + 1);
    tempNode->phoneNumber = malloc(strlen(pn) + 1);

    /* Omitted check for malloc failures for brevity */
    strcpy(tempNode->firstName, fn);
    strcpy(tempNode->lastName, ln);
    strcpy(tempNode->phoneNumber, pn);

    /* Now continue with what you were doing */
}

接下来,您需要一个相应的freeEntry函数来释放空间。

另一种方法是以不同的方式声明您的struct

#define MAX 20
struct bookNode
{
    char firstName[MAX];
    char lastName[MAX];
    char phoneNumber[MAX];
    struct bookNode *next;
} *head;

接下来,你的addEntry函数不需要为firstNamelastNamephoneNumber调用malloc(),但你仍然需要使用strcpy()来复制数据。(要了解原因,请参考上面的链接。)相应的freeEntry()函数也不需要释放这些成员。

现在,对于你程序的其余部分。你找到终止换行符的方法是可行的,但你可以通过使用标准C函数strchr()来简化它。在你的情况下,调用将如下所示:

char *nl;
if ((nl = strchr(first, '\n')) != NULL) {
    *nl = '\0';
}

最后,当你修复了上述所有问题时,你会发现你在电话簿中得到了 最后一个记录两次。在 C 中,feof() 并不告诉你现在是否已到达文件末尾:它告诉你由于你已经到达文件末尾而上一次从文件读取尝试失败了。

1

你的bookNode结构体包含指向内存的指针。你的addEntry函数将这些指针的副本放入列表中,但它们所指向的内存仍然归调用者所有:实际上,它是你在main中声明的firstlastnum数组,在循环的下一次迭代中你会覆盖它们。

你的addEntry函数需要做的不是复制输入指针,而是为字符串分配足够的内存,将输入复制到该内存中并保留副本的指针。你还需要确保在完成后释放你已经分配的所有内存。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接