Reading and Writing Disk Files

Submitted by tushar pramanick on Sun, 03/10/2013 - 22:21

Reading and Writing Disk Files

The program in Listing 21.1 does not do anything with the text file, haiku.txt, except open and close it. In fact, there are two pieces of Japanese haiku, written by Sodo and Chora, saved in the haiku.txt file. So how can you read them from the file?

In C, you can perform I/O operations in the following ways:

    Read or write one character at a time.
    Read or write one line of text (that is, one character line) at a time.
    Read or write one block of characters at a time.

The following three sections explain the three ways to read and write to disk files.
One Character at a Time

Among the C I/O functions, there is a pair of functions, fgetc() and fputc(), that can be used to read from or write to a disk file one character at a time.

The syntax for the fgetc() function is

#include <stdio.h>
int fgetc(FILE *stream);

Here stream is the file pointer that is associated with a stream. The fgetc() function fetches the next character from the stream specified by stream. The function then returns the value of an int that is converted from the character.

The syntax for the fputc() function is

#include <stdio.h>
int fputc(int c , FILE *stream);

Here c is an int value that represents a character. In fact, the int value is converted to an unsigned char before being outputted. stream is the file pointer that is associated with a stream. The fputc() function returns the character written if the function is successful; otherwise, it returns EOF. After a character is written, the fputc() function advances the associated file pointer.

To learn how to use the fgetc() and fputc() functions, let's have a look at Listing 21.2, which contains a program that opens a text file, and then reads and writes one character at a time.
 

TYPE
Listing 21.2. Reading and writing one character at a time.


1:  /* 21L02.c: Reading and writing one character at a time */
2:  #include <stdio.h>
3:
4:  enum {SUCCESS, FAIL};
5:
6:  void CharReadWrite(FILE *fin, FILE *fout);
7:
8:  main(void)
9:  {
10:    FILE *fptr1, *fptr2;
11:    char filename1[]= "outhaiku.txt";
12:    char filename2[]= "haiku.txt";
13:    int reval = SUCCESS;
14:
15:    if ((fptr1 = fopen(filename1, "w")) == NULL){
16:       printf("Cannot open %s.\n", filename1);
17:       reval = FAIL;
18:    } else if ((fptr2 = fopen(filename2, "r")) == NULL){
19:       printf("Cannot open %s.\n", filename2);
20:       reval = FAIL;
21:    } else {
22:       CharReadWrite(fptr2, fptr1);
23:       fclose(fptr1);
24:       fclose(fptr2);
25:    }
26:
27:    return reval;
28: }
29: /* function definition */
30: void CharReadWrite(FILE *fin, FILE *fout)
31: {
32:    int c;
33:
34:    while ((c=fgetc(fin)) != EOF){
35:       fputc(c, fout);  /* write to a file */
36:       putchar(c);      /* put the character on the screen */
37:    }
38: }


    OUTPUT
    After running the executable 21L02.exe, I get the following output:

C:\app>21L02
Leading me along
my shadow goes back home
from looking at the moon.
--- Sodo
   (1641-1716)


ANALYSIS

A storm wind blows
out from among the grasses
the full moon grows.
--- Chora
   (1729-1781)
C:\app>

The purpose of the program in Listing 21.2 is to read one character from a file, write the character to another file, and then display the character on the screen. (You need to copy the file, haiku.txt, from the CD-ROM in the book, and put it in the same directory where you save the executable file 21L02.exe. haiku.txt is the text file that is going to be read by 21L02.exe.)

In Listing 21.2 there is a function called CharReadWrite(), which has two file pointers as its arguments. (See the declaration of the CharReadWrite() function in line 6.)

The statement in line 10 defines two file pointers, fptr1 and fptr2, which are used later in the program. Lines 11 and 12 define two character arrays, filename1 and filename2, and initialize the two arrays with two strings containing filenames, outhaiku.txt and haiku.txt.

In line 15, a text file with the name outhaiku.txt is opened for writing. outhaiku.txt is contained by the filename1 array. The file pointer fptr1 is associated with the file. If the fopen() function returns NULL, which means an error occurs, a warning message is printed out in line 16. Also, in line 17, the reval variable is assigned 1 and is represented by the enum name FAIL.

If the file outhaiku.txt is opened successfully, another text file, called haiku.txt, is opened for reading in line 18. The file pointer fptr2 is associated with the opened text file.

If no error occurs, the CharReadWrite() function is invoked in line 22 with two file pointers, fptr1 and fptr2, passed to the function as arguments. From the definition of the CharReadWrite() function in lines 30 and 38, we see that there is a while loop that keeps calling the fgetc() function to read the next character from the haiku.txt text file until the function reaches the end of the file. (See line 34.)

Within the while loop, the fputc() function in line 35 writes each character read from the haiku.txt file to another text file, outhaiku.txt, which is pointed to by fout. In addition, putchar() is called in line 36 in order to put the character returned by the fgetc() function on the screen.

After the CharReadWrite() function finishes its job, the two opened files, which are associated with fptr1 and fptr2, are closed with a call to the fclose() function respectively in lines 23 and 24.

As mentioned earlier, the haiku.txt file contains two pieces of Japanese haiku written by Sodo and Chora. If the program in Listing 21.2 is run successfully, we see the two pieces of haiku shown on the screen, and they are written into the outhaiku.txt file as well. You can view outhaiku.txt in a text editor to confirm that the content of haiku.txt has been correctly copied to outhaiku.txt.
One Line at a Time

Besides reading or writing one character at a time, you can also read or write one character line at time. There is a pair of C I/O functions, fgets() and fputs(), that allows you to do so.

The syntax for the fgets() function is

#include <stdio.h>
char *fgets(char *s, int n, FILE *stream);

Here s references a character array that is used to store characters read from the opened file pointed to by the file pointer stream. n specifies the maximum number of array elements. If it is successful, the fgets() function returns the char pointer s. If EOF is encountered, the fgets() function returns a null pointer and leaves the array untouched. If an error occurs, the function returns a null pointer, and the contents of the array are unknown.

The fgets() function can read up to n-1 characters, and can append a null character after the last character fetched, until a newline or an EOF is encountered. Note that if a newline is encountered during the reading, the fgets() function includes the newline in the array. This is different from what the gets() function does. The gets() function just replaces the newline character with a null character. (The gets() function was introduced in Hour 13, "Manipulating Strings.")

The syntax for the fputs() function is

#include <stdio.h>
int fputs(const char *s, FILE *stream);

Here s points to the array that contains the characters to be written to a file associated with the file pointer stream. The const modifier indicates that the content of the array pointed to by s cannot be changed. (You learned about the const modifier in Hour 14, "Scope and Storage Classes in C.") If it fails, the fputs() function returns a nonzero value; otherwise, it returns zero.

Note that the character array must include a null character at the end as the terminator to the fputs() function. Also, unlike the puts() function, the fputs() function does not insert a newline character to the string written to a file. (The puts() function was introduced in Hour 13.)

We can modify the program in Listing 21.2 to read or write one character line at a time by calling the fgets() and fputs() functions. The modified version is shown in Listing 21.3.

TYPE
Listing 21.3. Reading and writing one character line at a time.


1:  /* 21L03.c: Reading and writing one line at a time */
2:  #include <stdio.h>
3:
4:  enum {SUCCESS, FAIL, MAX_LEN = 81};
5:
6:  void LineReadWrite(FILE *fin, FILE *fout);
7:
8:  main(void)
9:  {
10:    FILE *fptr1, *fptr2;
11:    char filename1[]= "outhaiku.txt";
12:    char filename2[]= "haiku.txt";
13:    int reval = SUCCESS;
14:
15:    if ((fptr1 = fopen(filename1, "w")) == NULL){
16:       printf("Cannot open %s for writing.\n", filename1);
17:       reval = FAIL;
18:    } else if ((fptr2 = fopen(filename2, "r")) == NULL){
19:       printf("Cannot open %s for reading.\n", filename2);
20:       reval = FAIL;
21:    } else {
22:       LineReadWrite(fptr2, fptr1);
23:       fclose(fptr1);
24:       fclose(fptr2);
25:    }
26:
27:    return reval;
28: }
29: /* function definition */
30: void LineReadWrite(FILE *fin, FILE *fout)
31: {
32:    char buff[MAX_LEN];
33:
34:    while (fgets(buff, MAX_LEN, fin) != NULL){
35:       fputs(buff, fout);
36:       printf("%s", buff);
37:    }
38: }


    OUTPUT
    Because the program in Listing 21.3 reads the same text file, haiku.txt, as the program in Listing 21.2 did, I get the same output on the screen:

C:\app>21L03
Leading me along
my shadow goes back home
from looking at the moon.
--- Sodo
   (1641-1716)

A storm wind blows
out from among the grasses
the full moon grows.
--- Chora
   (1729-1781)
C:\app>

ANALYSIS

From the program in Listing 21.3, you can see that a function called LineReadWrite() has replaced the CharReadWrite() function.

The definition of the LineReadWrite() function is shown in lines 30_38. The fgets() function is called repeatedly in a while loop to read one character line at a time from the haiku.txt text file, until it reaches the end of the text file. In line 34, the array name buff and the maximum number of the array elements MAX_LEN are passed to the fgets() function, along with the file pointer fin that is associated with the opened haiku.txt file.

Meanwhile, each line read by the fgets() function is written to another opened text file called outhaiku.txt that is associated with the file pointer fout. This is done by invoking the fputs() function in line 35.

The statement in line 36 prints the contents of each string on the screen so that you see the two pieces of Japanese verse after running the program in Listing 21.3. Also, you can view the outhaiku.txt file in a text editor to make sure that the contents of the haiku.txt file have been copied to the outhaiku.txt file.
One Block at a Time

SYNTAX
If you like, you can also read or write a block of data at a time. In C, there are two I/O functions, fread() and fwrite(), that can be used to perform block I/O operations. The fread() and fwrite() functions are mirror images of each other.

The syntax for the fread() function is

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t n, FILE *stream);


Here ptr is a pointer to an array in which the data is stored. size indicates the size of each array element. n specifies the number of elements to read. stream is a file pointer that is associated with the opened file for reading. size_t is an integral type defined in the header file stdio.h. The fread() function returns the number of elements actually read.

SYNTAX
The number of elements read by the fread() function should be equal to the value specified by the third argument to the function, unless an error occurs or an EOF (end-of-file) is encountered. The fread() function returns the number of elements that are actually read, if an error occurs or an EOF is encountered.

The syntax for the fwrite() function is

#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t n, FILE *stream);

 

Here ptr references the array that contains the data to be written to an opened file pointed to by the file pointer stream. size indicates the size of each element in the array. n specifies the number of elements to be written. The fwrite() function returns the number of elements actually written.

If there is no error occurring, the number returned by fwrite() should be the same as the third argument in the function. The return value may be less than the specified value if an error occurs.

Note that it's the programmer's responsibility to ensure that the array is large enough to hold data for either the fread() function or the fwrite() function.

SYNTAX
In C, a function called feof() can be used to determine when the end of a file is encountered. This function is more useful when you're reading a binary file because the values of some bytes may be equal to the value of EOF. If you determine the end of a binary file by checking the value returned by fread(), you may end up at the wrong position. Using the feof() function helps you to avoid mistakes in determining the end of a file.

The syntax for the feof() function is

#include <stdio.h>
int feof(FILE *stream);


Here stream is the file pointer that is associated with an opened file. The feof() function returns 0 if the end of the file has not been reached; otherwise, it returns a nonzero integer.

TYPE
The program in Listing 21.4 demonstrates how to read and write one block of characters at a time by calling the fread() and fwrite() functions. In fact, the program in Listing 21.4 is another modified version of the program from Listing 21.2.

Listing 21.4. Reading and writing one block of characters at a time.

1:  /* 21L04.c: Reading and writing one block at a time */
2:  #include <stdio.h>
3:
4:  enum {SUCCESS, FAIL, MAX_LEN = 80};
5:
6:  void BlockReadWrite(FILE *fin, FILE *fout);
7:  int ErrorMsg(char *str);
8:
9:  main(void)
10: {
11:    FILE *fptr1, *fptr2;
12:    char filename1[]= "outhaiku.txt";
13:    char filename2[]= "haiku.txt";
14:    int reval = SUCCESS;
15:
16:    if ((fptr1 = fopen(filename1, "w")) == NULL){
17:       reval = ErrorMsg(filename1);
18:    } else if ((fptr2 = fopen(filename2, "r")) == NULL){
19:       reval = ErrorMsg(filename2);
20:    } else {
21:       BlockReadWrite(fptr2, fptr1);
22:       fclose(fptr1);
23:       fclose(fptr2);
24:    }
25:
26:    return reval;
27: }
28: /* function definition */
29: void BlockReadWrite(FILE *fin, FILE *fout)
30: {
31:    int num;
32:    char buff[MAX_LEN + 1];
33:
34:    while (!feof(fin)){
35:       num = fread(buff, sizeof(char), MAX_LEN, fin);
36:       buff[num * sizeof(char)] = `\0';  /* append a null character */
37:       printf("%s", buff);
38:       fwrite(buff, sizeof(char), num, fout);
39:    }
40: }
41: /* function definition */
42: int ErrorMsg(char *str)
43: {
44:    printf("Cannot open %s.\n", str);
45:    return FAIL;
46: }


    OUTPUT
    Again, I get the same output on the screen because the program in Listing 21.4 also reads the same text file, haiku.txt:

C:\app>21L04
Leading me along
my shadow goes back home
from looking at the moon.
--- Sodo
   (1641-1716)

A storm wind blows
out from among the grasses
the full moon grows.
--- Chora
   (1729-1781)
C:\app>

ANALYSIS
The purpose of the program in Listing 21.4 is to show you how to invoke the fread() and fwrite() functions in your program to perform block I/O operations. In List-ing 21.4, the haiku.txt file is read by the fread() function, and then the fwrite() function is used to write the contents read from haiku.txt to another file called outhaiku.txt. We call the two C I/O functions from our own function, BlockReadWrite().

From the definition of the BlockReadWrite() function in lines 29_40, you can see that a character array called buff is defined with the number of elements of MAX_LEN + 1 in line 32, although we only read MAX_LEN number of characters by calling the fread() function in line 35. The reason is that we append a null character in line 36 after the last character read so that we ensure the block of characters saved in buff is treated as a string and can be printed out on the screen properly by the printf() function in line 37.

The while loop, shown in lines 34_39, keeps calling the fread() function to read a character block with MAX_LEN elements, until the feof() function in line 34 returns 0, which means that the end of the text file has been reached. As shown in lines 35 and 38, we use the sizof operator to measure the size of the char data type because the elements in the buff array are all characters.

If everything goes smoothly, you should see the Japanese verses again on the screen or in the outhaiku.txt file after running the program in Listing 21.4.

Comments

Related Items

মডুলার C প্রোগ্রামিং (Modular C Programming)

কেবল মাত্র একটি ফাংশন দিয়ে কোনো বড়ো জটিল সমস্যা সমাধানের চেষ্টা করা ভাল প্রোগ্রামিংয়ের পদ্ধতি নয়। সঠিক পদ্ধতি হ'ল সমস্যাটিকে কয়েকটি ছোট ছোট এবং সরল টুকরো করে ফেলা যাতে তা আরও বিশদে বোঝা যায় । তারপরে এই ছোট এবং সরল সমস্যাগুলি সমাধান করার জন্য ছোট ছোট ফাংশন ব্লক তৈরি করা এবং পরে সেগুলি নিয়মানুযায়ী সংযোজিত করা ।

Programming Style

Programming Style

In this section, I'd like to briefly highlight some points that will help you write clean programs that can easily be read, understood, and maintained.

Exercises : Answer the following Question

To help solidify your understanding of this hour's lesson, you are encouraged to answer the quiz questions and finish the exercises provided in the Workshop before you move to the next lesson.

Question and Answer

    Q Is the C preprocessor part of the C compiler?

    A No. The C preprocessor is not part of the C compiler. With its own line-oriented grammar and syntax, the C preprocessor runs before the compiler in order to handle named constants, macros, and inclusion of files.

Compiling Your Code Under Conditions

Compiling Your Code Under Conditions

You can select portions of your C program that you want to compile by using a set of preprocessor directives. This is useful, especially when you're testing a piece of new code or debugging a portion of code.