在C中将文件数据读入链接列表

时间:2010-01-15 23:48:43

标签: c database file-io linked-list

我正在尝试创建一个简单的电话簿程序,该程序从文件中读取数据并将内容存储到列表中的特定节点中。如果我将addEntry函数与静态数据一起使用,它可以正常工作,例如:

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

如果我尝试从文件中读取多个条目,则每个条目看起来都是文件中最后一个条目的内容。例如,如果我的文件包含:

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 个答案:

答案 0 :(得分:2)

您需要将字符串值复制到每个新节点。您只存储指向每个字符串的指针,但它始终是相同的指针(在main中声明的first,last和num),因此它们都指向相同的内存。

因此,在addEntry方法中,您需要先分配内存来存储字符串,然后将字符串复制到新内存中。

手动添加条目的示例有效,因为char指针指向静态字符串。

所以在你的addEntry方法中,你应该这样做:

tempNode = (struct bookNode *)malloc(sizeof(struct bookNode));
tempNode->firstName = (char *)malloc(strlen(fn)+1);
strcpy(tempNode->firstName, fn);

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

答案 1 :(得分:1)

您的bookNode结构包含指向内存的指针。您的addEntry函数会将这些指针的副本放入列表中,但它们指向的内存仍由调用者拥有:事实上,它是firstlast和{ {1}}在num中声明的数组,然后在循环的下一次迭代中继续覆盖。

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

答案 2 :(得分:1)

您的计划存在一些问题。

你在做什么相当于:

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

the context of the last line中,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功能不需要malloc()firstNamelastName的{​​{1}}次来电,但您仍需要复制数据使用phoneNumber。 (要了解原因,请参阅上面的链接。)您的相应strcpy()功能也不需要释放这些成员。

现在,对于你的其他程序。您找到终止换行符的方式有效,但您可以使用freeEntry()标准C函数来简化它。您案件中的电话会是:

strchr()

最后,当您修复上述所有内容后,您会发现自己的电话簿中有last record twice。在C中,char *nl; if ((nl = strchr(first, '\n')) != NULL) { *nl = '\0'; } 不会告诉您现在是否在文件的末尾:它告诉您最后一次尝试从文件读取失败,因为 在结束时文件。