Learn files programming in C: practical tutorial 3 (Brief introduction to files in C)
"In the Last tutorial, I got a bit carried away. The first question I asked was a bit too much for most C beginners; but if you have a good experience programming in other languages and had shifted to learning C, then I guess that the previous tutorial might have been of at-most use to you in understanding basic C file handling functions." -K Sreram
"To follow this tutorial you should be familiar with the basic file opening and closing operations using fopen, fclose; and should also be familiar with fscanf and fprintf operations to do simple read write operations."
fprintf and fscanf functions are not opt for reading and writing binary data to a file. The reason is, many internal conversions take place during the read/write process. Not all operating systems have the same binary value for next line, enter, back space or just space characters. If you write a program and want it to be portable, you choose the functions to use carefully. Here, you don't always consider the working of your program but consider the actual purpose of the function you use. If fprintf is used, it means you have intended to write text to a destined file. If you use fprintf like a binary writing function, you must note that you could allow unexpected conversions to take place, if the program was compiled for a different platform. Also using these functions for binary read-write is a bad practice as it could affect the readability of your source. After all what matters is portability and readability of your source code. And more over, the extra conversations are brought about by extra processes (which does the task of checking, rearranging, etc.,) which takes place apart from reading the data from the file, which is not time-vice efficient (if you intend to use fprintf like fwrite).
As listed out in tutorial 1, there are a variety of functions for read and write; its better to know all of them; we may choose the one opt for the particular situation we face. Let me give you example programs using those functions and explain their use in detail.
Section 1: using functions fopen, fclose, fgets, fputs
program 1: using functions fopen, fgets, fclose:
/*
* this program example uses fgets function to get an input from a file that already exist
* prototype: char* fgets (char* variable, int size, FILE* fileStream);
*/
#include <stdio.h> // for functions: fopen, fgets, fclose
#include <conio.h> // for function: getch
int main()
{
char text[100];
FILE* f = fopen("fName.txt", "r"); //opens file named fName.txt, present in the local directory in read mode
if(f==NULL) // checks if the file opened correctly
{
printf("error: unable to open file fName.txt");
getch();
return -1; // returns error signal
}
while(!(fgets(text, sizeof(char)*100, f)==NULL)) //attempts to read data from the file using gets
printf("%s\n", &text); // displays the data
/* This function causes data to be read in blocks of sizeof(char)*100 units each time. Each time the
* characters are read, they are displayed on the application's console output screen. It stops reading
* a line either if it encounters a next line character or the exceeds the size (here its 100)
*/
fclose(f); // clearing all pointers and closing the stream
getch();
return 0;
}
This program just reads data from a file and displays them on the screen. Note that we had given the size of the text variable as a parameter along with the C string as another parameter and the stream to which the data is to be written as another parameter. This size parameter ensures that only a certain number of characters are read from the steam and written to the variable named 'text'; its also to ensure the safety of the function. The at-most number of values the 'text' parameter can hold is of size 'sizeof(char)*100'. If the value entered exceeds the maximum size of the C string, we can expect a run-time error of attempting to access an inaccessible memory location. when we either declare a variable or dynamically allocate one, a specific memory location automatically gets reserved for our program. If we attempt to read from a memory location that's not reserved for our program, then the operating system terminates the program immediately. The fgets function stops reading and returns when it comes across the following characters: '\n', '\0', '\r'. fgets returns the pointer to the string text, in the above case; if the read operation had been a failure, it returns the value NULL. Again, this function is only used for text based read operation.
basic causes for the failure of "fgets" operation (during failure, fgets returns NULL):
- The file being read is accessed by another application.
- The end-of-file is reached.
/*
* this program example uses fputs function to write to a file
* prototype: int fputs (char* variable, FILE* fileStream);
*/
#include <stdio.h> // for functions: fopen, fgets, fclose
#include <conio.h> // for function: getch
#include <strings.h>
int main()
{
char text[100];
FILE* f = fopen("fName.txt", "w"); //opens file named fName.txt, present in the local directory in write mode
if(f==NULL) // checks if the file opened correctly
{
printf("error: unable to open file fName.txt");
getch();
return -1; // returns error signal
}
while (1)// starts an infinite loop
{
gets(text); // obtains the text as input
if(strcmp(text, "exit") == NULL) // checks for the string in text: if its "exit" the loop terminates
break;
if(fputs(text, f) == EOF) // writes data to file, and checks if its done right.
{
printf("unable to write to file");
getch();
return -1;
}
}
fclose(f); // clearing all pointers and closing the stream
getch();
return 0;
}
There is one difference between determining end-of-file, while reading data from a file using fgets and writing data do a file using fputs, fgets does not return an integer type signal of the status of reading from the file, instead returns the pointer to the C string to which the read data is stored; In case of failure, a null pointer is returned (i.e., 0). Whereas, fputs returns EOF (or -1) in case of failure and zero in case of success. There is a specific function in C for testing if the file had reached the end-of-file. Its 'int feof(FILE* streamName);' this returns a non zero value when the pointer had reached the end-of-file. This signals the end of file. But if the pointer hadn't reached the file's end, zero is returned.
Using feof when other functions used here accomplishes the same task is not desirable. In this case, the run-time of the program is very less and making slight changes such as adding an feof function to detect the file's end (while reading the data from file) is not going to make any difference. But in real time situations where each function is bound to be called many times before the termination of a particular task or the program, we may avoid calling certain functions often or unnecessary as it could hinder the run-time of the task or the application drastically.
program 3: using functions fopen, fclose, fgets, fputs:
/*
* program to copy the contents of a text file to another;
*/
#include <stdio.h>
int main()
{
FILE *f_read, *f_write; // a stream for reading and another stream for writing
char readFile[260],writeFile[260]; // names of files or directories are stored here. Note that
// for windows operating system, the value 260 denotes the maximum
// size of a file name
char data[100]; // a C string used as a temporary buffer to store read data and to write
// it to the destined file.
printf("enter the name of the file to read from (or its directory):"); // get the names or paths of the files
gets(readFile);
printf("enter the name of the file to write to (or its directory):");
gets(writeFile);
f_read = fopen(readFile, "r"); // opens the first stream in read mode
f_write = fopen(writeFile, "w"); // opens the second stream in write mode
while(!(fgets(data, 100, f_read) == NULL)) // gets data until a read error occurs.
fputs(data, f_write); //writes data to stream.
fclose(f_read); // close the streams and clear all memory.
fclose(f_write);
// an alternative to substitute the above two statements could be fcloseall()
return 0;
}
The above code asks for the name/path of the source file to read from and the name/path of the destination file. It then opens both the files and links them to the streams 'f_read' and 'f_write' declared in the main function. Once they are open, the function 'fgets' is made to read data from the file associated with the stream 'f_read' and writes them to the file associated with the stream 'f_write'. This read/write process continues until the 'fgets' function encounters an error. This happens when the file's pointer had reached the file's end.
After the read/write process gets over, both the streams are closed. In this example, both the streams were closed separately, but in situations where there are more than one streams opened and its needed to close them all, then the function 'fcloseall()' can be called. This function closes all the streams other than the basic ones like 'stdin', 'stdout' and 'stderr'.
Section 2: using functions fopen, fclose, fgetc, fputc:
program 1: using functions fopen, fclose, fgetc
/*
* program to count the number of a give character in a particular file using getc
*/
#include <stdio.h>
#include <conio.h>
int main()
{
int count = 0;
char fName[260], ch, ch2;
FILE *f;
printf("enter the file name:");
gets(fName);
printf("\nenter the character to search:");
scanf("%c", &ch);
f = fopen(fName, "r");
if(f == NULL)
{
printf("error opening file %s", fName);
return -1;
}
while(!((ch2=fgetc(f))==EOF)) //fgetc returns the character that was read from the stream f
if(ch == ch2) // it returns -1 in case of error. It is because, there isn't any text based
++count; // character with the value -1.
printf("number of %c's in the file %s is %d", ch, fName,count);
fclose(f);
getch();
return 0;
}
Note that the function fgetc can be interchangeably written as getc. The only difference is, getc is a macro, which is dangerous at some times, and fgetc is a function. Macros are sometimes unsafe. lets say you have a macro defined as '#define foo(a) a*(a+1)/2'. Now, lets pass on an argument, 'b = b+1' to the macro foo. Lets say, foo(b = b
* program to read text form a text file and convert each sentence's
* starting word's starting letter to upper case. There is a grammatical
* rule in English according to which the sentence's starting word's starting
* letter should be made upper case compulsorily.
*/
#include <stdio.h>
#include <conio.h>
#include <ctype.h>// for isalpha and toupper
int main()
{
FILE *fr, *fw;
char rfName[260], wfName[260], data;
int truth = 1; // signals 1 if the next character read should be converted to upper-case
printf("enter the name of the file to read from (or the directory):");
gets(rfName);
printf("\nenter the name of the file to write to (or its directory):");
gets(wfName);
fr = fopen(rfName, "r");
if(fr == NULL)
{
printf("\nunable to open file %s", rfName);
return -1;
}
fw = fopen(wfName, "w");
if(fw == NULL)
{
printf("\nunable to open file %s", wfName);
return -1;
}
while(!feof(fr))
{
data = fgetc(fr);
if(data == EOF)
{
printf("error: reading data from file");
return -1;
}
else if(data == '.' || data == '!' || data == '?')
truth = 1;
else if(isalpha(data) && truth)
{
data = toupper(data);
truth = 0;
}
if(fputc(data, fw) == EOF) // fputc returns EOF if there was a error reading and sets stderr
{
printf("error writing to file");
return -1;
}
}
fclose(fw);
fclose(fw);
getch();
return 0;
}
copyright (c) 2015 K Sreram. You may not distribute this article without the concerned permission from K Sreram.
About my blog