C Перезаписать существующую запись в файле

Я пишу программу, которая имеет структуру для банковских клиентов (BankAccount), состоящую из 3 элементов: номер счета, имя клиента и банковский баланс. У меня уже есть файл с записями, и у меня есть функция для изменения баланса. Однако я не могу этого сделать. Баланс остается прежним. Я думаю, что мой fseek() неверен, но я не уверен, как и почему. Я использовал fflush(stdin) в некоторых местах, где это может не потребоваться, но я не думаю, что это связано с проблемой. Я прокомментировал, чтобы вы знали мою логику, на случай, если в моих концепциях возникнет какое-то недопонимание. Вот код:

 void modify(){

    int account_number;
    FILE *ptr;
    BankAccount account;

    ptr = fopen("account.txt", "r+");
    printf("Enter account number: ");
    fflush(stdin);
    scanf("%d", &account_number);

    while (!feof(ptr)) // To search the whole "account.txt" file.
    {
        fread(&account, sizeof(BankAccount), 1, ptr); //brings record into memory
        if (account.account_number == account_number){ // if record's account number is same as account number entered by user above
            printf("***Account found***\n\nAccount number: %d\nAccount name: %s\nAccount balance: %.2f\n", account.account_number, account.name, account.balance);
            printf("\nEnter new balance: ");
            fflush(stdin);
            scanf("%f", &account.balance); // rewrites account's balance in memory
            fseek(ptr, -sizeof(BankAccount), SEEK_CUR); //pointer seeked to the beginning of the record to overwrite it with the one in memory
            fwrite(&account,sizeof(BankAccount), 1, ptr); // record overwritten
            return;

        }
    }


    printf("Account not found\n");
    fflush(stdin);
    getch();
}

Вот весь файл .cpp моего проекта на случай, если вы захотите его запустить: Исходный код . Буду признателен за руководство. Заранее спасибо.


person mmahmad    schedule 22.12.2013    source источник
comment
Файлы C не имеют записей (или каких-либо других структур). Это просто поток (или последовательность) байтов. Рассматривали ли вы GDBM или SQlite ? В какой операционной системе работает ваш код?   -  person Basile Starynkevitch    schedule 22.12.2013
comment
@BasileStarynkevitch Я первокурсник, изучаю CS. Мы только начали изучать FileIO на C, поэтому я понятия не имею о GDBM или SQlite (хотя я думаю, что они как-то связаны с базами данных и прочим?) Работает под управлением Windows 8.   -  person mmahmad    schedule 22.12.2013
comment
@mbrach Да, я могу видеть Найдена учетная запись вместе с данными учетной записи. Я также могу ввести данные для нового баланса, но баланс не перезаписывается, когда я снова распечатываю запись (для этого есть другая функция). И я извиняюсь за бинарный материал, не понимаю этого. Что именно вы подразумеваете под чтением/записью в двоичном формате?   -  person mmahmad    schedule 22.12.2013
comment
while(!feof(ptr)) предупреждение о злоупотреблении... в этом цикле while говорится: Пока мы не прочитали _past EOF, и при чтении не возникло ошибок, do ..._   -  person Elias Van Ootegem    schedule 22.12.2013
comment
@EliasVanOotegem Ммм... я не понимаю. Это неправильно использовать? Я хочу, чтобы цикл выполнялся до тех пор, пока не будет account.number == account_number. Для этого он должен искать все записи в файле, верно? Есть ли другой способ сделать это?   -  person mmahmad    schedule 22.12.2013
comment
См. Почему while (!feof(file)) всегда неправильно для обсуждения того, почему ваша структура цикла неверна. . Вы не ошибаетесь, проверяя операции открытия или чтения; это всегда плохая идея.   -  person Jonathan Leffler    schedule 22.12.2013


Ответы (1)


Проблема может заключаться в вызове fseek():

fseek(ptr, -sizeof(BankAccount), SEEK_CUR);

Возвращаемое значение из sizeof() является беззнаковым типом; отрицание этого будет очень большим числом. Технически это неверно (fseek() должен получить long, а не size_t). Однако, если sizeof(size_t) == sizeof(long), вам это сойдет с рук (у меня это работает).

Другой аспект проблемы может заключаться в том, что вы не закрываете файл до того, как вернетесь (независимо от того, найдете ли вы запись или нет). Это, безусловно, утечка памяти; это также может повлиять на то, как данные записываются на диск. Это, вероятно, основная причина ваших проблем. У вас есть еще одна функция, которая открывает файл для чтения данных, чтобы увидеть изменения, но, поскольку файл не закрыт, данные не были записаны на диск. Примечание. Поскольку исходный код теперь доступен, это является причиной проблем.

Поскольку вы не показываете нам структуру данных, другой проблемой может быть несоответствие типа float и double для члена balance; в равной степени единственная проблема может заключаться в том, что float является неподходящим типом для остатков на счетах (например, он не может надежно представить остатки выше примерно 100 000,00 долларов США с точностью до цента — например, ввод 199999,99 отображается как 199999,98).

Примечание. В современных версиях Linux и в Windows, fflush(stdin) — это определенная операция (и определенное поведение разумно и полезно). Согласно стандарту C и POSIX, это дает неопределенное поведение. Будьте осторожны при его использовании — помните, что это не переносимая операция.

Преобразование в SSCCE (Короткий, автономный, правильный пример), очень небольшая модификация вашего кода (добавление fclose()) работает на меня:

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

typedef struct BankAccount
{
    int account_number;
    char   name[20];
    float balance;
} BankAccount;

static void modify(void)
{
    int account_number;
    FILE *ptr;
    BankAccount account;

    ptr = fopen("account.txt", "r+");
    printf("Enter account number: ");
    fflush(stdin);
    scanf("%d", &account_number);

    while (!feof(ptr))
    {
        fread(&account, sizeof(BankAccount), 1, ptr);
        printf("***Account read***(%d: %s: %.2f)\n", 
               account.account_number, account.name, account.balance);
        if (account.account_number == account_number)
        {
            printf("***Account found***\n\nAccount number: %d\nAccount name: %s\nAccount balance: %.2f\n", account.account_number, account.name, account.balance);
            printf("\nEnter new balance: ");
            fflush(stdin);
            scanf("%f", &account.balance);
            fseek(ptr, -sizeof(BankAccount), SEEK_CUR);
            fwrite(&account, sizeof(BankAccount), 1, ptr);
            fclose(ptr);
            return;
        }
    }

    printf("Account not found\n");
    fflush(stdin);
    fclose(ptr);
}

static void write(void)
{
    FILE *fp = fopen("account.txt", "w");
    if (fp == 0)
    {
        fprintf(stderr, "Create file failed\n");
        exit(1);
    }
    static const BankAccount data[] =
    {
        { 1, "His", 20.00 },
        { 2, "Hers", 2000.00 },
        { 3, "Theirs", 1.00 },
    };
    if (fwrite(data, sizeof(data), 1, fp) != 1)
    {
        fprintf(stderr, "Write file failed\n");
        exit(1);
    }
    fclose(fp);
}

static void read(void)
{
    FILE *fp = fopen("account.txt", "r");
    if (fp == 0)
    {
        fprintf(stderr, "Open file failed\n");
        exit(1);
    }
    BankAccount ac;
    while (fread(&ac, sizeof(ac), 1, fp) == 1)
    {
        printf("A/C: %4d %-20s  %8.2f\n", ac.account_number, ac.name, ac.balance);
    }
    fclose(fp);
}

int main(void)
{
    write();
    read();
    modify();
    read();
    return 0;
}

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

$ gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
      -Wold-style-definition -Werror ba.c -o ba

При запуске показывает:

A/C:    1 His                      20.00
A/C:    2 Hers                   2000.00
A/C:    3 Theirs                    1.00
Enter account number: 2
***Account read***(1: His: 20.00)
***Account read***(2: Hers: 2000.00)
***Account found***

Account number: 2
Account name: Hers
Account balance: 2000.00

Enter new balance: 4000
A/C:    1 His                      20.00
A/C:    2 Hers                   4000.00
A/C:    3 Theirs                    1.00

Обратите внимание на правильную форму проверки того, достигли ли вы конца файла в функции read(). Если вы звоните feof(), вы делаете это неправильно в 99,9% случаев.

person Jonathan Leffler    schedule 22.12.2013
comment
Спасибо за отладку! Я совершенно забыл fclose(), хотя и использовал ее в других своих функциях. Заставил меня осознать, насколько это важно. Кроме того, я не знал, что мой способ проверки EOF был неправильным, спасибо и за это. Я использую команду fflush(stdin) всякий раз, когда я сканирую f() или gets(), в противном случае это вызывает проблемы. Я использовал его здесь чаще, чем следовало бы, так что спасибо, что предупредил меня и об этом тоже. - person mmahmad; 22.12.2013
comment
Пожалуйста. Обратите внимание, что есть некоторые другие мелкие проблемы, такие как повторение имени файла в каждой из трех функций; который должен быть определен один раз в переменной или константе, и переменная передается различным функциям. В моем коде используются write() и read(); они также являются именами функций, определенными в POSIX. Написанный код безопасен (и работает в POSIX-системах), но на самом деле должен использовать другие имена. Я не обновлял ваш код для проверки ошибки вызова scanf(); это должно быть проверено — все операции ввода (и открытия) должны быть проверены. Будьте осторожны с использованием getch() без подсказки. - person Jonathan Leffler; 22.12.2013