Запись/чтение структруры в бинарный (двоичный) файл

Тема в разделе "Borland C++ Builder & Kylix", создана пользователем BattleMage, 18 июл 2007.

  1. BattleMage

    BattleMage Гость

    Как структуру, у которой 5 полей типа char [20] записать в бинарный файл. Точнее записал, но вот не считывается...

    Структура объявлена так:
    struct kniga
    {
    char nazvanie[20];
    char avtor[20];
    char zhanr[20];
    char izdatelstvo[20];
    char god_izdaniya[20];
    };
    struct kniga *ptr;

    Вот так записал:
    FILE *db;
    db=fopen("DataBase.xxx","wb");
    fwrite((char*)&(*ptr),sizeof(*ptr),1,db);
    fclose(db);

    Вот так считываю. То что считал хочу записать в ячейки StringGrid-a:
    db=fopen("DataBase.xxx","rb");
    fread((char*)&ptr,sizeof(ptr),1,db);
    while (fgetc(db)!=EOF)
    {
    StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(StringGrid1->RowCount);
    StringGrid1->Cells[1][StringGrid1->RowCount]=ptr->nazvanie;
    StringGrid1->Cells[2][StringGrid1->RowCount]=ptr->avtor;
    StringGrid1->Cells[3][StringGrid1->RowCount]=ptr->zhanr;
    StringGrid1->Cells[4][StringGrid1->RowCount]=ptr->izdatelstvo;
    StringGrid1->Cells[5][StringGrid1->RowCount]=ptr->god_izdaniya;
    StringGrid1->RowCount++;
    }
    fclose(db);

    Что я не так делаю? Либо записываю, либо читаю, либо и то и то :)
     
  2. Pasha

    Pasha Гость

    Намутил с указателями.
    Код (Text):
    // выделили память
    ptr = new kniga();
    // записали
    fwrite((char*)ptr,sizeof(kniga),1,db);
    // прочитали
    fread((char*)ptr,sizeof(kniga),1,db);
    // удалили
    delete ptr;
     
  3. BattleMage

    BattleMage Гость

    Pasha, написал как ты сказал. Скомпилировалось нормально, запустилось. Ввел некоторые данные - он записал их в бинарный файл. Потом ещё раз запустить решил. Думал, что покажет в ячейках StringGrid-a то что я прошлый раз вводил. Не тут то было... Вылетела ошибка: "EAccessViolation."
    Что это значит?

    Мне кажется я не так считываю...

    db=fopen("DataBase.xxx","rb");
    rewind(db);
    fread((char*)ptr,sizeof(kniga),1,db);
    while (fgetc(db)!=EOF)
    {
    ptr=(struct kniga*)malloc(sizeof(struct kniga));
    StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(StringGrid1->RowCount);
    StringGrid1->Cells[1][StringGrid1->RowCount]=ptr->nazvanie;
    StringGrid1->Cells[2][StringGrid1->RowCount]=ptr->avtor;
    StringGrid1->Cells[3][StringGrid1->RowCount]=ptr->zhanr;
    StringGrid1->Cells[4][StringGrid1->RowCount]=ptr->izdatelstvo;
    StringGrid1->Cells[5][StringGrid1->RowCount]=ptr->god_izdaniya;
    StringGrid1->RowCount++;
    free(ptr);
    }
    fclose(db);
     
  4. Pasha

    Pasha Гость

    Для: BattleMage
    Не проверял (нет у меня установленных плюсов), но примерно так:
    Код (Text):
    db=fopen("DataBase.xxx","rb");
    ptr=new kniga();
    while (!feof(db))
    {
    fread(ptr,sizeof(kniga),1,db);
    StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(StringGrid1->RowCount);
    StringGrid1->Cells[1][StringGrid1->RowCount]=ptr->nazvanie;
    StringGrid1->Cells[2][StringGrid1->RowCount]=ptr->avtor;
    StringGrid1->Cells[3][StringGrid1->RowCount]=ptr->zhanr;
    StringGrid1->Cells[4][StringGrid1->RowCount]=ptr->izdatelstvo;
    StringGrid1->Cells[5][StringGrid1->RowCount]=ptr->god_izdaniya;
    StringGrid1->RowCount++;
    }
    fclose(db);delete ptr;
    или так
    Код (Text):
    db=fopen("DataBase.xxx","rb");
    kniga buff;
    while (!feof(db))
    {
    fread(&buff, sizeof(kniga),1,db);
    StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(StringGrid1->RowCount);
    StringGrid1->Cells[1][StringGrid1->RowCount]=ptr.nazvanie;
    [snip]
    }
    fclose(db);
     
  5. Over

    Over Well-Known Member

    Регистрация:
    4 июл 2007
    Сообщения:
    116
    Симпатии:
    0
    Вот ты в этом коде сначала делаешь fread, а потом в цикле уже делаешь malloc. Делать надо наоборот. Сначала выделить память, а потом в неё уже производить считывание.
    И ещё. Выделяешь память странно как-то. Нужно так: ptr = (kniga *) malloc(sizeof(kniga))
     
  6. BattleMage

    BattleMage Гость

    1) БОЛЬШОЕ спасибо за советы. Сейчас попробую сделать...

    2) Over, а что есть разница между строчками:

    ptr = (kniga *) malloc(sizeof(kniga))
    и
    ptr=(struct kniga*)malloc(sizeof(struct kniga));

    приколист :)
     
  7. BattleMage

    BattleMage Гость

    Так. Почти все как надо. Только вот один недочет:
    добавил очередную запись. закрыл программу. запускаю, а запись которая была добавлена в последний раз два раза встречается в StringGrid - e (на последней и предпоследней строчке). Вот только не понятно: он просто не так как-то в StringGrid выводит это или в текстовом файле такая "каша"...

    И ещё. Если файл пуст. То StringGrid состоит из строки, где в поле номер записано "1", а в поле название "$u"

    сейчас у меня такой код:

    FILE *db;
    //установка выравнивания структуры на 1
    #pragma pack(push) // сохранение текущего выравнивания
    #pragma pack(1)
    struct kniga
    {
    char nazvanie[20];
    char avtor[20];
    char zhanr[20];
    char izdatelstvo[20];
    char god_izdaniya[20];
    };
    struct kniga *ptr;
    #pragma pack(pop) // восстановление предыдушего состояния
    //---------------------------------------------------------------------------
    __fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
    {
    StringGrid1->Cells[0][0]="№";
    StringGrid1->Cells[1][0]="Название";
    StringGrid1->Cells[2][0]="Автор";
    StringGrid1->Cells[3][0]="Жанр";
    StringGrid1->Cells[4][0]="Издательство";
    StringGrid1->Cells[5][0]="Год издания";
    db=fopen("DataBase.xxx","rb");
    rewind(db);
    ptr=(struct kniga*)malloc(sizeof(struct kniga));
    while (!feof(db))
    {
    fread(ptr,sizeof(kniga),1,db);
    StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(StringGrid1->RowCount);
    StringGrid1->Cells[1][StringGrid1->RowCount]=ptr->nazvanie;
    StringGrid1->Cells[2][StringGrid1->RowCount]=ptr->avtor;
    StringGrid1->Cells[3][StringGrid1->RowCount]=ptr->zhanr;
    StringGrid1->Cells[4][StringGrid1->RowCount]=ptr->izdatelstvo;
    StringGrid1->Cells[5][StringGrid1->RowCount]=ptr->god_izdaniya;
    StringGrid1->RowCount++;
    }
    fclose(db);
    free(ptr);
    }
    //---------------------------------------------------------------------------

    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
    if ((Edit1->Text=="")||(Edit2->Text=="")||(Edit3->Text=="")||(Edit4->Text=="")||(Edit5->Text==""))
    ShowMessage("Нужно заполнить все поля!");
    else
    {
    ptr=(struct kniga*)malloc(sizeof(struct kniga));
    strcpy(ptr->nazvanie,Edit1->Text.c_str());
    strcpy(ptr->avtor,Edit2->Text.c_str());
    strcpy(ptr->zhanr,Edit3->Text.c_str());
    strcpy(ptr->izdatelstvo,Edit4->Text.c_str());
    strcpy(ptr->god_izdaniya,Edit5->Text.c_str());
    StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(StringGrid1->RowCount);
    StringGrid1->Cells[1][StringGrid1->RowCount]=ptr->nazvanie;
    StringGrid1->Cells[2][StringGrid1->RowCount]=ptr->avtor;
    StringGrid1->Cells[3][StringGrid1->RowCount]=ptr->zhanr;
    StringGrid1->Cells[4][StringGrid1->RowCount]=ptr->izdatelstvo;
    StringGrid1->Cells[5][StringGrid1->RowCount]=ptr->god_izdaniya;
    StringGrid1->RowCount++;
    db=fopen("DataBase.xxx","a+b");
    fwrite(ptr,sizeof(kniga),1,db);
    fclose(db);
    Edit1->Text="";
    Edit2->Text="";
    Edit3->Text="";
    Edit4->Text="";
    Edit5->Text="";
    free(ptr);
    }
    }
     
  8. Over

    Over Well-Known Member

    Регистрация:
    4 июл 2007
    Сообщения:
    116
    Симпатии:
    0
    Хорошо. Вот ты берёшь просто так и вызываешь функцию открытия файла. А прикинь, если файла вообще не будет существовать? Тебе надо вставить проверку на ошибки. Что-то типа такого:
    Код (Text):
    if ((db=fopen("database.xxx","rb")) == NULL)
    { //Выдать сообщение об ошибке
    return -1;  
    }
    Дальше. Когда выделил память, её нужно обнулить. Тогда в ней не будет сожержаться мусор. И потом ещё в цикле проверять значение, возвращаемое функцией fread() (не помню, вроде возвращает количество считанных байт). Если файл оказался пуст, то данные не считаются, и тогда в StringGrid не нужно ничего добавлять.

    Разницы может и нет, но что-то я до сих пор не встречал, чтоб писали как ты: (struct kniga*)... Это просто банально лишний код.
     
  9. European

    Регистрация:
    4 сен 2006
    Сообщения:
    2.580
    Симпатии:
    0
    Для: BattleMage
    Вы уж простите, что я в Тулу со своим самоваром... Но зачем плодить кашу из сишного и плюс-плюсного кода? Неуженли нельзя использовать new и delete вместо malloc и free, неужели нет нормальных функций для работы с файлами?
     
  10. Pasha

    Pasha Гость

    Для: European
    Нормальные функции ... ты еще файлы отображаемые в память предложи использовать - ведь это красивее, чем тупо читать по одной структуре в буффер :) Пусть человек сначала с основами разберется, а стремление к порядку и красоте кода ему привьют на первой же серьезной работе.
     
  11. European

    Регистрация:
    4 сен 2006
    Сообщения:
    2.580
    Симпатии:
    0
    <!--QuoteBegin-Pasha+19:07:2007, 11:53 -->
    <span class="vbquote">(Pasha @ 19:07:2007, 11:53 )</span><!--QuoteEBegin-->ты еще файлы отображаемые в память предложи использовать - ведь это красивее, чем тупо читать по одной структуре в буффер
    [snapback]72687" rel="nofollow" target="_blank[/snapback]​
    [/quote]
    А вот палку перегибать не надо!
    <!--QuoteBegin-Pasha+19:07:2007, 11:53 -->
    <span class="vbquote">(Pasha @ 19:07:2007, 11:53 )</span><!--QuoteEBegin-->Пусть человек сначала с основами разберется
    [snapback]72687" rel="nofollow" target="_blank[/snapback]​
    [/quote]
    Ну так все в Turbo С и писать приложения под консоль.
    Городить огород типа:
    Код (Text):
    struct kniga *ptr;
    ptr=(struct kniga*)malloc(sizeof(struct kniga));
    вместо того чтобы написать
    Код (Text):
    kniga* ptr = new kniga;
    это изучение основ? Учится можно сразу, а не ждать нормальной работы, на которую из-за такого кода в тестовом задании могут и не взять
     
  12. Kmet

    Kmet Well-Known Member
    Java Team

    Регистрация:
    25 май 2006
    Сообщения:
    1.018
    Симпатии:
    1
    в случае тестового задания, имхо, скорее бы обратили бы внимание не на malloc, а на то, что использование динамического выделенения памяти в данном случае является абсолютно излишним.
     
  13. Pasha

    Pasha Гость

    Для: Kmet
    А еще скорее всего обратили бы внимание на использование текстового файла вместо нормальной базы данных, слишком жесткого ограничения на строки (название книги до 20 символов), код копирования строк без проверки длины (strcpy(ptr->avtor,Edit2->Text.c_str()):), имена контролов (Edit1,2,3,4)...
    С другой стороны, всегда есть вероятность, что это не тестовое задание, а задача по "программированию" с условием "чтение и запись данных в бинарный (двоичный) файл с использованием динамического выделения памяти.".
     
  14. Over

    Over Well-Known Member

    Регистрация:
    4 июл 2007
    Сообщения:
    116
    Симпатии:
    0
    Для European:
    Функция new, если посмотришь исходники, вызывает напрямую функцию malloc. delete в общем-то то же самое. Поэтому с программной точки зрения разницы нет. Если уже только гнаться за красивостью кода.
     
  15. European

    Регистрация:
    4 сен 2006
    Сообщения:
    2.580
    Симпатии:
    0
    Для: Over
    Иногда лучше жевать, чем говорить.
    1 - new это не функция, а предопределенный оператор языка C++. Надеюсь разница понятна!
    2 - new и delete могут использовать malloc и free, но нет никакой гарантии этого.
    3 (и самое главное) - malloc и free ничего не знают о конструкторах и деструкторах классов соответсвенно. malloc выделяет неициализированную память. Интересно, как бы ты передал значение конструктору используя malloc. Далее, как ты выражаешься: "delete в общем-то то же самое"
    4 - использование new и delete вперемешку с malloc и free приводит к непредсказуемым последствиям. Т.е. используя оба способа выделения памяти необходимо помнить о том, как была выделена память
    5 - избегать использования malloc и free рекомендуют практически все нормальные авторы нормальных книг. Надеюсь фамилии Страуструпа, Майерса и Саттера тебе что-то говорят и ты прочитал хотя бы парочку их трудов. Если нет, то продолжение спора бесполезно
     
  16. Kmet

    Kmet Well-Known Member
    Java Team

    Регистрация:
    25 май 2006
    Сообщения:
    1.018
    Симпатии:
    1
    Для: Pasha
    судить о целесообразности использования БД в данном мы не можем, так как толком задания не знаем. ограничение длины строки, можно тоже списать на ТЗ. проверка длины строки может производится на уровне компонента, что конечно же коряво, но все же. именна контролов можно списать на не соответстивие code convention, что конечно же плохо, но не так страшно. malloc\free в данном конкретном случае вполне допустимо. И дело не в том, что я не уважаю Страутсрапа и КО, просто если не обращать внимание на VCL, то код вполне соотвествует С-style.

    С другой стороны, понимание критериев использования динамической памяти и дисциплина при работе с ней критична для любого С/C++ программиста. и не важно тут, это тестовое задание или просто задача.
     
  17. BattleMage

    BattleMage Гость

    Ну вы блин даёте... Вот только сейчас решил новые сообщения форума прочесть... Таким ублюдком начинаешь себя ощущать B)

    Вот у меня ещё вопрос. Вроде написал как вы сказали. Ну и что-то от себя добавил. Почему при отсутствии файла DataBase.xxx (заполнение всех эдитов) вылетает сообщение об ошибке такое:
    "Access violation at address 32657E9A in module 'CC3260MT.DLL'. Write of address 00000004.",
    а если файл присутствует (пуст), то пишет:
    "EAccessViolation."

    Код теперь такой (меняется буквальные каждые 5 минут :) ) :

    //---------------------------------------------------------------------------
    #include <vcl.h>
    #pragma hdrstop
    #include<stdio.h>
    #include "Unit1.h"
    //---------------------------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    TForm1 *Form1;
    FILE *db;
    //установка выравнивания структуры на 1
    #pragma pack(push) // сохранение текущего выравнивания
    #pragma pack(1)
    struct kniga
    {
    int nomer;
    char nazvanie[40];
    char avtor[40];
    char zhanr[40];
    char izdatelstvo[40];
    char god_izdaniya[10];
    };
    kniga *ptr;
    #pragma pack(pop) // восстановление предыдушего состояния
    //---------------------------------------------------------------------------
    __fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
    {
    StringGrid1->Cells[0][0]="№";
    StringGrid1->ColWidths[0]=30;
    StringGrid1->Cells[1][0]="Название";
    StringGrid1->Cells[2][0]="Автор";
    StringGrid1->Cells[3][0]="Жанр";
    StringGrid1->Cells[4][0]="Издательство";
    StringGrid1->ColWidths[5]=73;
    StringGrid1->Cells[5][0]="Год издания";
    if ((db=fopen("DataBase.xxx","rb"))==NULL) ShowMessage("Файл базы данных DataBase.xxx отсутствует!");
    else
    {
    rewind(db);
    ptr=(kniga*)malloc(sizeof(kniga));
    ptr=NULL;
    while (!feof(db))
    {
    fread(ptr,sizeof(kniga),1,db);
    ptr->nomer=StringGrid1->RowCount;
    StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(ptr->nomer);
    StringGrid1->Cells[1][StringGrid1->RowCount]=ptr->nazvanie;
    StringGrid1->Cells[2][StringGrid1->RowCount]=ptr->avtor;
    StringGrid1->Cells[3][StringGrid1->RowCount]=ptr->zhanr;
    StringGrid1->Cells[4][StringGrid1->RowCount]=ptr->izdatelstvo;
    StringGrid1->Cells[5][StringGrid1->RowCount]=ptr->god_izdaniya;
    StringGrid1->RowCount++;
    }
    fclose(db);
    free(ptr);
    }
    }
    //---------------------------------------------------------------------------

    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
    if ((Edit1->Text=="")||(Edit2->Text=="")||(Edit3->Text=="")||(Edit4->Text=="")||(Edit5->Text==""))
    ShowMessage("Нужно заполнить все поля!");
    else
    {
    ptr=(kniga*)malloc(sizeof(kniga));
    ptr=NULL;
    strcpy(ptr->nazvanie,Edit1->Text.c_str());
    strcpy(ptr->avtor,Edit2->Text.c_str());
    strcpy(ptr->zhanr,Edit3->Text.c_str());
    strcpy(ptr->izdatelstvo,Edit4->Text.c_str());
    strcpy(ptr->god_izdaniya,Edit5->Text.c_str());
    ptr->nomer=StringGrid1->RowCount;
    StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(ptr->nomer);
    StringGrid1->Cells[1][StringGrid1->RowCount]=ptr->nazvanie;
    StringGrid1->Cells[2][StringGrid1->RowCount]=ptr->avtor;
    StringGrid1->Cells[3][StringGrid1->RowCount]=ptr->zhanr;
    StringGrid1->Cells[4][StringGrid1->RowCount]=ptr->izdatelstvo;
    StringGrid1->Cells[5][StringGrid1->RowCount]=ptr->god_izdaniya;
    StringGrid1->RowCount++;
    db=fopen("DataBase.xxx","a+b");
    fwrite(ptr,sizeof(kniga),1,db);
    fclose(db);
    Edit1->Text="";
    Edit2->Text="";
    Edit3->Text="";
    Edit4->Text="";
    Edit5->Text="";
    free(ptr);
    }
    }
     
  18. European

    Регистрация:
    4 сен 2006
    Сообщения:
    2.580
    Симпатии:
    0
    Я полагаю причина в таком коде:
    Код (Text):
    ptr=(kniga*)malloc(sizeof(kniga));
    ptr=NULL;
    strcpy(ptr->nazvanie,Edit1->Text.c_str());
    Ты выделил память и получил указатель. Затем ты установил указатель в NULL и пытаешься к нему обращаться. Естественно будет ошибка. Кстати, если хочешь могу сделать парочку замечаний по "красоте кода"
     
  19. BattleMage

    BattleMage Гость

    Это кстати я уже исправил. Но все равно спасибо...
    Вот ещё что. Он повторяет запись последнюю. Причем полностью (от первого до последнего поля). Я думаю можно исправить это заполнением оперделённых ячеек StringGrid-а. да? Но вот если файл существует, но в нем ничего нет (размер равен 0), то это уже не пройдет. Так как он начнёт затирать уже нужные данные. Как сделать проверку на размер файла? типа если 0, то одни, если нет - то другие...

    Замечания давай. С удовольствием послушаю...
     
  20. European

    Регистрация:
    4 сен 2006
    Сообщения:
    2.580
    Симпатии:
    0
    <!--QuoteBegin-BattleMage+20:07:2007, 10:28 -->
    <span class="vbquote">(BattleMage @ 20:07:2007, 10:28 )</span><!--QuoteEBegin-->Вот ещё что. Он повторяет запись последнюю.
    [snapback]72779" rel="nofollow" target="_blank[/snapback]​
    [/quote]
    Я так понял, что повторяет последнюю запись при создании формы? Хм-м, попробуй так:
    Код (Text):
    __fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
    {
    ...
    if ((db=fopen("DataBase.xxx","rb"))==NULL) ShowMessage("Файл базы данных DataBase.xxx отсутствует!");
    else
    {
    rewind(db);
    ptr=(kniga*)malloc(sizeof(kniga));
    while (!feof(db))
    {
    fread(ptr,sizeof(kniga),1,db);
    if( !ferror( db ) )
    {
    ptr->nomer=StringGrid1->RowCount;
    StringGrid1->Cells[0][StringGrid1->RowCount]=IntToStr(ptr->nomer);
    ...
    StringGrid1->RowCount++;
    }
    }
    fclose(db);
    free(ptr);
    }
    }
    P.s. А разве для добавления строки в TStringGrid достаточно сделать StringGrid1->RowCount++? И по умолчанию уже есть одна строка? Что-то я не помню уже...
     
Загрузка...

Поделиться этой страницей