1 Востаннє редагувалося Faltfromoss (22.07.2014 22:21:23)

Тема: Запис та зчитування структури в/з файлу

Зіткнувся за такою проблемкою. Є декілька ділянків коду.

Структура "Абонент":

struct Subscriber
{
   char FIO [50];
   int YearOfBirth;
   char Town [20];
   char Number [15];
 
   Subscriber * left, * right, * parent;
};

А також клас Tree - бінарне дерево, яке сортує ці структури.
Клас Tree має метод SaveInFile () - збереження усіх записів в файл:

void Tree::SaveInFile (Subscriber * Node, FILE * F)
{
    if(Node != 0)
    {
        SaveInFile (Node->left, F);
            fwrite (Node, sizeof (Subscriber), 1, F);
        SaveInFile (Node->right, F);
    }
}

А також відповідний метод зчитування із файлу ReadOutFile ():

void Tree::ReadOutFile (FILE * F)
{
    while (!feof (F))
    {
        Subscriber *Node = new Subscriber;
        fread (Node, sizeof (Subscriber), 1, F);
        Insert (Node); // вставляет указатель на считанную структуру в дерево
    }
}

Метод SaveInFile в свою чергу викликається в функції SaveBase ():

Прихований текст
void  SaveBase (Tree &T)        //сохранение базы в файл
{
    int key = 0;
    // записать в существующий файл или создать новый?
    do
    {
        system ("cls");     
        cout<<endl<<"\tЗаписать в существующий файл (1) или создать новый (2) ?: ";
        key = _getch();
    } while (key !=49 && key!=50);
    
    FILE *Base;
    char buffer [200];
 
    switch (key)
    {
    case '1':                   //Если в существующий
        system ("cls");         
        cout<<endl<<"\tВведите путь или имя файла: ";
        gets_s (buffer);
        strcat (buffer, ".txt");
        if(_access(buffer, 00) == -1)
        {
            cout<<endl<<"\tУказан неверный путь или имя файла";
            return;
        }
        
        if (!(Base = fopen (buffer, "w")))
        {
            cout<<endl<<endl<<"\tОшибка при открытии файла"<<endl<<endl;
            return;
        }
        T.SaveInFile (T.GetRoot(), Base);
        fclose (Base);
        cout<<endl<<endl<<"\t\tФайл записан успешно!"<<endl;
        break;
    case '2':           //если новый файл
        system ("cls");         
        cout<<endl<<"\tВведите имя файла: ";
        gets_s (buffer);
        strcat (buffer, ".txt");
        if (!(Base = fopen (buffer, "w")))
        {
            cout<<endl<<endl<<"\tОшибка при открытии файла"<<endl;
            return;
        }
        T.SaveInFile (T.GetRoot(), Base);
        fclose (Base);
        cout<<endl<<endl<<"\t\tФайл записан успешно!"<<endl;
        break;
    }
}

Та віповідна функція ReadBase ():

Прихований текст
void  ReadBase (Tree &T)        //чтение базы из файла
{
    FILE *Base;
    char buffer [200];
    if (T.GetRoot ())
        T.Del ();
    system ("cls");         
    cout<<endl<<"\tВведите путь или имя файла: ";
    gets_s (buffer);
    strcat (buffer, ".txt");
    if(_access(buffer, 00) == -1)
    {
        cout<<endl<<"\tУказан неверный путь или имя файла"<<endl;
        return;
    }
        
    if (!(Base = fopen (buffer, "r")))
    {
        cout<<endl<<endl<<"\tОшибка при открытии файла"<<endl<<endl;
        return;
    }
    
    T.ReadOutFile (Base);
    fclose (Base);
    cout<<endl<<endl<<"\t\tФайл считан успешно!"<<endl;
    
}

Проблема в тому, що коли я зчитую послідовно записи із файлу, требя якимось чином закінчити зчитування, що зазвичай робиться за допомогою команди feof (), як тут:

while (!feof (F))
    {
        Subscriber *Node = new Subscriber;
        fread (Node, sizeof (Subscriber), 1, F);
        Insert (Node);
    }

Але справа в тому, що дані до файлу записуються абсолютно всі, включно із невикористаними байтами із символьних масивів FIO, Town, Number. І в підсумку, після зчитування всієї необхідної інформації, він вкінці дописує решту сміття з файлу. Ну ось приклад.

1) Ввожу інфу:

Прихований текст

http://i67.сайт-злодій/big/2014/0723/fc/73ea6e4d8c0d8fed6d963e4e603557fc.png

2) Виводжу на екран:

Прихований текст

http://i67.сайт-злодій/big/2014/0723/b6/64aeae6aa2736759b3238a1b1f6871b6.png

3) Зберігаю в файл:

Прихований текст

http://i67.сайт-злодій/big/2014/0723/e2/7319f0cebb47893ae6e63b061e800de2.png

4) Зчитую із файлу:

Прихований текст

http://i67.сайт-злодій/big/2014/0723/b1/3132271f386dc9d933377b4bd97a5eb1.png

5) Виводжу зчитану інформацію на екран:

Прихований текст

http://i67.сайт-злодій/big/2014/0723/76/39f55773e9e1cda7efed791926f6b776.png

Загалом я розумію причину, але не можу збагнути, як зробити, щоб зчитувалося все, без сміття. Записувати і зчитувати построково шляхом fputs () і fgets () якось нерозумно і накладно як на мене. Може я якось невірно описав функції запису і зчитування?

2 Востаннє редагувалося koala (22.07.2014 22:56:14)

Re: Запис та зчитування структури в/з файлу

Ви неправильно зрозуміли причину. Ваші рядки записуться коректно (так, там "зайві" байти, але їх рівно стільки, скільки в структурі), але ще є один некоректний рядок. Виникає він під час читання - тому що feof повертає true тільки після помилки читання, отака фіча в C. А першим він стає через сортування в Insert. Краще контролювати закінчення читання не feof, а кількістю байтів, і робити це окремою функцією:

Subscriber *readFromFile( FILE *F )
{
  Subscriber *result = new Subscriber;//ще питання, що швидше - створювати/знищувати чи копіювати...
  int bytesRead = fread ( result, sizeof (Subscriber), 1, F );
  if( bytesRead != sizeof( Subscriber ) )
  {
     delete result;
     result = 0;
  }
  return result;
}

ну а цикл тоді буде

Subscriber *Node;
while( ( Node = readFromFile( F ) ) != 0 )
{
  Insert( Node );
}

А ще краще, оскільки це плюси, використовувати файлові потоки і перевизначити << та >>.

Подякували: Faltfromoss1

3

Re: Запис та зчитування структури в/з файлу

koala написав:

Ви неправильно зрозуміли причину. Ваші рядки записуться коректно (так, там "зайві" байти, але їх рівно стільки, скільки в структурі), але ще є один некоректний рядок. Виникає він під час читання - тому що feof повертає true тільки після помилки читання, отака фіча в C. А першим він стає через сортування в Insert. Краще контролювати закінчення читання не feof, а кількістю байтів, і робити це окремою функцією:

Subscriber *readFromFile( FILE *F )
{
  Subscriber *result = new Subscriber;//ще питання, що швидше - створювати/знищувати чи копіювати...
  int bytesRead = fread ( result, sizeof (Subscriber), 1, F );
  if( bytesRead != sizeof( Subscriber ) )
  {
     delete result;
     result = 0;
  }
  return result;
}

ну а цикл тоді буде

Subscriber *Node;
while( ( Node = readFromFile( F ) ) != 0 )
{
  Insert( Node );
}

А ще краще, оскільки це плюси, використовувати файлові потоки і перевизначити << та >>.

Нарешті в мене все вдалося. Дуже дякую за допомогу. Спираючись на ваш варіант я таки дійшов висновку як правильно поставити умову при зчитуванні файлу. Однак у вашому коді теж є помилка, що мене і спантеличило.
Я спочатку хотів зробити згідно з вашим варіантом, але не став створювати нову функцію, а піредагував існуючу:

void Tree::ReadOutFile (FILE * F)
{
    while (1)
    {
        Subscriber *Node = new Subscriber;
        if (fread (Node, sizeof (Subscriber), 1, F) != sizeof (Subscriber)) return;
        Insert (Node);
    }
}

Однак при спробі зчитати файл і вивести результат на екран - програма нічого не виводила, хоча в файлі записи були. Прослідкувавши роботу функції за домогою відладчика побачив, що стрічка

if (fread (Node, sizeof (Subscriber), 1, F) != sizeof (Subscriber)) return;

просто не виконуються жодного разу і функція завершується, так і не зробивши свого діла з однієї простої причини - функція fread повертає не 104 байти, як це робить sizeof (Subscriber), а лише 1. Тобто, я дійшов висновку, що fread повертає не фактичну кількість зчитанних байтів, а кількість зчитаних елементів, які ми задаємо в 3-му аргументі функції fread! Тобто, якщо написати, наприклад так:

fread (Node, sizeof (Subscriber), 3, F)

то функція поверне 3 (звісно якщо всі 3 елементи будуть зчитані безпомилково). Ось такий вінегрет виходить. Ну виходячі із вищеприведених висновків, було нетяжко написати правильну умову для виходу із циклу:

void Tree::ReadOutFile (FILE * F)
{
    while (1)
    {
        Subscriber *Node = new Subscriber;
        if (fread (Node, sizeof (Subscriber), 1, F) != 1) return;
        Insert (Node);
    }
}

І все запрацювало, тепер всі записи правильно зчитуються і правильно відображуються.

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

А з приводу файлових потоків, то ця тема в нашому матеріалі йде наступною, тому це ДЗ треба було зробити за допомогую С-шних функцій роботи з файлами

4

Re: Запис та зчитування структури в/з файлу

Так, вибачте. fread повертає не кількість байтів, а кількість елементів, я дійсно переплутав.