Solved

Updating a random access file

Posted on 2007-03-25
16
2,140 Views
Last Modified: 2012-06-27
Below is the program code I have been working on.
See the structure called Tool below.
In the update function, I'd like to have the user enter an idNumber of a tool
and then have them enter a name which corresponds to the id number.
The update function should then update the name field of the record.
So far, I've been unable to do this. What is wrong with the update function?



// Kimberley M. Griffith (406003213)
// March 24th 2007

#include <stdio.h>
#include <ctype.h>

typedef struct
{
      int idNumber;
      int quantity;
      double cost;
      char name[20];
}Tool;


void transfer();
void update();
void check();


int main()//serves as menu
{
      //Initialise variables
      int selection;

      /* POPULATES THE OLDHARDWARE FILE FOR PURPOSES OF THE PROGRAM
      FILE* odPtr; //fPtr is data file pointer

      Tool oldData[4] =
      {
            {5, 7, 57.98, "Electronic sander"},
            {9, 76, 11.99, "Hammer"},
            {11, 21, 11.00, "Jig saw"},
            {2, 0, 79.50, "Lawn mower"}
      };

      odPtr = fopen("oldhardware.dat", "w");
      if(odPtr == NULL)
      {
            printf("\nThe file you entered cannot be open. \nPlease try again.\n"); //exit or return
      }
      else
      {
            //filling in the records
            for(int i = 0; i < 4; i++)
            {
                  fwrite(&oldData[i], sizeof(Tool), 1, odPtr);
                  fflush(stdin);
            }
      }
      fclose(odPtr);
      */

      do
      {
            selection = NULL;

            printf("\nMenu List");
            printf("\n[0] Exit the program");
        printf("\n[1] Transfer information from an old to a new file");
            printf("\n[2] Update the records");
            printf("\n[3] Add a new tool");
            printf("\n[4] Delete a tool");
            printf("\n[5] Display record information");
            printf("\nEnter your selection here:");
            scanf("%u", &selection);


            switch(selection)
            {
                  case 0: return 0;
                  case 1: printf("\nTransfer information from an old to a new file");
                              transfer();
                              check();
                        break;
                  case 2: printf("\nUpdate the records");
                              update();
                              check();
                        break;
                  case 3: printf("\nAdd a new tool");
                        break;
                  case 4: printf("\nDelete a tool");
                        break;
                  case 5: printf("\nDisplay record information");
                        break;
                  default: printf("The value you entered is invalid.");
            }//end of switch statement


      }
      while(selection > 0 || selection < 5);
      
      return 0;
}

void transfer()
{
      FILE* odPtr;
      FILE* ndPtr; //fPtr is data file pointer

      Tool oldData[4];

      odPtr = fopen("oldhardware.dat", "r");
      if(odPtr == NULL)
      {
            printf("\nThe file you entered cannot be open. \nPlease try again.\n"); //exit or return
      }
      else
      {            
            ndPtr = fopen("newhardware.dat", "w");
            if(ndPtr == NULL)
            {
                  printf("\nThe file you entered cannot be open. \nPlease try again.\n"); //exit or return
            }
            else
            {
                   for(int i = 0; i < 4; i++)
                  {
                        fread(&oldData[i], sizeof(Tool), 1, odPtr);
                        fwrite(&oldData[i], sizeof(Tool), 1, ndPtr);
                  }
            }
            fclose(ndPtr);
      }
      fclose(ndPtr);
}//end transfer function


void check()
{
      FILE* checkPtr; //fPtr is data file pointer
      Tool checkData[4];

      checkPtr = fopen("newhardware.dat", "r");
      if(checkPtr == NULL)
      {
            printf("\nThe file you entered cannot be open. \nPlease try again.\n"); //exit or return
      }
      else
      {
            //filling in the records
            for(int i = 0; i < 4; i++)
            {
                  fread(&checkData[i], sizeof(Tool), 1, checkPtr);
                  printf("\n%u, %u,%.2lf,  %s\n", checkData[i].idNumber, checkData[i].quantity, checkData[i].cost, checkData[i].name);
            }
      }
      fclose(checkPtr);
}//end check function


void update()
{
      FILE* ndPtr; //fPtr is data file pointer

      int idNum;
      //char choice;

      Tool updateData;
      
            /*
      printf("\nNow, enter a letter from the menu below to tell us what you would like to edit");
      printf("\nA. Name");
      printf("\nB. Quantity");
      printf("\nC. Cost");
            scanf("%c", &choice);
            
      choice = toupper(choice);

      switch(choice)
            {
                  case 'A':
                        break;
                  case 'B':
                        break;
                  case 'C': printf("\nDisplay record information");
                        break;
                  default: printf("The value you entered is invalid.");
            }//end of switch statement
            */
            bool change = false;

      ndPtr = fopen("newhardware.dat", "a+");
      if(ndPtr == NULL)
      {
            printf("\nThe file you entered cannot be open. \nPlease try again.\n"); //exit or return
      }
      else
      {      
            
            printf("Enter the record number of a tool to edit:");
            fflush(stdin);
            scanf("%u", &idNum);

            fseek(ndPtr, (idNum-1)*sizeof(Tool),SEEK_SET);
            fread(&updateData, sizeof(Tool), 1, ndPtr);

            //change = true;
            printf("\nupdate data structure:\n%u, %u,%.2lf,  %s\n", updateData.idNumber, updateData.quantity, updateData.cost, updateData.name);

            printf("\nEnter the name to change to:");
            fflush(stdin);
                  scanf("%s", updateData.name);

            fseek(ndPtr, (idNum-1)*sizeof(Tool),SEEK_SET);

            //printf("\n%u, %u,%.2lf,  %s\n", updateData.idNumber, updateData.quantity, updateData.cost, updateData.name);

            //resets pointer to the start of the record to be updated
            //fseek(ndPtr, (idNum-1)*sizeof(Tool),SEEK_SET);
            fwrite(&updateData, sizeof(Tool), 1, ndPtr);
            //}
      }
      fclose(ndPtr);

}//end transfer function
0
Comment
Question by:kgpretty
  • 6
  • 4
  • 3
  • +2
16 Comments
 
LVL 24

Accepted Solution

by:
fridom earned 250 total points
Comment Utility
It's quite a weak assumption that you just can fread and fseek in a file and access the proper data. That can work but does not have to.  Howerver you invokue undefined behaviour also with fflush(stdin).

I'd suggest you structure you code a bit different.
Maybe putting every record on it's own line.
Then you just count the lines.
Or you use some other way of indexing e.g with something like
[231]  /* id of the record
quantity=12
cost=123.12
name="Some name"

Or you use for something like Berkeley DB also  (http://www.sleepycat.de) or how about using an embedded SQL library like sqlite?

But even if we assume that you just use fread, fwrite and the like then ask yourself what happens if you have

Tool  t1 = {5,12,123.0,"Some name")
Tool t2= {100, 1,1234.0, "Some other name");

How can fseek work that way. You have to change it to something like a linear search if you do. And of course you just have two elements but you will try to seek for over 100, which surely will not work.

there are other problems with your program also..

Here's some code which may work (at least has a good chance to run in one run)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <assert.h>



typedef struct
{
      int idNumber;
      int quantity;
      double cost;
      char name[20];
}Tool;

static Tool test_data[4] =
        {
            {5, 7, 57.98, "Electronic sander"},
            {9, 76, 11.99, "Hammer"},
            {11, 21, 11.00, "Jig saw"},
            {2, 0, 79.50, "Lawn mower"}
        };

static char *file_name = "tool_db.bin";


static void populate_file(void){
  FILE *fout = fopen(file_name, "wb");
  if (fout == NULL) {
    exit(EXIT_FAILURE);
  }
  for (int i = 0; i < 4; i++) {
    fwrite(&test_data[i], sizeof(test_data[i]), 1, fout);
  }
  fclose(fout);
}

enum{RC_RECORD_NOT_FOUND=-1,
     RC_SUCCESS = 0,
     RC_FAILURE = -2};
 
static int update_record(int id, char* new_name) {
  FILE *fh = fopen(file_name, "rb+");
  size_t rc = 0;
  Tool my_tool;
  int result = RC_SUCCESS;
  int assumed_size = sizeof(my_tool);
  if (fh  == NULL) {
    result = RC_FAILURE;
    goto err_out;
  }
  rc = fread(&my_tool, assumed_size, 1, fh);
  if (rc != 1) {
    /* end of file? or other error?
       assume not found */
    result = RC_RECORD_NOT_FOUND;
    goto err_out;

  } else {
    if (my_tool.idNumber == id) {
      assert(strlen(new_name) < 20);
      strncpy(my_tool.name, new_name,20);
      /* now we can update the record */
      rc = fseek(fh, -assumed_size, SEEK_CUR);
      if (rc != 0) {
        /* seek has failed */
        result = RC_FAILURE;
        goto err_out;
      } else {
        rc = fwrite(&my_tool, assumed_size, 1, fh);
        if (rc != 1) {
          result = RC_FAILURE;
          goto err_out;
        }
      }
    }
  }
 err_out:
  if (fh) fclose(fh);
  return result;
}

void print_file(void) {
  int i = 0;
  Tool my_tool;
 
  FILE *fin = fopen(file_name, "rb");
  assert(fin);
  for (i = 0; i < 4; i++) {
    fread(&my_tool, sizeof(my_tool), 1, fin);
    printf("id = %d\n"
           "quantity = %d\n"
           "price = %.2f\n"
           "name = %s\n\n", my_tool.idNumber, my_tool.quantity,
           my_tool.cost, my_tool.name);
  }
  fclose(fin);
}
 


int main(void) {
  int i_rc = 0;
  populate_file();
  print_file();
  i_rc = update_record(5, "Name");
  print_file();
  return 0;
}

Regards
Friedrich






0
 
LVL 24

Expert Comment

by:fridom
Comment Utility
Well of course the update stuff has to be put in a loop.
static int update_record(int id, char* new_name) {
  FILE *fh = fopen(file_name, "rb+");
  size_t rc = 0;
  Tool my_tool;
  int result = RC_SUCCESS;
  int assumed_size = sizeof(my_tool);
  if (fh  == NULL) {
    result = RC_FAILURE;
    goto err_out;
  }
  while((rc = fread(&my_tool, assumed_size, 1, fh)) == 1) {
    if (my_tool.idNumber == id) {
      assert(strlen(new_name) < 20);
      strncpy(my_tool.name, new_name,20);
      /* now we can update the record */
      rc = fseek(fh, -assumed_size, SEEK_CUR);
      if (rc != 0) {
        /* seek has failed */
        result = RC_FAILURE;
        goto err_out;
      } else {
        rc = fwrite(&my_tool, assumed_size, 1, fh);
        if (rc != 1) {
          result = RC_FAILURE;
          goto err_out;
        }
      }
    }
  }
 err_out:
  if (fh) fclose(fh);
  return result;
}
so use this of course. However you should check for the case that the stuff is not in the file and act accordingly.

Regards
Friedrich
0
 
LVL 16

Expert Comment

by:imladris
Comment Utility
Kimberley,

it certainly is conceptually possible to run a flat file of fixed length records, and access them by record number. But accessing them by idNumber (a data field in the record) cannot be mediated by fseek. The fseek function will only be able to get you to a particular place in the file. So, if you want record #2, you can tell the fseek function you want to go to the second record by multiplying the record length by 2, and calling fseek with that location. Then that record can be read.

But as to what you *find* there (the idNumber), that's something the program will have to deal with. So finding a record by idNumber will involve reading each record and checking if it has the idNumber you are looking for or not. The code that fridom has posted is following this approach.

Note also, that in order to force the system to always right out the same number of bytes (corresponding to the structure size) you should open the file in binary mode (fopen(name,"rb")).

The update function is reading a record in the file. Initially there is nothing there (since newhardware has not been initialized), and so it displays random garbage for the record. If you try it with record number 0, you will see that the entered name is preserved in the file, though. To that extent, it is working OK.
0
 
LVL 16

Expert Comment

by:PaulCaswell
Comment Utility
Hi kgpretty,

The central answer to you question is that if you wish to treat a file as binary instead of text (as you seem to want) then you MUST open it in binary. fopen ( ..., "a+b") for example but ALL 'fopen' must work in binary.

My secondary comment. If this is all your own work you have done very well. I have one specific style suggestion though, whether it is yours or not.

Try to code pessimistically. :) Instead of:

if ( function() ) {
 // Fail code here.
} else {
 // Success code here.
}

Try:

if ( function() ) {
 // Success code here.
} else {
 // Fail code here.
}

It makes code so much more easy to read.

Don't get me wrong! You must ALWAYS code for failure! I usually fill in the failure code first myself when I am coding but it must always must be done.

Paul
0
 
LVL 24

Expert Comment

by:fridom
Comment Utility
The binary mode is just needed on Windows, because it distinguishes between that modes (althoug I've written this tuff on Linux I did prepare for that).

I disagree with the suggestion about how to deal with failure. In the end encapsulationg this stuff in ifs just hide the "real work to be carried out" So I think you should do the following

call the code you like to execute
in your case rc = function();
then after that write the code for either error handling or not. I kow there are some "patterns" like

while((pc = fgets ...) != EOF) and the like
and one has to be able to read that stuff. But there is not need to obfuscate the work intended to be done in C although i have the impression the C folks like that all too much.

One even can think about the "one entry- one exit" control flow in C. I know C makes it easy to just "jump" out if something goes wrong. But that means there are so many control flows possiblities in one function that it's unbelievable difficult to "get them all"

You have to do more test in a functiion but at least you have  steady flow from the beginning of some function to it's end. Howerver one is for sure error handling in C is not really easy. But if you omit it, it'll turn out to be a really bad decision.

Regards
Friedrich
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
Comment Utility
>>>> then you MUST open it in binary.
Friedrich is right.  That is necessary at Windows platform but not not at UNIX.

The file is written with fixed length records, so a direct access is possible by using fseek. However, if the id number doesn't run from 1 to n without gaps or the records were not sorted they cannot be wriiten to the file in a sequential loop if there is no additional index which says what id is written to which record (number). If the ids normally were contiguous it would be senseful to create the file by writing empty records up to the maximum id number expected. The writing part would change that way that you have to call fseek with a position that was calculated from the current id number and the size of the records. Doing so, the update function could work the same way.

Note, the max number of records can be calculated by dividing the file size by the record size. The file could grow if you open the file in append mode and write some more empty records.

If the id numbers were not contiguous you have to add an index where each id points to the record number it was stored. Alternatively, if the file is not big, you can read all records with an initial loop and build the index table in memory only. A persistent index table could be added to the data file by reserving some head records for the id array. The file would look like

    36   4     0  0    0  0  0   0  0  
      5    9  11  2    0   0  0   0  0
      0    0    0   0   0   0  0   0  0  
      0    0    0   0   0   0  0   0  0  
      0    0    0   0   0   0  0   0  0  
      5    7  57.98   Electronic sander  
      9  76  11.99   Hammer
     11 21  11.00  Jig saw
      2   0   79 .50  Lawn mower

where each line is a 36 byte record . 100 is the maximum number of records, 4 is the number of used records. 5 9 11 2  are the ids of the first 4 records. You can search the index records for a given id and calulate record number and position.

Regards, Alex
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
Comment Utility
>>>> 100 is the maximum number of records,
It's a typo. It should read:

36  is the maximum number of records
0
 
LVL 16

Expert Comment

by:PaulCaswell
Comment Utility
Kimberley,

Not sure if they are the cause but there are several places in your code where you do stuff like:

     file= fopen("filename", "a+");
      if(file== NULL)
      {
        ...
      }
      else
      {      
        ...
      }
      fclose(ndPtr);

This is potentially dangerous. You should only close a file if it correctly opened:

     file= fopen("filename", "a+");
      if(file== NULL)
      {
        ...
      }
      else
      {      
        ...
        fclose(ndPtr);
      }

Friedrich,

Just to clarify what I was trying to say. I would, for example write:

void transfer()
{
      FILE* odPtr;
      FILE* ndPtr; //fPtr is data file pointer

      Tool oldData[4];

      odPtr = fopen("oldhardware.dat", "r");
      if(odPtr == NULL)
      {
            printf("\nThe file you entered cannot be open. \nPlease try again.\n"); //exit or return
      }
      else
      {            
            ndPtr = fopen("newhardware.dat", "w");
            if(ndPtr == NULL)
            {
                  printf("\nThe file you entered cannot be open. \nPlease try again.\n"); //exit or return
            }
            else
            {
                   for(int i = 0; i < 4; i++)
                  {
                        fread(&oldData[i], sizeof(Tool), 1, odPtr);
                        fwrite(&oldData[i], sizeof(Tool), 1, ndPtr);
                  }
            }
            fclose(ndPtr);
      }
      fclose(ndPtr);
}//end transfer function


as:

void transfer()
{
      FILE* odPtr;
      odPtr = fopen("oldhardware.dat", "r");
      if(odPtr != NULL)
      {
          FILE* ndPtr; //fPtr is data file pointer
            ndPtr = fopen("newhardware.dat", "w");
            if(ndPtr != NULL)
            {
                  Tool oldData[4];

                  for(int i = 0; i < 4; i++)
                  {
                        fread(&oldData[i], sizeof(Tool), 1, odPtr);
                        fwrite(&oldData[i], sizeof(Tool), 1, ndPtr);
                  }
                  fclose(ndPtr);
            }
            else
            {
                  printf("\nThe file you entered cannot be open. \nPlease try again.\n"); //exit or return
            }
            fclose(odPtr);
      }
      else
      {            
            printf("\nThe file you entered cannot be open. \nPlease try again.\n"); //exit or return
      }
}//end transfer function

Paul
0
IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 
LVL 24

Expert Comment

by:fridom
Comment Utility
Well yes one can write C that way the unfotunate side effect is that you collect all the elses in the end which you "have forgotten" on your way. Well probybly not in this case but let there be just 5 or so calls with a few parameter and you loose track of that quite fast. Advantage is of course you have that error handling "concentrated" at the end. So it's a matter of taste I guess, but if you go beyond 10 or so calls I really would not like to dig through that code any longer.
I prefer

call
//error handling
call
//error handling.

Of course is is also distrcting, but well we have no choice really or do we ?

Regards
Friedrich
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
Comment Utility
>>>> Just to clarify what I was trying to say.
Paul, in the last sample code you have the success code first and the fail code in the else branch. But one comment before you "said" to handle the pessimistic case first while the sample code does vice versa but has a positive condition ( if (function() ) for both the success and the fail case.  In your remarks you tell "I usually fill in the failure code first" what was contrary to the samples.
 
IMO, there is no benefit when handling the pessimistic case first beside you would terminate it with a return statement. If you have a lengthy error handling first you often can't see the (fail or success) condition if you put all positive part in the else branch.  More over you are nesting more and more what makes the whole code badly readable.

I personally prefer something like that:

       if (!<success condition>)
              return errorlog(.....);   // put all relevant error info in one line
       // success

Here you have no nesting and the success condition is near enough to read it with the success code.

Regards, Alex
0
 
LVL 16

Expert Comment

by:PaulCaswell
Comment Utility
Alex,

Good catch! My bad, I meant to say "take the optimistic approach".

What I meant about the failure code first only applies to the order I write the code in, not the final shape of it.

On the rest, all your techniques generate code quickly, efficiently and usually fully functional. This is all good.

I have been programming for many years and have come to realise that code spends MUCH more time being browsed and enhanced than it does being written. Holding all the meat of the function near the top means you take much less time understanding its function.

Friedrich,

Following from my previous paragraph, not using pre-emptive returns makes enhancement significantly easier.

Your point about nesting is totally correct but a balance must be found.

And on your last point :) Yes, I think we do have a choice. When I work for a company I plan to stay there a while. Long enough to forget some of the earlier code I wrote perhaps. To me, planning for browse speed and enhancement safety is important ... when I have time. :)

I think we digress.

Kimberley,

Have we solved your problem or do you need any more help?

Paul
0
 
LVL 24

Expert Comment

by:fridom
Comment Utility
Well I meant that we do not have a choice on handling errors. We have a choice on how to do that, but within the bounds of the language. But the point is that n other programs you simply do not see the error handling as you did in C code. And we have so many different ways of doing this error handling stuff as there are
- using the return value as failure indicator
- using some out parameter of a function for error handling
- setting some global variable to indicate the kind of error.

howerver life even gets harder
on Unices 0 means usually success
but 0 means also false to C
Alex wrote
if (! success)
well that is readable for sure howerver if 0 means success than this reads (
if not 0 but the success is 0.

And so it goes on and on.

No we do not disgress we just think differently about how to structure our code but it's not that different
in the end.


regards
Friedrich
0
 

Author Comment

by:kgpretty
Comment Utility
Thankyou all who responded to my question. I found the code originally posted by fridom a bit hard to understand as I have not been taught the concepts of enumeration or use of the variable rc in the program.

My program is working somewhat satisfactory. You can view the code and further down is the code to generate my oldhardware.dat file.

For the main program, you'll notice for reading in data I've used the For loop. This shouldn't be as the data is dynamic. When the user adds and deletes records, how would I be able to tell the size?

In the dispay function I've tried placing  (!feof(fPtr)) inside the for loop.. so it looked like for(int i = 0; (!feof(fPtr)); i++) .... just to see if it'd work. When I did that, I got the 4 originally records and one line of garbage following. The

Could someone please explain fridom's concept of the variable rc in the while loop:
while((rc = fread(&my_tool, assumed_size, 1, fh)) == 1)

Or, what's the best approach of having a loop to facilitate and terminate data being read in?


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Kimberley M. Griffith (406003213)
// March 24th 2007

#include <stdio.h>
#include <ctype.h>
#include <string.h>

typedef struct
{
      int idNumber;
      int quantity;
      double cost;
      char name[20];
}Tool;

#define SIZE sizeof(Tool)

void transfer(FILE *);
void update(FILE *);
void deleteTool(FILE *);
void display(FILE *);


int main()//serves as menu
{
      //Initialise variables
      int selection;
      FILE* nhPtr; //nhPtr is data file pointer to the newHardware data file

      nhPtr = fopen("newhardware.dat", "w+");
      if(nhPtr == NULL)
      {
            printf("\nThe file you entered cannot be open. \nPlease try again.\n"); //exit or return
      }
      else
      {
            do
            {
                  selection = NULL;

                  fflush(stdin);

                  printf("\nMenu List");
                  printf("\n[0] Exit the program");
                  printf("\n[1] Transfer information from an old to a new file");
                  printf("\n[2] Update the records");
                  printf("\n[3] Add a new tool");
                  printf("\n[4] Delete a tool");
                  printf("\n[5] Display record information");
                  printf("\nEnter your selection here:");
                        scanf("%u", &selection);
      
                  switch(selection)
                  {
                        case 0: return 0;
                        case 1: transfer(nhPtr);
                                    printf("\nThe data from oldhardware.dat has been transferred to newhardware.dat");
                              break;
                        case 2: printf("\nUpdate the records");
                                    update(nhPtr);
                              break;
                        case 3: printf("\nAdd a new tool");
                              break;
                        case 4: printf("\nDelete a tool");
                                    deleteTool(nhPtr);
                              break;
                        case 5: printf("\nDisplay record information");
                                    display(nhPtr);
                              break;
                        default: printf("The value you entered is invalid.");
                  }//end of switch statement

            }while(selection > 0 || selection < 5);
      }//end if statement

      fclose(nhPtr);

      return 0;
}

void transfer(FILE *fPtr)
{
      rewind(fPtr);
      FILE* ohPtr;

      Tool oldData[4];

      ohPtr = fopen("oldhardware.dat", "r");
      if(ohPtr == NULL)
      {
            printf("\nThe file you entered cannot be open. \nPlease try again.\n"); //exit or return
      }
      else
      {            
            for(int i = 0; i < 4; i++)
                  {
                        fread(&oldData[i], SIZE, 1, ohPtr);
                        fwrite(&oldData[i], SIZE, 1, fPtr);
                  }
      }
      fclose(ohPtr);
}//end transfer function


void update(FILE *fPtr)
{
      rewind(fPtr);

      int i = 0;
      int idNum = 0;

      Tool updateData;
      char choice;

      fflush(stdin);

      printf("Enter the record number of a tool to edit:");
            scanf("%u", &idNum);
      
            fflush(stdin);
      printf("\nNow, enter a letter from the menu below to tell us what you would like to edit");
      printf("\nA. Name");
      printf("\nB. Quantity");
      printf("\nC. Cost\n");
            scanf("%c", &choice);
            
      choice = toupper(choice);

      bool change = false;

      while(i < 4 && change == false)
            {
                  fread(&updateData, SIZE, 1, fPtr);

                  if(updateData.idNumber == idNum)
                  {
                              switch(choice)
                              {
                                    case 'A': printf("\nEnter the name to change to:");
                                                      scanf("%s", updateData.name);
                                          break;
                                    case 'B': printf("\nEnter the quantity change to:");
                                                      scanf("%u", &updateData.quantity);
                                          break;
                                    case 'C': printf("\nEnter the cost to change to:");
                                                      scanf("%lf", &updateData.cost);
                                          break;
                                    default: printf("The value you entered is invalid");
                              }//end of switch statement

                        printf("\nUPDATED data structure:\n%u, %u,%.2lf,  %s\n", updateData.idNumber, updateData.quantity, updateData.cost, updateData.name);
                        
                        fseek(fPtr, -SIZE, SEEK_CUR);
                                          
                        fwrite(&updateData, SIZE, 1, fPtr);

                        change = true;
                  }
            i++;
            }
      
}//end transfer function

void deleteTool(FILE *fPtr)
{
      rewind(fPtr);
      Tool data;
      int idNum;
      int i = 0;

      bool change = false;

            fflush(stdin);

      printf("Enter the record number of a tool to edit:");
            scanf("%u", &idNum);

            while(i < 4 && change == false)
            {
                  printf("\nThe file opened"); //exit or return
            
                  fread(&data, SIZE, 1, fPtr);

                  
                  if(data.idNumber == idNum)
                  {
                        data.idNumber = 0;
                        data.quantity = 0;
                        data.cost = 0.0;
                        strcpy(data.name, " ");

                        printf("\nif num was found");
                        fseek(fPtr, SIZE, SEEK_CUR);
                                          
                        fwrite(&data, SIZE, 1, fPtr);

                        change = true;
                  }
                  i++;
            }
}//end deleteTool function


void display(FILE *fPtr)
{
      rewind(fPtr);

      Tool checkData[4];

      for(int i = 0; i < 4; i++)
      {
            fread(&checkData[i], SIZE, 1, fPtr);
            printf("\n%u, %u,%.2lf,  %s\n", checkData[i].idNumber, checkData[i].quantity, checkData[i].cost, checkData[i].name);
      }

}//end display function


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Code for oldhardware.dat file:

#include <stdio.h>
#include <ctype.h>

typedef struct
{
      int idNumber;
      int quantity;
      double cost;
      char name[20];
}Tool;

int main()//serves as menu
{
      //Initialise variables
      int selection;

      // POPULATES THE OLDHARDWARE FILE FOR PURPOSES OF THE PROGRAM
      FILE* odPtr; //fPtr is data file pointer

      Tool oldData[4] =
      {
            {5, 7, 57.98, "Electronic sander"},
            {9, 76, 11.99, "Hammer"},
            {11, 21, 11.00, "Jig saw"},
            {2, 0, 79.50, "Lawn mower"}
      };

      odPtr = fopen("oldhardware.dat", "w");
      if(odPtr == NULL)
      {
            printf("\nThe file you entered cannot be open. \nPlease try again.\n"); //exit or return
      }
      else
      {
            //filling in the records
            for(int i = 0; i < 4; i++)
            {
                  fwrite(&oldData[i], sizeof(Tool), 1, odPtr);
                  fflush(stdin);
            }
      }
      fclose(odPtr);


}
0
 
LVL 16

Expert Comment

by:imladris
Comment Utility
If you have a loop like this:

      for(int i = 0; !feof(fPtr); i++)
      {
            fread(&checkData[i], SIZE, 1, fPtr);
            printf("\n%u, %u,%.2lf,  %s\n", checkData[i].idNumber, checkData[i].quantity, checkData[i].cost, checkData[i].name);
      }

you will wind up reading one record too many, as you experienced. The reason is that feof does not go out and determine whether the end of the file has been reached. It is merely a report on flags that have been *already* been set by fread. That is, feof will not report true, unless you have executed an fread that has attempted to go beyond the end of the file. So, in the above loop the feof condition will be checked followed by the reading of a record. This will happen 4 times. The 5th time, feof will *still* be false (since the prior (4th) fread had no problem), and so it will proceed to execute the loop again. So a *5th* fread will be executed, and its results (garbage) will be printed. The next time feof is executed (the 6th time) it *will* report that end of file has been reached. The feof function could be used if you structure things a little differently, but fridoms solution is the more elegant one:

while(fread(&my_tool, assumed_size, 1, fh) == 1)
{   printf("\n%u, %u,%.2lf,  %s\n", checkData[i].idNumber, checkData[i].quantity, checkData[i].cost, checkData[i].name);
}

actually the rc is not a critical part of this. This loop exploits the fact that fread itself returns an indicator of success or failure. The value fread returns is the number of "records" that it successfully read. The second argument (assumed_size) is the record size, the third argument (1) is the number of "records" you are asking fread to read. If it succeeded in reading the record it will return a 1. If the end of file was hit, it could not read a record and so it will return a 0. So in this loop, it will read 4 times, succeed, and thus proceed to the loop body to print out the result. The 5th time the fread will not be able to read a record, thus return a 0, which is not 1, and so the loop body is not executed. So this way the loop cleanly cuts out at the end of the file.

0
 
LVL 24

Expert Comment

by:fridom
Comment Utility
Oh, come on, the enum stuff just introduces constants. And well you simply do not check if your reading succeeds. My code does that under the assumption that the size erally fits, because fread returns the number of records read. So I try to read one record a time, If I'm at the end of the file something like 0 or EOF is returned and so the loop simply stops.

You can wite it as
rc = fread ....
while (rc > 0) {
 
    rc = fread_.....
}

However this while (    ==  or !=  is quite idiomatic C. So you will see this more often then the code pointed out above.

Regards
Friedrich


0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
Comment Utility
>>>> Could someone please explain fridom's concept
>>>> of the variable rc in the while loop:
>>>> while((rc = fread(&my_tool, assumed_size, 1, fh)) == 1)

I din't think the question Kimberly asked was answered sufficiently.

>>>> while

You'll will use a while loop instead of  a 'do while' if there is a chance that the while condition is false from the beginning. Generally a while loop has the problem that if the condition turned to false within the loop you either have to break - what makes the while condition worthless cause you could have a while(1)  as well - or you call 'continue;' what jumps to end of loop thus the while condition was checked again. However the latter is worthless too cause if you already *know* that you have to break the loop why shouldn't you break but let the while do a further (unneeded) check?

A solution for this is to put the action to the while condition itself and check for the return. That guarantees that the while was broken at the most earliest time.

>>>> while((rc =  

You see the additional open round bracket '('?  'rc = ' is an assignment which result would be stored in rc *but* as you want to use that result as the left part of a a comparision you need to put the whole assignment between parantheses.

>>>> fread(&my_tool, assumed_size, 1, fh))
The third argument passed is the number of items of size 'assumed-size' you want to read at maximum. As the third argument was 1 the only returns were 1 (success) or 0 (fail). The most likely fail condition if open was successfully is EOF (end-of-file).

>>>> while((rc = fread(&my_tool, assumed_size, 1, fh)) == 1)  

From all said before we know now that the while will read *records* of size 'assumed_size' until file end of until an error occurs. So whenever the the while block was entered we know that we have read a full record to my_tool.  That is quite optimally and I prefer that concept over any other, e. g. checking the fail condition within the loop.

>>>> Alex wrote  if (! success)  well that is readable for sure
>>>> howerver if 0 means success than this reads
>>>> if not 0 but the success is 0.

No Friedrich, just have more creative phantasy ;-)

I never would have statements like

    char * psz = "abc";
    if (! strcmp("abc", psz))   // SUCCESS

Instead I would do (and did whenever I had to use C and strcmp rather than C++ and std::string)

#define EQUAL 0

    ...
    char * psz = "abc";
    if (! strcmp("abc", psz) == EQUAL)   // FAILURE
          return errorlog("this function: strings were not equal", psz, "abc");

I never never would treat a return code 0 as a boolean and I think that developers who do that didn't have understood the difference between "what can be done" and "what should be done".

Regards, Alex

 


0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

This tutorial is posted by Aaron Wojnowski, administrator at SDKExpert.net.  To view more iPhone tutorials, visit www.sdkexpert.net. This is a very simple tutorial on finding the user's current location easily. In this tutorial, you will learn ho…
Windows programmers of the C/C++ variety, how many of you realise that since Window 9x Microsoft has been lying to you about what constitutes Unicode (http://en.wikipedia.org/wiki/Unicode)? They will have you believe that Unicode requires you to use…
The goal of this video is to provide viewers with basic examples to understand opening and writing to files in the C programming language.
Video by: Grant
The goal of this video is to provide viewers with basic examples to understand and use nested-loops in the C programming language.

771 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now