Sequential File etc. [Part 1]


"Structure" in C is very important, especially when dealing with files. Hence we discuss "structure" first, before we come to "files".

How to declare data "structure"

Exercise : What are the common data types in C ? Name some.

Ans : char, short, long, int, float, double ....

Exercise : From the previous chapters, can you remember any other ways to define new data types?

Ans : e.g.

       typedef struct { int age;
                        double wages;
                      } New_data_type;
       New_data_type  arecord, *ptr;
Notice that "typedef" and "struct" are keywords.

Above we have a new data type called "New_data_type", which is a structure and consists of 2 elements, one an integer and the other a double precision number.

The declaration

       New_data_type  arecord, *ptr;
declares "arecord" to be of this data type, and "ptr" a pointer for this new data type.



Three ways to declare a structure

  1. Direct direction without the use of "typedef"

    e.g.

        struct { int age;
                 double wages;
               }  arecord, *ptr;
           

    Exercise : Suppose we wish to have a data type consisting of

       
        name   which is a string of 40 bytes
        age    which is an integer
        wages  which is a double
           
    and we wish to have two variables "reca, recb" as well as a pointer "prec" to this data type. How are we to declare it?

    Ans :

        struct {  char name[40];
                  int  age;
                  double wages;
               }  reca, recb, *prec ;
           

    Exercise : How are we to access data in "reca, recb, prec " ?

    Ans : e.g. reca.name, reca.age, reca.wages, recb.name, recb.age, recb.wages, prec->name, prec->age, prec->wages (OR we may use (*prec).age, (*prec).age, (*prec).wages , but usually we will use the " -> " , because it is more suggestive. )



  2. Give a name to a structure first, then ...

    e.g.

        struct recname
            { int age;
              double wages;
            } ;
        struct recname arecord, *ptr;
           
    Notice that "recname" may be any name. A valid variable name is also a valid structure name.

    Exercise : Would it be correct if we omit "struct" , e.g.

        struct recname
            { int age;
              double wages;
            } ;
        recname arecord, *ptr;
    
    (and NOT)
    
        struct recname arecord, *ptr;
    
     ?
           

    Ans : No. "recname arecord, *ptr; " is wrong in syntax. We should use "struct recname arecord, *ptr; ". We will discuss "typedef" later, and with it, we do not have to use "struct".

    Exercise : For the structure,

        
        name   which is a string of 40 bytes
        age    which is an integer
        wages  which is a double
           
    How are we to declare "reca, recb, *prec" ?

    Ans :

        struct staffrec
            {  char name[40];
               int  age;
               double wages;
            } ;
        struct staffrec reca, recb, *prec ;
           


  3. Using "typedef"

    This is what we have been doing, e.g.
        typedef struct { int age;
                         double wages;
                       } New_data_type;
        New_data_type  arecord, *ptr;
    

    Exercise : For the structure,

        
        name   which is a string of 40 bytes
        age    which is an integer
        wages  which is a double
           
    How are we to declare "reca, recb, *prec" using "typedef" ?

    Ans :

        typedef struct
            {  char name[40];
               int  age;
               double wages;
            } staffrec;
        staffrec reca, recb, *prec ;
           
    (Notice that we DO NOT HAVE TO USE "struct" here !)

    Exercise : How are we to access data in "reca, recb, prec " ?

    Ans : Same as before, with : reca.name, reca.age, reca.wages, recb.name, recb.age, recb.wages, prec->name, prec->age, prec->wages




More on "typedef"

Exercise : What are the data type of "vara, varb, varc, vard, pvar" in the following :

       int vara;
       double varb;
       char varc[40];
       double vard[20];
       char *pvar;

Ans :
vara is an integer,
varb is a double precision floating point number,
varc is a character array (or string) of 40 bytes long,
vard is an array of double precision floating point number and has 20 elements,
pvar is a character pointer.

Exercise : Now suppose we put "typedef" before the declarations, i.e.

       typedef int vara;
       typedef double varb;
       typedef char varc[40];
       typedef double vard[20];
       typedef char *pvar;
Then what are the data type of "d1, d2, d3, d4, pd5" in the declarations
       vara d1;
       varb d2;
       varc d3;
       vard d4;
       pvar pd5;

Ans :
d1 is of type "int", as if we have declared it with "int d1;"
d2, "double", as if we have declared it with "double d2;"
d3 is, as if we have declared it with "char d3[40];", i.e. a character array of 40 bytes,
d4 is an array of double precision floating point number and has 20 elements
pd5 is a character pointer, same as if we have declared it with "char *pd5;"

WHENEVER A VARIABLE NAME HAVE BEEN "typedef", THEN ANY OTHER VARIABLES DECLARED WITH THIS VARIABLE NAME HAVE IDENTICAL DATA TYPE.

Exercise :Declare "Boolean" as "unsigned short", then declare variables "ia, ib, ic" as "Boolean".

Ans :

        typedef unsigned short Boolean;
        Boolean ia, ib, ic;

Exercise :What is "p" in the declaration "Boolean *p;" ?

Ans : "p" is a pointer for "unsigned short".

Exercise :"typedef" the following structure as "Masterrec", then declare "rectom, recmary, recjack" as three variables of this type. Also declare "prec" as a pointer to this new data type.

    
    name   which is a string of 40 bytes
    tel    which is a string of 20 bytes
    addr   which is a string of 60 bytes
    wages  which is a double
       

Ans :

    typedef struct 
        {  char name[40];
           char tel[20];
           char addr[60];
           double wages;
        } Masterrec;
    Masterrec rectom, recmary, recjack, *prec;

Exercise : What do the following statements do ?

     prec = &recmary;
     prec->wages = 5000;

Ans : The first statement loads the effective address of "recmary" into the address pointer "prec". The second statement is the same as
"recmary.wages = 5000; ".

Exercise : (WORD ALIGNMENT IN MICROPROCESSOR) The number of lines in "data bus" nowadays is usually 32 (i.e. 4 bytes will be loaded/stored at one time). For 64 bits microprocessor, the number of lines in "data bus" is 64. Hence 8 bytes of data may be loaded/stored at one time. For efficient loading/storing of data, variable should be stored in addresses "aligned with word boundary".
Would the following "structure" be efficient in storing/loading ? (Assuming a 32 bits microprocessor will be used.)

    typedef struct 
        {  char name[47];
           char tel[25];
           char addr[66];
           double wages;
        } Masterrec;

Ans : No, because 47, 25, 66 are NOT DIVISIBLE BY 4. Hence a better one should be

    typedef struct 
        {  char name[48];
           char tel[24];
           char addr[68];
           double wages;
        } Masterrec;
(Note : in actual implementation, a C compiler would usually "pad" the field so that it becomes aligned on word boundaries, e.g. "char name[47];", compiler would pad one idle byte to make it 48. For "char tel[25];", compiler would pad 3 idle bytes to make it 28. For "char addr[66];", compiler would pad 2 idle bytes to make it 68.

Question : Suppose the microprocessor is 64 bits; how many idle bytes would be padded for the example above ? Ans : 48 (47+1), 32 (25+7), 72 (66+6). )


Note about syntax specification

The syntax (or grammar) of a computer language is usually specified in the following two ways :-

  1. Diagram

    e.g.

          int i, j, k=5, ia, ib ;
          double x=10.2, y, z=60.7 ;
    
  2. Using "recursive descend" type of specification

    e.g. ( Notice that the vertical stroke | means "OR" )

         expression =   assignment-expression
                      | conditional-expression
                      | unary-expression
                      |   ....
             ....
             ....
    
         additive-expression =     multiplicative-expression
                                |  additive-expression  +  multiplicative-expression
                                |  additive-expression  -  multiplicative-expression
    
         multiplicative-expression =       unary-expression
                                       |  multiplicative-expression  *  unary-expression
                                       |  multiplicative-expression  /  unary-expression 
    
         unary-expression =     primary-expression
                             |  unary-expression[expression]
                             |  unary-expression(expression,expression, ... )
                             |  unary-expression.identifier
                             |  unary-expression->identifier
    
         primary-expression =     identifer
                               |  constant
                               |  string
                               |  (expression)
    
    You will have noticed that "expression" is defined in terms of "assignment-expression, ..." which in turn are defined in terms of "additive-expression". And "additive-expression" is defined in terms of "multiplicative-expression". And "multiplicative-expression" is defined in terms of "unary-expression". "unary-expression" is defined in terms of "identifier, constant, string", and a bracket with "expression" inside !

    You will have noticed that nearly all such definitions are "circular".

    Moreover : (1) "identifier, constant, string" have to be defined in terms of alphabets and digits. (2) the definition "expression" is CIRCULAR, i.e. it is defined in terms of itself. "Circular definition" makes no sense in Mathematics, but it makes sense in computer parsing, because after each parsing operation, the original sentence gets shorter and shorter, and as the sentence is of finite length, the process will terminate, and will not go on indefinitely.

The above are EXAMPLES only, and are much abbreviated. For a full description of C syntax (or grammar), please refer to the Appendix of K&R's book "The C Programming Language".


Structure "FILE"

In <stdio.h> (Standard Input/Output Header File), there is defined a structure to be used with file operations,

struct  _IO_FILE
    {  char* _IO_write_base;	/* Start of put area. */ 
       char* _IO_write_ptr;	/* Current put pointer. */
       char* _IO_write_end;	/* End of put area. */
       char* _IO_buf_base;	/* Start of reserve area. */
       char* _IO_buf_end;	/* End of reserve area. */
       char *_IO_save_base;     /* Pointer to start of non-current get area. */
       char *_IO_backup_base;   /* Pointer to first valid character of backup area */
       char *_IO_save_end;      /* Pointer to end of non-current get area. */
       struct _IO_marker *_markers;
       struct _IO_FILE *_chain;
       int _fileno;
       int _blksize;
       _IO_off_t _offset;
       unsigned short _cur_column;
       char _unused;
       char _shortbuf[1];
       struct _IO_lock_t _IO_lock;
    };

typedef struct  _IO_FILE   FILE ;

Notice that this defintion of "struct ... ; typedef ...;" varies from system to system, but usually it contains similar items.


Some Common IO Subroutines

These subroutines are those described in "stdio.h" (The standard Input/Output subroutines that comes with an ANSI C compiler.)

(1)  FILE *fopen(char *filename, char *mode)

     e.g.   FILE *filein;                  /*This declares "filein" to be 
                                             a FILE pointer.  It is to store
                                             the return value from "fopen(..)" */
            filein = fopen("testfile","r");    /*Opens the file for reading*/

     This subroutine requires 2 arguments : one is a character string containing
     the filename.  The other is mode, and

     Mode may be

            "r"      for reading.
            "w"      for writing.  Original content will be overwritten.
            "a"      append at the end.
            "r+"     for both read and write, i.e. update a file.
            "w+"     for write and read. Create file if it doesn't exist.
            "a+"     append as well as read.

     If it fails to open the file, it returns NULL (which is
     usually defined to be 0).  Otherwise, it returns (FILE *),
     the address where a structure of type "FILE" is stored.

     Another example,

             FILE  *fileout;
             if ( (fileout = fopen("outfile","w")) == NULL )
                {perror("Unable to open file");
                 exit(1);
                }
               ....
               ....

     Here we test if the file has been successfully opened or not.  If not,
     we use "perror(..)" ("print error to file stderr" - one of the three 
     standard files opened by the system automatically : 

           stdin - standard input, usually the keyboard;
           stdout - standard output, usually the screen;
           stderr - standard error output, usually the screen too.)

     "exit(expression)" is another subroutine usually supplied, though not in
     the standard IO library.  It will flush out all data in buffers and close all
     opened files. "expression" in exit(..) is the return code, much like "return 0;"
     in the main program ( NOTE : you may use "return .." instead of exit(..) ).

     Exercise :   What is wrong with the following

       (1)      FILE  fin;
                fin = fopen("testfile","r");

       (2)      FILE *fout;
                fout = fopen("outfile",'w');

     Ans : 

       (1)  FILE pointer required, not FILE structure, hence it should be

                FILE *fin;

            And not 

                FILE fin;

       (2)  "w" which is a string, but 'w' is a character.
(2)  void perror(char *s)

     This subroutine prints the string (char *s) to stderr - standard error file.
(3)  int fclose(FILE *ptr)

     If the file is closed successfully, it returns 0, otherwise it returns EOF,
     which is a constant defined in <stdio.h>, and is usually = -1.

     e.g.   fclose(fin);
            fclose(fout);
(4)  FILE *tmpfile(void)

     Usually we want a temporary file, for temporary storing of data.  
     This will open a file, and we NEED NOT supply a filename.
     The file may be used for both read/write.
     It returns an address (FILE pointer) if successful, or NULL if it fails in
     any way.
     The file will be deleted when program terminates.
(5)  int feof(FILE *ptr)

     This tests if "end of file" has been reached or not.
     If "eof" has been reached, it returns non-zero (i.e. "true".  Remember in C,
     any value other than 0 is regarded as "true".  0 means "false"),
     otherwise, it returns 0.

     e.g.
            if (feof(fin)) { ...... }

     Note that we seldom test EOF this way.  The reason being that EOF condition
     may be set before a read operation, or after a read operation, and is system
     dependent.  In practice, we test EOF using the return values of "fscanf(..)"
     etc.  You may find examples of testing EOF below.
(6) void rewind(FILE *ptr)

    This rewind the file to its starting position.  For example, we may write
    onto a temporary file, then rewind it, and read back the data.
(7)  int printf(char *format, ...)
     int sprintf(char *targetstring, char *format, ...)
     int fprintf(FILE *ptr, char *format, ...)

     We have discussed "printf" (print with format) in Chapter 4 "Simple Input and
     Output".

     There are two additional ones : sprintf(...) and fprintf(...)

     sprintf(...) will print onto a string.

     fprintf(...) will print onto a file.

     If the subroutine succeeds, it will return the number of characters printed,
     otherwise, it will return a negative number.
(8)  int scanf(char *format, ...)
     int sscanf(char *sourcestring, char *format, ...)
     int fscanf(FILE *ptr, char *format, ...)

     We have discussed "scanf" (scan with format) in Chapter 4 "Simple Input and
     Output".

     There are two additional ones : sscanf(...) and fscanf(...)

     sscanf(...) will scan from a string, (char *sourcestring, above)

     fscanf(...) will scan from a file.

     If successful, it will return the number of items that have been successfully
     scanned.  If it fails, it will return EOF (usually defined to be -1 in <stdio.h>),
     and the most probable reason for failure is End of File is reached.

     Incidentally, this is more useful and safer in testing EOF condition, than that
     of using "feof(FILE *ptr)".
(9) We have used

    int getchar(void)      /* gets one character from stdin, keyboard.
                              Returns character. */
    int putchar(int c)     /* prints one character to stdout, screen */
    char *gets(char *s)    /* gets one string from stdin, keyboard */
    char *puts(char *s)    /* prints one string to stdout, screen */

    For their counterparts that deal with file, we have

    int fgetc(FILE *ptr)   /* gets one character from file.  Returns character */
    int fputc(int c, FILE *ptr)    /* prints one character to file.  Returns character
                                      written */
    char *fgets(char *s, int n, FILE *ptr)  
                                   /* gets at most n-1 characters from file
                                      and put it in string char *s.
                                      One character is reserved for '\0' 
                                      If error occurs, usually when EOF is
                                      reached, it returns NULL */
    int fputs(char *s, FILE *ptr)  /* prints the string char *s to file  */

    If error occurs, they (except fgets(..)) return EOF.  (End of File
    Condition is regarded as an error too.)


    CAUTION : For gets(..), it will strip away the newline '\n' character and
              replaces it with '\0'  -  end of string character.
              For puts(..), it will automatically add '\n'.

              NOT so with fgets(...).  It will NOT strip away the '\n' character.
              For fputs(...), it will NOT add the '\n' character.

              Personally, I think this is a design flaw.  User would expect similar
              behavior from "gets(..), puts(...)"  and "fgets(...), fputs(...)"
              except one is from keyboard and the other, from file.

              Also for the scanf(...) or fscanf(...)  subroutine.  They will ignore
              blank (or newline, or tab), e.g.

                   scanf("%d%d", &i, &j);    /* If we key in one number, then press
                                                Enter (='\n'), then key in another
                                                number, they will be scanned in 
                                                correctly into i,j */

              But gets(..), fgets(..) will not ignore newline character '\n'.

    Exercise :  Will fscanf(...) work correctly in

                  fscanf(fin,"%d%d%d%d", &i, &j, &k, &l);

          if (1) The four numbers are on the same line in the data file, e.g.

                           30 40 50 73

             (2) If the four numbers are on separate lines in the data file, e.g.

                           30
                           40
                           50
                           73  ?

    Ans :   Yes. Because '\n' is ignored by scanf(..) or fscanf(..).

    I myself have some frustrating experiences with "scanf(..), fscanf(..)".  
    I don't know whether they are design flaws or that my computer has been
    sabotaged - my PC can be radio-controlled by persons I have no idea of.

    Suggested REMEDIES :

              (1)  Use gets(..), or fgets(...) to read in one whole line,
                   including the '\n' character, into a string first.  Then use
                   sscanf(...) (String_scan_with_format) to scan the string.
                   We will be using this method throughout this book, to avoid
                   problems caused by '\n'.


              (2)  Write custom scanning subroutines for your data file.  The
                   scanning subroutines need not be general purpose scanning
                   subroutines, but just for your particular data file.  This
                   will be illustrated by an example near the end of this chapter,
                   (Part 2) where we are to read a data file prepared by "Qbasic,
                   under MSDOS".


              (3)  Use "structure" to write and read files.  This will be explained
                   later (Part 2).  But unfortunately, files prepared this way 
                   cannot be typed out with "cat", i.e. they are not human-readable,
                   nor can the data file be prepared with an editor like "emacs".
                   Example of this use will be given later (Part 2).



Examples of using IO Subroutines

Exercise : We have the following programs in Qbasic Chapter 6,
OPEN "C:\test\master.txt" FOR OUTPUT AS #1

L20:
    INPUT "Enter name "; name$

    IF name$ <> "****" THEN

         INPUT "Enter age "; age
         INPUT "Enter telephone no. "; tel
         WRITE #1, name$, age, tel
         GOTO L20

    ELSE

         CLOSE #1
         END

    END IF

which accepts inputs from the keyboard, "name, age, tel", and then writes the data onto a file. The QBasic statement "write #1 .... " will output the data in the format, e.g.

"Wu Siu Yan",50,23003  
"tom",60,93939         
"mary",30,939393
"jackie chan",49,3003
Now write a C program, but output the file in the format
Wu Siu Yan
50
23003
tom
60
93939
mary
30
939393
jackie chan
49
3003
i.e. one data on one line. Use "char name[40], tel[24]".

Ans : e.g.
#include <c.h>

int main()
{    char name[40], tel[24], buffer[80];
     int age;
     FILE *fout;

     if ( (fout = fopen("master.txt","w") ) == NULL )
          {perror("Unable to open file");
           exit(1);
          }

 L20:
     printf("Enter name\n");
     gets(name);
     if ( strcmp(name,"****") == 0 )   
          {fclose(fout);
           return 0;
          }
     printf("Enter age\n");
     gets(buffer);
     sscanf(buffer,"%d",&age);
     printf("Enter telephone no\n");
     gets(tel);
     fprintf(fout,"%s\n%d\n%s\n", name, age, tel);
     printf("%s\n%d\n%s\n", name, age, tel);
     goto L20;

}

strcmp(s1,s2) is a subroutine described in <string.h>. It compares two strings.

Note that if you have not put the "#include .... " in "/home/tom/include/c.h", then you should "#include " and "#include ".

The command to compile should be somewhat like

gcc -Wall t1.c -I/home/tom/include/ -lm
if you have used <c.h.>.

Exercise : Write a C program to read in the above data file, and print them as
           Name                Age              Tel

         xxxxxxxx              xxx           xxxxxxxxx
         xxxxxxxx              xxx           xxxxxxxxx
Ans : e.g.
#include <c.h>

void erasenewline(char *a)
{    int c;
 L20:
     c=*a;
     if (c == '\n')
         {*a='\0';
          return;
         }
     a++;
     goto L20;
}

int main()
{    char name[40], tel[24], buffer[80];
     int age;
     FILE *fin;

     if ( (fin = fopen("master.txt","r") ) == NULL )  
          {perror("Unable to open file");
           exit(1);
          }

     printf("         Name                           Age             Tel\n\n");

 L20:
     /* Note : NULL means EOF in fgets(..) */
     if ( fgets(name,40,fin) == NULL )
          {fclose(fin);
           return 0;
          }
     fgets(buffer,80,fin);
     sscanf(buffer,"%d",&age);
     fgets(tel,24,fin);
     erasenewline(name);
     erasenewline(tel);
     printf("   %-40s  %3d  %-24s\n", name, age, tel);
     goto L20;

}

Note :

  1. Since fgets(..) will retain '\n', we write a small subroutine "erasenewline(char *a)" specifically for this purpose.
  2. For the format "%-40s", minus, "-", means "left-justified".
  3. It can be seen that C is very cumbersome as compared with Qbasic. It is because C is a minimal language, a standardized assembler. Anyhow, production program is written once only, it really doesn't matter for the extra coding involved. Moreover, craftsmanship demands patience. See how a carver spends tremendous time and patience in his work. You may choose to write your program in Qbasic or Perl too, they both require less coding effort. (If LORD's wills, I will write PERL after finishing with this C)

Exercise : First use "emacs" to create a file "/home/tom/data.txt" with the following data,

40.34 
52.12 
70.5 
38.213 
68.4 
Then write a program, to read in these data, and print them out, together with the average of them. Draw the flowchart first. Notice that the the file contains only numbers, hence we may use "fscanf(..)" to read in the data.

Ans :
#include <c.h>

int main()
{   double total = 0., x, average;
    int ncount = 0;
    FILE *fin;

    if ( (fin=fopen("data.txt","r")) == NULL )
        {perror("Unable to open file");
         exit(1);
        }

 L20 :
    if ( fscanf(fin,"%lf",&x) == EOF ) 
         {goto L40;}
    /* When EOF is reached, "fscanf(..)" returns EOF */
    ncount++;
    total += x;
    printf("%20.7f    %20.7f\n", x, total);
    goto L20;
 
 L40:
    average = total/ncount;
    printf("\nAverage is %20.7f\n\n", average);
    fclose(fin);
    return 0;
}

Notice that "%lf" is the format to read in a double precision number (= long floating point number).

Exercise : Use the program in the first exercise to create a master file "master.txt" with the following data, assuming that they are students in a tutorial class.

        Name        Age       Telephone No.
        ----        ---       ------------

        Tom          16         1234
        Mary         16         6635
        Jack         17         7899
        Lee See      16         1222
        Chan Sam     17         8386
Suppose they obtained the following results in a Maths test,
70.4 
68 
73.6 
55 
47 
Now write a program, that reads the names from "master.txt", and prints out the name one by one, and prompts for input, e.g.
       Tom  ?  70.4
       Mary  ?  68
       Jack  ?  73.6
       Lee See  ? 55
       Chan Sam  ? 47
And then write the test results in a file named "math.txt". i.e. the file "math.txt" should contain
70.4
68
73.6
55
47

Ans :

#include <c.h>

void erasenewline(char *a)
{    int c;
 L20:
     c=*a;
     if (c == '\n')
         {*a='\0';
          return;
         }
     a++;
     goto L20;
}

int main()
{    char name[40], buffer[80], filename[40];
     double xmark;
     FILE *fmaster, *fout;

     if ( (fmaster = fopen("master.txt","r") ) == NULL )  
          {perror("Unable to open master file");
           exit(1);
          }

     printf("Enter filename for output file\n");
     gets(filename);
     if ( (fout = fopen(filename,"w")) == NULL )
          {perror("Unable to open output file");
           exit(1);
          }

 L20:
     /* Note : NULL means EOF in fgets(..) */
     if ( fgets(name,40,fmaster) == NULL )
          {fclose(fmaster);
           fclose(fout);
           return 0;
          }
     /* These two statements read in age, tel, but will not use them */
     fgets(buffer,80,fmaster);
     fgets(buffer,80,fmaster);
     erasenewline(name);     /* get rids of newline character */
     printf("%s  ?  ", name);
     gets(buffer);
     sscanf(buffer,"%lf",&xmark);
     printf("%s %10.3f\n",name,xmark);
     fprintf(fout,"%10.3f\n",xmark);
     goto L20;

}

Exercise : Use the above program to enter the test results for English "eng.txt", and the test results for Chinese "chi.txt".

Suppose the English test results are

60
50
58
70
74
And the Chinese test results are
68
72
75
66.3
59
Now write a program that opens 4 files "master.txt", "math.txt", "eng.txt", and "chi.txt" and then prints a report to output file "out.txt", e.g.
       Student :   Tom

       Mathematics           70.4
       English               60.0
       Chinese               68.0

       Student :   Mary

       Mathematics           68.0
       English               50.0
       Chinese               72.0

           .....
           .....

Ans :

#include <c.h>

void erasenewline(char *a)
{    int c;
 L20:
     c=*a;
     if (c == '\n')
         {*a='\0';
          return;
         }
     a++;
     goto L20;
}

int main()
{    char name[40], buffer[80];
     double xmath, xeng, xchi;
     FILE *fmaster, *fout, *fmath, *feng, *fchi;
     int iflag=0;

     if ( (fmaster = fopen("master.txt","r") ) == NULL )  
          {perror("Unable to open master file");
           iflag=1;
          }
     if ( (fmath = fopen("math.txt","r") ) == NULL )  
          {perror("Unable to open maths file");
           iflag=1;
          }
     if ( (feng = fopen("eng.txt","r") ) == NULL )  
          {perror("Unable to open English file");
           iflag=1;
          }
     if ( (fchi = fopen("chi.txt","r") ) == NULL )  
          {perror("Unable to open Chinese file");
           iflag=1;
          }
     if ( (fout = fopen("out.txt","w") ) == NULL )  
          {perror("Unable to open output file");
           iflag=1;
          }

     if (iflag==1) {exit(1);}

 L20:
     /* Note : NULL means EOF in fgets(..) */
     if ( fgets(name,40,fmaster) == NULL )
          {fclose(fmaster);
           fclose(fmath);
           fclose(feng);
           fclose(fchi);
           fclose(fout);
           return 0;
          }
     /* These two statements read in age, tel, but will not use them */
     fgets(buffer,80,fmaster);
     fgets(buffer,80,fmaster);
     erasenewline(name);     /* get rids of newline character */
     fscanf(fmath,"%lf", &xmath);
     fscanf(feng,"%lf", &xeng);
     fscanf(fchi,"%lf", &xchi);
     fprintf(fout, "         Student        %-40s\n\n"
                   "     Mathematics                  %5.2f\n"
                   "     English                      %5.2f\n"
                   "     Chinese                      %5.2f\n\n\n"
             , name, xmath, xeng, xchi);
     goto L20;

}



When the number of files are large, it is advisable to use an array. The following program does the same as above, but uses arrays.

#include <c.h>

void erasenewline(char *a)
{    int c;
 L20:
     c=*a;
     if (c == '\n')
         {*a='\0';
          return;
         }
     a++;
     goto L20;
}

int main()
{    char name[40], buffer[80];
     char *fname[] = {"master.txt", "math.txt", "eng.txt", "chi.txt", "out.txt"};
     char *mode[] = {"r", "r", "r", "r", "w"};
     double xmark[5];
     int i, iflag;
     FILE *f[5];

     for (iflag=0, i=0; i<5; i++)
         {if ( (f[i] = fopen(fname[i],mode[i])) == NULL )  
               {printf("Unable to open %s\n",fname[i]);
                iflag=1;
               }
         }
     if (iflag==1) {exit(1);}

 L20:
     /* Note : NULL means EOF in fgets(..) */
     if ( fgets(name,40,f[0]) == NULL )
          {for (i=0;i<5; i++)
               {fclose(f[i]);
               }
           return 0;
          }
     /* These two statements read in age, tel, but will not use them */
     fgets(buffer,80,f[0]);
     fgets(buffer,80,f[0]);
     erasenewline(name);     /* get rids of newline character */
     for (i=1; i<=3; i++)
          {fscanf(f[i],"%lf", &xmark[i]);
          }

     fprintf(f[4], "         Student        %-40s\n\n"
                   "     Mathematics                  %5.2f\n"
                   "     English                      %5.2f\n"
                   "     Chinese                      %5.2f\n\n\n"
             , name, xmark[1], xmark[2], xmark[3]);
     goto L20;

}


In Part 2 of this Chapter, we shall discuss how to use "structure" in sequential files, and also how to read a file prepared by "Qbasic - MSDOS".


[Previous] [Home] [Next]