Чтение данных файла в связанный список в C - PullRequest
3 голосов
/ 16 января 2010

Я пытаюсь создать простую программу телефонной книги, которая считывает данные из файла и сохраняет содержимое в определенных узлах в списке. Это работает нормально, если я использую свою функцию 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 голосов
/ 16 января 2010

Вам необходимо скопировать строковые значения в каждый новый узел. Вы храните только указатель на каждую строку, но это всегда одни и те же указатели (first, last и num, которые объявлены в main), поэтому все они указывают на одну и ту же память.

Итак, в вашем методе addEntry вам нужно сначала выделить память для хранения строки, а затем скопировать строку в новую память.

Ваш пример, где вы добавляете записи вручную, работает, потому что указатели на символы указывают на статические строки.

Итак, в вашем методе addEntry вы должны сделать что-то вроде этого:

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

и то же самое для фамилии и телефона. Помните, что вам нужно пройти по списку и освободить память для каждой строки, а также для узлов в списке.

1 голос
/ 16 января 2010

Есть несколько проблем с вашей программой.

То, что вы делаете, эквивалентно:

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

В контекст последней строки , data относится к указателю, который указывает на первый элемент массива data (то есть строка эквивалентна p = &data[0];). Итак, вы просто присвоили указателю p значение адреса первого символа в data. Позже, когда вы изменяете содержимое из data, указатель на первый элемент data остается прежним (data все еще существует в той же ячейке памяти). Итак, все ваши указатели ссылаются на одно и то же хранилище, и вы продолжаете перезаписывать содержимое хранилища.

Но тогда почему ваша программа работает, когда вы предоставляете буквенные строки? Потому что каждая литеральная строка в C гарантированно существует на протяжении всей жизни программы и имеет уникальный адрес. (Существует незначительное исключение: если вы используете литеральную строку в своей программе более одного раза, это может означать или не ссылаться на одну и ту же память.)

Итак, вы должны динамически распределять память для firstName, lastName и phoneNumber членов ваших узлов и не забывать освобождать их, когда закончите с этим.

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() для firstName, lastName и phoneNumber, но вам все равно нужно копировать данные, используя strcpy(). (Чтобы узнать причину, перейдите по ссылке выше.) Ваша соответствующая функция freeEntry() также не должна освобождать этих членов.

Теперь для остальной части вашей программы. Ваш способ найти завершающий символ новой строки работает, но вы можете упростить его, используя strchr() стандартную функцию C. Вызов в вашем случае будет выглядеть так:

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

Наконец, когда вы исправите все вышеперечисленное, вы обнаружите, что в вашей телефонной книге вы получаете последнюю запись дважды . В C feof() не сообщает вам, находитесь ли вы в конце файла: он сообщает, что последняя попытка чтения из файла не удалась, потому что вы были в конце файла.

1 голос
/ 16 января 2010

Ваша bookNode структура содержит указатели на память. Ваша функция addEntry помещает копию этих указателей в список, но память, на которую они указывают, по-прежнему принадлежит вызывающей стороне: фактически это массивы first, last и num, которые вы объявляете main, что затем вы перезапишете на следующей итерации цикла.

Что нужно сделать вашей функции addEntry вместо копирования входных указателей, так это выделить достаточно памяти для строк, скопировать входные данные в эту память и сохранить указатели на копии. Вам также необходимо убедиться, что вы освободили всю выделенную память, как только закончили с ней.

...