10.3.1 Sequential Files

C has no built-in ability to perform input/output. Input/output is done by using the standard C library <stdio> or by developing a user library. The printf and scanf functions used in the programs of the book are in this standard library.The <stdio> library contains a number of other functions that also perform input/output.

C input/output is character-oriented. All reads or writes to or from keyboards, screens, tapes, or disks are character-oriented. C reads or writes in bytes.The functions that read or write numbers or strings in turn use calls to the basic character-oriented input/output functions.

There are two ways to read or write files in C. They are termed high-level and low-level input/output. In high-level input/output, reading and writing is done a character at a time. The input/output is buffered, and the programmer does not have to consider buffer sizes or other characteristics of the operating system. The buffers, however, are not available to the programmer. In low-level input/output, which is essentially UNIX-type input/output to provide a UNIX interface, the buffers are not provided; the programmer must provide buffers and pointers to control the input/output process. In the following discussion only high-level input/output will be considered.

The simplest organization for a file is sequential. A sequential file is a sequence of records. The records may or may not be kept in sorted order in the sequence. In standard C input/output all files are sequential files. A record of a file is not necessarily declared to be of type structure. Although file records are typically of type structure, a file record may also be declared to be of type integer, float, character, or any other C type. All records of a file need not be the same type.

Example 10.3

To illustrate how records composed of structures are treated, suppose you are asked to keep a master file, bookinventory, consisting of records representing all books currently in stock in a book store. Each record is to contain a title, author, publisher, price, and currentstock field. These declarations may be used:

typedef struct

{

char title[MAXTITLE];

char author[MAXAUTHOR];

char publisher[MAXPUBLISHER];

float price;

int currentstock;

}bookrecords;

bookrecords bookinventory; n

The name of a file is a variable within a C program. Unlike other variables, it represents stored information that may have been available before the execution of the program or that may be available after the execution of the program. A file named f is depicted in Figure 10.1.

Figure 10.1 File F and F ?a name="02fe_0921">

Associated with each file is a file pointer, which always points to a record or is positioned prior to the first record or after the last record. File pointers must be declared and are of type FILE. For example,

FILE *f_ptr;

declares f_ptr as a file pointer.

A record may be read from a file or written to a file. A read or write statement refers to the record indicated by the current position of the file pointer. Each file also has a portion of main memory, called the buffer, associated with it. When the file pointer is pointing to a file record, the file buffer contains a copy of that record's contents.

To attach a file to a program, it is necessary to execute an open statement. For reading the records in the file containing the bookinventory records, the following code is used.

>Comment

f_ptr = fopen("bookinventory", "r");

if(f_ptr == NULL)

printf("Can not open the file");

F_ptr is the pointer to the file bookinventory. The r signifies that the file will be opened for reading only and that the file is already in existence.

Executing the first statement causes the file pointer f_ptr to be positioned to the first record. If any error occurs and the file can not be opened, the file pointer will returns a NULL value. For this reason, it is a wise policy always to test for the NULL pointer when opening a file, as was done above.

If the open is executed with a w (instead of an r) it opens the file for writing. If no file by the name used exists, it creates a new file and places the file pointer at the beginning of the file. If a file already exists, it places the file pointer at the beginning of the file, in effect erasing or truncating the file. If a file is opened with an a, it creates a new file or, if one already exists, positions the pointer at the end of the file for appending items. A similar set of values, r+, w+, and a+ is used to open files for both reading and writing.

To read a single record or numbers of records from a file the fread( ) function is used. The general form is

fread(  pointer to the location of the ,  size of ,  number of,  pointer to  )

        memory in which the records       record to  records to  the file to

        read are to be placed             be read    be read     be read

For example, to read a single record from the opened file:

fread(&bookinventory,sizeof(bookrecords),1,f_ptr);

Executing this statement will cause the value of the record pointed to by the file pointer to be copied into memory starting at the memory location pointed to by &bookinventory. Also, fread returns the number of records read, or zero when the end of file is reached.

To write a record to a file that is open for writing:

fwrite(&bookinventory,sizeof(bookrecords),1,f_ptr);

Executing this statement will cause the value of the record starting at the location pointed to by &bookinventory to be copied into the storage pointed to by the file pointer, and the file pointer moved to point to the storage to be used for the next record.

Reading from a file thus causes information in secondary storage (the file) to be transferred to variables whose values are stored in internal memory. Writing to a file causes the information stored in internal memory to be transferred to secondary storage.

Example 10.4

To illustrate how files composed of single variables such as integers are treated and the effect of the file operations, consider the following program. The file prime will be created initially to consist of the first 10 primes. Note that the reading and writing are done with fscanf and fprintf. They are like scanf and printf except that they work on files of individual variables. In contrast, fread and fwrite work on files of records. Also, fscanf returns EOF when the end of the file is reached or an error occurs. n

#define NULL 0

#include <stdio>

main()

/* Creates a file consisting of the first 10 prime

numbers, and a file containing these primes in

reversed order, and prints the contents of both

the files.

*/

{

/* This section creates file prime and

stores the first ten primes in it.

*/

int a[20],i,dummy;

FILE *f_ptr,*fr_ptr,*fopen();

>Comment

f_ptr = fopen("prime","w");

>Comment

fprintf(f_ptr,"%d",2);

fprintf(f_ptr,"%d",3);

fprintf(f_ptr,"%d",5);

fprintf(f_ptr,"%d",7);

fprintf(f_ptr,"%d",11);

fprintf(f_ptr,?/FONT>%d?/FONT>,13);

fprintf(f_ptr,"%d",17);

fprintf(f_ptr,"%d",19);

fprintf(f_ptr,"%d",23);

fprintf(f_ptr,"%d",29);

>Comment

fclose(f_ptr);

/* This section reads from the prime file,

fills a with its contents, and creates

reversedprime which will contain

the contents of a in reversed order.

*/

i = 0;

>Comment

(1) f_ptr = fopen("prime","r");

>Comment

if(f_ptr == NULL)

{

printf("Can not open");

exit(-1);

}

>Comment

while(fscanf(f_ptr,"%d",&dummy) != EOF)

{

>Comment

a[i] = dummy;

i++;

}

>Comment

i--;

>Comment

(2) fr_ptr = fopen("reversedprime","w");

>Comment

if(fr_ptr == NULL)

{

printf("Can not open");

exit(-1);

}

>Comment

while(i >= 0)

{

>Comment

fprintf(fr_ptr,"%d",a[i]);

i--;

}

>Comment

fclose(f_ptr);

>Comment

fclose(fr_ptr);

/* This section prints, at the terminal,

the contents of prime and then of

reversedprime.

*/

>Comment

f_ptr = fopen("prime","r");

>Comment

while(fscanf(f_ptr,"%d",&dummy) != EOF)

printf("\n %d\n",dummy);

>Comment

fr_ptr = fopen("reversedprime","r");

>Comment

while(fscanf(fr_ptr,"%d",&dummy) != EOF)

printf("\n %d\n",dummy);

>Comment

fclose(f_ptr);

>Comment

fclose(fr_ptr);

}

Prime and reversedprime are files of integers and a is an array of integers.

After (1) fopen is executed, the file called prime and its file pointer can be depicted as in Figure 10.2(a). After nine executions of the first while loop body, the situation will be as shown in Figure 10.2(b).

The tenth execution sets a [9] to 29 and moves the file pointer beyond the last file record. The EOF test yields the value true, so the loop is exited with i set at 10.

After (2) fopen is executed, reversedprime appears as in Figure 10.3(a). After the second while loop body has been executed nine times, reversedprime looks like Figure 10.3(b). The last execution yields the file shown in Figure 10.3(c).

The standard function, exit, closes any open files, flushes any buffered output, signals what has happened, and terminates program execution. A zero parameter value, by convention, signals that all is well, while other values may have different meanings. The -1 is used here to signal that a file could not be opened. Files can also be closed by simply invoking the function fclose, whose parameter is the file pointer of the file to be closed. It is important to close all files appropriately.

Suppose you wished to append the eleventh prime, 31, to prime after 29. This can be done directly in C by opening the file prime to its end so that items can be appended. To do this, as noted earlier, the file need only be opened with the a to position the file pointer

f_ptr = fopen("prime","a");

fprintf(f_ptr,"%d",31);

(a) After FOPEN Is Executed

(b) After Nine Executions of the While Loop

Figure 10.2 File PRIME

(a) After FOPEN Is Executed

(b) After Nine Executions of the Second While Loop

(c) After Last Execution

Figure 10.3 File REVERSEDPRIME

Example 10.5

Suppose there are two files with records of the same type in sorted order based on key value. Merge the two files. n

Producing a new file consisting of all the records of the two files in sorted order is called merging the two files. The merging of two files is a basic component of efficient methods of external sorting. The task in Example 10.5 is to write a function to produce a merged file assuming records are ordered with the smallest key value appearing in the first record.

The basic idea is straightforward. Read a record from each sorted file, and append the record with the smaller key value to the merged file. Read the next record of the file that had the record with the smaller key value. Compare it with the record that has not yet been appended. Append the record with the smaller key value to the merged file. Repeat this process of reading and comparing records from the two files until one of the files becomes empty. Then append the records on the remaining file to the merged file.

Assume that the files to be merged are not empty and have been opened with filepointers f1_ptr,f2_ptr, and f3_ptr. Filepointers f1_ptr and f2_ptr point to the input files to be merged. Filepointer f3_ptr points to the resultant merged file. The files are made up of records of typedeffilerecords, with key the field of filerecords on which merging is to be based. The merge may be written as follows:

merge(f1_ptr,f2_ptr,f3_prt)

/* Merges the files pointed to by f1_ptr

and f2_ptr. The merged file is pointed

to by f3_prt. The files must be opened before the merge is invoked.

*/

FILE *f1_ptr,*f2_ptr,*f3_ptr;

{

struct filerecords record1,record2;

int flag,length;

flag = 0;

length = sizeof(filerecords);

>Comment

fread(&record1,length,1,f1_ptr);

fread(&record2,length,1,f2_ptr);

>Comment

while(flag == 0)

>Comment

if(record1.key < record2.key

{

>Comment

fwrite(&record1,length,1,f3_ptr);

>Comment

if(fread(&record1,length,1,f1_ptr) == 0)

{

flag = 1;

>Comment

fwrite(&record2,1ength,1,f3_ptr);

}

}

>Comment

else

{

>Comment

fwrite(&record2,length,1,f3_ptr);

>Comment

if(fread(&record2,length,1,f2_ptr) == 0)

{

flag = 2;

>Comment

fwrite (&record1,length,1,f3_ptr);

}

}

>Comment

if(flag == 1)

>Comment

while(fread(&record2,length,1,f2_ptr) != 0)

fwrite(&record2,length,1,f3_ptr);

>Comment

else

>Comment

while(fread(&record1,1ength,1,f1_ptr) != 0)

fwrite(&record1,1ength,1,f3_ptr);

}

The files must be opened before the merge is invoked.

The following program, when the merge is included, merges two files and prints the resultant file.

#define NULL 0

#include <stdio>

typedef struct

{

int key;

}filerecords;

main()

/* Merges two files and prints the merged file.

*/

{

filerecords record;

FILE *f1_ptr,*f2_ptr,*f3_ptr;

>Comment

f1_ptr = fopen("prime","r");

if(f1_ptr == NULL)

{

printf("Can not open file1");

exit(-1);

}

f2_ptr = fopen("other","r");

if(f2_ptr == NULL)

{

printf("Can not open file2");

exit(-1);

}

f3_ptr = fopen("result","w");

if(f3_ptr == NULL)

{

printf("Can not open file3");

exit(-1);

}

>Comment

merge(f1_ptr,f2_ptr,f3_ptr);

>Comment

fclose(f1_ptr);

fclose(f2_ptr);

fclose(f3_ptr);

>Comment

f3_ptr = fopen("result","r");

>Comment

while(fread(&record,sizeof(filerecords),1,f3_ptr) != 0)

printf("\n %d\n",record.key);

>Comment

fclose(f3_ptr);

}