简体   繁体   中英

C Binary File not updating/reading

Below I have provided my code. I am to "deposit" money based on a provided account number. The name and account number and balance are all in the following 'accounts.dat' file.

Herman T Travis 3 $500
Sam L Travis 1 $40
Henry O Billiam 2 $6000

I am unsure if its the way I created my file (with a simple vi editor) or if it is a problem within my code, but when I run it and give the account number and balance to the program does not report the new balance in the file. Any suggestions?

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

typedef struct {
    char first[15];
    char middle[2];
    char last[15];
    float balance;
    long int acct;
} data;

int main() {
    int choice = -1, i = 0, n = 0;
    long int number;
    double amount;
    FILE *aPtr;
    data accounts[50];

    aPtr = fopen("accounts.dat", "ab+");/* Opens file for read/write; appends to file if exist */

    if (aPtr == NULL) {
        printf("File could not be opened");
        exit(1);
    }

    fseek(aPtr, 0, SEEK_SET); /* Moving pointer to start of file*/

    while (fread(accounts + 1, sizeof(data), 1, aPtr) == 1) /* Read and store info in file, into accounts array */

        i++;
        n = i; /* Num. of records in file */

        do {
            printf("Select Option\n" );
            printf("0: Exit\n1: Deposit\n2: Withdrawl\n3: Add Account\n4: Remove Account\n5: Balance Inquiry\n6: View Accounts\n: ");
            scanf("%d", &choice);

            switch (choice) {
              case 0: /* Exit */
                fclose(aPtr);
                break;

              case 1: /* Deposit*/
                fseek(aPtr, 0, SEEK_SET);
                printf("Enter account number:\n");
                scanf("%ld", &number);
                printf("Enter amount to be deposited:\n");
                scanf("%lf", &amount);
                for (i = 0; i < n; i++) {
                    if (accounts[i].acct == number) {
                        accounts[i].balance = accounts[i].balance + amount;
                        fseek(aPtr, i * sizeof(data), SEEK_SET); /* Pointer goes to accountlocation in file*/
                        fwrite(accounts + i, sizeof(data), 1, aPtr); /* Write modified account into file */
                        break;
                    }
                }
                if (i == n) {
                    printf ( "Account does not exist\n" );
                }
                break;
            }
        } while (choice != 0);

    return 0;
}

This while loop to read in the data isn't right

while( fread(accounts+1, sizeof(data), 1, aPtr) == 1 ) /* Read and store info in file, into accounts array */
    i++;

It's only reading into accounts[1] , so no matter how many accounts are in the file, you'll only have one in the array.

What you want is to do is read each record into accounts[i] which is what the following code does.

while( fread(&accounts[i], sizeof(data), 1, aPtr) == 1 ) /* Read and store info in file, into accounts array */
    i++;

Or better yet, if you work out the size of the file

fseek(aPtr, 0, SEEK_END);
unsigned long len = (unsigned long)ftell(aPtr);

then you know that len divided by sizeof(data) is how many records there are

n = len/sizeof(data);

and you can read all the records in one go.

if(fread(accounts, sizeof(data), n, aPtr) != n)
  {
  /* Oops! Did not read everything in */
  }

This has the added benefit that you could use malloc so that you're not setting a hard limit of 50 accounts.

Also the way you've opened the file isn't quite right as it will append anything you write to the end of the file regardless of what you do with fseek . You need to use "rb+" instead, but if the file doesn't exist, you'll need to create it first with "wb+".

Getting the struct populated correctly is is the focus of this answer.

Naming convention for your file indicates it should be a binary file (ie .dat is commonly used in the industry for binary files). But the contents you show clearly indicate it is a text file, so in the following example, I use data.txt as the file name. And as it is a text file, and includes well defined line fields, parsing the contents into a struct will be fairly straight forward

The format of your file is strictly defined, and uses spaces as delimiters. This is all okay, but choosing a visible delimiter, not normally used in the field content, such as a comma: , would make things easier when a field is not used, such as when no middle name. But this is not a requirement, and the following example will use space delimiters.

If you will forgive the use of fgets() and strtok() in this implementation (with little to no error checking/handling), it provides an example of populating the struct with the contents of the data file:

typedef struct
{
    char first[15];
    char middle[2];
    char last[15];
    float balance;
    long int acct;
}DATA;
const char filename[] = {".\\data.txt"}; 

int lineCount(const char *fname);
DATA * populateData(int lines, const char *fname);

int main(void)
{
    int lines = lineCount(filename);//count number of accounts in file (lines in file)
    DATA *data = populateData(lines, filename);//Create and populate data structure
    if(data)
    {
        ;   //use data here
        free(data); //free data memory when finished using it.
    }

    return 0;
}


int lineCount(const char *fname)
{
    int count=0;
    char line[260];
    FILE *fp = fopen(fname, "r");
    if(fp)
    {
        while(fgets(line, 260, fp)) count++;
        fclose(fp);
    }
    return count;
}

DATA * populateData(int lines, const char *fname)
{
    int i;
    char *tok;
    char *endPtr;
    char line[260];
    DATA *data = calloc(lines, sizeof(*data ));
    if(data)
    {
        FILE *fp = fopen(fname, "r");
        if(fp)
        {
            for(i=0;i<lines;i++)
            {
                if(fgets(line, 260, fp))
                {
                    //get first name
                    tok = strtok(line, " ");
                    if(tok)
                    {
                        strcpy(data[i].first, tok);
                        //get Middle name
                        tok = strtok(NULL, " ");
                        if(tok)
                        {
                            strcpy(data[i].middle, tok);
                            //get last name
                            tok = strtok(NULL, " ");
                            if(tok)
                            {
                                strcpy(data[i].last, tok);
                                //get balance
                                tok = strtok(NULL, " ");
                                if(tok)
                                {
                                    data[i].acct = atoi(tok);
                                    //get acct
                                    tok = strtok(NULL, "$");
                                    if(tok)
                                    {
                                        data[i].balance = strtod(tok, &endPtr);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            fclose(fp);
        }
    }

    return data;    
}

There is a major bug in your program:

while (fread(accounts + 1, sizeof(data), 1, aPtr) == 1) /* Read and store info in file, into accounts array */

Does not have a { to enclose all the statements in a block. Hence only the first one ( i++; ) is repeated in the while statement and the rest executes just once.

My suggestions are:

  • do not write comments at the end of a lines, they tend to make the program less readable.
  • use { and } to enclose all commanded statements in blocks.
  • put the { on the same line as the if , while , for and switch` statements (this style is known as Kernighan and Ritchie ).
  • do not pack statements on one line as if (i == n) { printf("Account does not exist\\n"); } if (i == n) { printf("Account does not exist\\n"); }

Regarding your program's objective, you cannot easily update a text file as you attempt to. Either read the entire file into memory structures and write the updated contents from the memory or use a binary format with fixed width fields.

The Unix philosophy is to favor the former approach and reserve the latter for larger databases with high level APIs to address concurrent access issues properly.

Also add fflush on writing to a file. See your doc on fflush().

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM