In all the C programs considered so far, we have assumed that the input data was read from standard input and the output was displayed on the standard output. These programs are adequate if the volume of data involved is not large. However many business-related applications require that a large amount of data be read, processed and saved for later use. In such a case, the data is stored on a storage device, usually a disk.
Introduction
So far we have dealt with various input/output functions like printf(), scanf(), getchar() etc. Now let us pay attention to the functions related to disk I/O.
These functions can be broadly divided into two categories.
- High-level file I/O functions also called as standard I/O or stream I/O functions.
- Low-level file I/O functions also called as system I/O functions.
The low-level disk I/O functions are more closely related to the computer’s operating system than the high-level disk I/O functions. This post is concerned only with high-level disk I/O functions.
As you can see the high-level file I/O functions are further categorised into text and binary but this chapter will deal only with text mode. We will directly jump to functions, which perform file I/O in high-level, unformatted text mode.
Unformatted High-Level I/O Functions
Opening a file with fopen() function
Before we can write information to a file on a disk or read it, we must open the file. Opening a file establishes a link between the program and the operating system. The link between our program and the operating system is a structure called FILE, which has been defined in the header file “stdio.h”. The FILE structure contains information about the file being used, such as current size, location in memory etc. So a file pointer is a pointer variable of the type FILE.
It is declared as
FILE *fp;
where fp is a pointer of FILE type.
The general format of fopen() is:
FILE *fp; fp=fopen(“file_name”, “type”);
where,
file_name – is character string that contains the name of the file to be opened.
Type – is a character string having one of the following modes in which we can open a file.
File Type/ File mode | Meaning |
---|---|
r | Opens an existing file for reading only. If the file does not exist, it returns NULL. |
w | Opens a new file for writing only. If the file exists, then it’s contents are overwritten. Returns NULL, if unable to open file. |
a | Opens an existing file for appending. If the file does not exist then a new file is created. Returns NULL, if unable to open file. |
r+ | Opens an existing file for reading, writing and modifying the existing contents of the file. Returns NULL, if unable to open file. |
w+ | Opens a new file for both reading and writing. If the file already exists then it’s contents are destroyed. Returns NULL, if unable to open file. |
a+ | Opens an existing file for reading and appending. If the file does not exist, then a new file is created. |
Closing a file with fclose() function
When we have finished working with the file, we need to close the file. This is done using the function fclose() through the statement.
fclose(fp);
fclose closes the file to which the file pointer fp points to. It also writes the buffered data in the file before close is performed.
Character Input/Output in Files
The getc() and putc() functions can be used for character I/O. They are used to read and write a single character from/to a file.
The function getc()
The function getc() is used to read characters from a file opened in read mode by fopen(). The general format is:
getc(fp);
getc() gets the next character from the input file to which the file pointer fp points to. The function getc() will return an end-of-file EOF marker when the end of the file has been reached or if it encounters an error.
The function putc()
The general format of putc() is:
putc(c,fp);
where putc() function is used to write characters to a disk file that can be opened using fopen()in “w” mode. fp is the file pointer and c is the character to be written to the file. On success the function putc() will return the value that it has written to the file, otherwise it returns EOF.
Now we have seen functions fopen(), fclose(), getc(), putc() etc. As a practical use of the above functions we can copy the contents of one file into another.
/* This program takes the contents of a text file and copies into another text file, character by character */ # include <stdio.h> void main(void) { FILE *fs,*ft; char ch; fs=fopen(“pr1.c”,”r”); /* open file in read mode */ if(fs==NULL) { puts(“Cannot open source file”); exit(0); } ft=fopen(“pr2.c”,”w”); /* open file in write mode */ if(ft==NULL) { puts(“Cannot open target file”); fclose(fs); exit(0); } while(1) { ch=getc(fs); if(ch==EOF) break; putc(ch,ft); } fclose(fs); fclose(ft); }
Types of Files
ASCII Text files
A text file can be a stream of characters that a computer can process sequentially. It is not only processed sequentially but only in forward direction. For this reason a text file is usually opened for only one kind of operation (reading, writing, or appending) at any given time.
Similarly, since text files only process characters, they can only read or write data one character at a time. (In C Programming Language, Functions are provided that deal with lines of text, but these still essentially process data one character at a time.) A text stream in C is a special kind of file. Depending on the requirements of the operating system, newline characters may be converted to or from carriage-return/linefeed combinations depending on whether data is being written to, or read from, the file. Other character conversions may also occur to satisfy the storage requirements of the operating system. These translations occur transparently and they occur because the programmer has signalled the intention to process a text file.
We’ll learn about text file processing with an example. This program will create a file a.txt if it does not exist, accept contents from the user and display the contents. If it already exists it will ask whether the user wants to overwrite or not. If the user does not want to overwrite it will simply display the contents, otherwise it will ask for new contents, write to the file and display the contents.
#include<stdio.h> #include<conio.h> void main() { FILE *fp; char ch,ch1; int f=0; clrscr(); fp=fopen("a.txt","r"); if(fp==NULL) f=1; else { fclose(fp); printf("File exists. Do you want to overwrite[Y/N]:"); ch1=getche(); } if(f==1 || ch1=='y' || ch1=='Y') { fp=fopen("a.txt","w"); if(fp==NULL) printf("File cannot be created"); else { printf("\nEnter contents for a.txt...\n"); ch=getche(); while(ch!=26) /* Ascii value of Ctrl+Z */ { if(ch==13) { printf("\n"); fputc('\n',fp); } else fputc(ch,fp); ch=getche(); } printf("\b ^Z File Saved"); fclose(fp); } } fp=fopen("a.txt","r"); if(fp==NULL) printf("File cannot be opened"); else { printf("\n Contents of a.txt...\n"); ch=fgetc(fp); while(!feof(fp)) { putchar(ch); ch=fgetc(fp); } fclose(fp); } getch(); }
Binary files
A binary file is no different to a text file. It is a collection of bytes. In C Programming Language a byte and a character are equivalent. Hence a binary file is also referred to as a character stream, but there are two essential differences. No special processing of the data occurs and each byte of data is transferred to or from the disk unprocessed.
C Programming Language places no constructs on the file, and it may be read from, or written to, in any manner chosen by the programmer. Binary files can be either processed sequentially or, depending on the needs of the application, they can be processed using random access techniques. In C Programming Language, processing a file using random access techniques involves moving the current file position to an appropriate place in the file before reading or writing data. This indicates a second characteristic of binary files – they a generally processed using read and write operations simultaneously.
For example, a database file will be created and processed as a binary file. A record update operation will involve locating the appropriate record, reading the record into memory, modifying it in some way, and finally writing the record back to disk at its appropriate location in the file. These kinds of operations are common to many binary files, but are rarely found in applications that process text files.
We’ll discuss about binary file handling with an example. This program creates a database like file and allows you to add records and display them. You can ask them to enhance this by making search employee, delete employee, modify employee and sort employees on the basis of names.
#include<stdio.h> #include<conio.h> typedef struct emp { int eno; char name[30]; float sal; }EMP; void getemployee(EMP *t) { printf("Employee No.:%d",t->eno); printf("\nEnter Name:"); fflush(stdin); gets(t->name); printf("Enter Salary:"); scanf("%f",&t->sal); } void putemployee(EMP t) { printf("\n %d %s %.2f",t.eno,t.name,t.sal); } void addrecord() { FILE *fp; int c=0; EMP e,e1; fp=fopen("emp.dat","ab+"); if(fp==NULL) printf("File cannot be created"); else { rewind(fp); fread(&e,sizeof(EMP),1,fp); while(!feof(fp)) { c++; e1=e; fread(&e,sizeof(EMP),1,fp); } if(c==0) e.eno=100; else e.eno=e1.eno+1; getemployee(&e); fwrite(&e,sizeof(EMP),1,fp); fclose(fp); } } void displayrecords() { FILE *fp; EMP e; fp=fopen("emp.dat","rb"); if(fp==NULL) printf("File cannot be opened"); else { fread(&e,sizeof(EMP),1,fp); while(!feof(fp)) { putemployee(e); fread(&e,sizeof(EMP),1,fp); } fclose(fp); } }
void main() { int ch=0; while(ch!=3) { clrscr(); puts("1.Add a record"); puts("2.Display all records"); puts("3.Exit"); printf("Enter your choice:"); scanf("%d",&ch); switch(ch) { case 1: addrecord(); break case 2: displayrecords(); break; } getch(); } }
String(Line) Input/Output in Files
We have seen putc() and getc() functions as character I/O in files. But reading or writing strings of characters from and to files is as easy as reading and writing individual characters.
The functions fgets() and fputs() can be used for string I/O.
Library Call fgets()
The routine fgets() is used to read a line of text from a file. The general format is:
char *fgets( char *s, int n, FILE *fp);
The function fgets() reads character from the stream fp into the character array ’s’ until a newline character is read, or end-of-file is reached, or n-1 characters have been read. It then appends the terminating null character after the last character read and returns ‘s’. If end-of-file occurs before reading any character or an error occurs during input fgets() returns NULL.
Library Call fputs()
The routine fputs() is used to write a line of text from a file. The general format is:
int fputs(const char *s, FILE *fp);
The function fputs() writes to the stream fp except the terminating null character of string s. It returns EOF if an error occurs during output otherwise it returns a nonnegative value.
The program given below writes strings to a file using the function fputs().
Program to accept the text and write it in the file:
/* Receives strings from keyboard and writes them to file. */ #include<stdio.h> void main(void) { FILE *fp; char s[80]; fp=fopen(“test.txt”,”w”); if(fp==NULL) { puts(“Cannot open file”); exit(0); } printf(“Enter few lines of text \n “); while(strlen(gets(s)) >0) { fputs(s,fp); fputs(“\n”,fp); } fclose(fp); }
In this program we have set up a character array to receive the string, the fputs() function then writes the contents of the array to the disk. Since the fputs() function does not automatically add a newline character we have done this explicitly.
Program to read strings from the file and display them on the screen:
/* Program to read strings from the file and displays them on the screen */ #include<stdio.h> void main(void) { FILE *fp; char s[80]; fp=fopen(“test.txt”,”r”); if(fp==NULL) { puts(“Cannot open file”); exit(0); } while(fgets(s,79,fp) !=NULL) printf(“%s”,s); fclose(fp); }
The function fgets() takes three arguments. The first is the address where the string is stored and second is the maximum length of the string. This argument prevents fgets() from reading it too long a string and overflowing the array. The third argument is the pointer to the structure FILE.
Formatted High-Level Disk I/O Functions
C language provides two functions fprintf() and fscanf() which provides formatted Input/Output to the files. The functions fprintf() and fscanf() are used in the same manner as scanf() and printf() and require a file pointer as their first argument.
The Library Function fprintf()
The general format is:
int fprintf(fp,format,s) FILE *fp; char *format;
– The call fprintf() places output on the named output to which the file pointer fp points,
– s represents the arguments whose values are printed.
– format is the format specifier string. The format conventions of printf() work exactly same with fprintf().
The function fscanf()
The function fscanf() reads from the file to which the file pointer points.
The general format is:
int fscanf(fp,format,s) FILE *fp; char *format;
The function fscanf() reads from the file to which the file pointer fp is pointing. fscanf() returns the number of values read.
– format is the format specifier string.
– s represents the arguments (or buffer area) where data is stored after the read operation.
The following program shows the use of fprintf() and fscanf().
/* This program is taking input from keyboard and writing it to the file and then printing on the screen */ # include<stdio.h> void main(void) { FILE *fp; char s[80]; if ((fp=fopen(“test.txt”,”w”))==NULL) { printf(“Cannot open the file \n”); exit(0); } fscanf(stdin,”%[^\n]”,s);/* reading from the keyboard */ fprintf(fp,”%s”,s); /* writing to the file */ fclose(fp); if((fp=fopen(“test.txt”,”r”))==NULL) { printf(“Cannot open the file \n”); exit(); } fscanf(fp,”%[^\n]”,s); /* reading from the file */ fprintf(stdout,”%s”,s); /* printing on the screen */ }
Direct Input/Output
Direct input/output functions provide facilities to read and write a certain number of data items of specified size. The functions are fread() and fwrite().
Library Call fread()
The general format is:
int fread(ptr,size,nitems,fp) char *ptr; int size,nitems; FILE *fp;
The function fread() reads into array ptr upto nitems data items of size size from the stream fp and returns the number of items read. If an error is encountered fread() returns EOF otherwise returns the number of items read.
The file position indicator is advanced by the number of characters successfully read. For example, assuming 4-byte integers, the statement:
rchar=fread(buf,sizeof(int),20,input);
reads 80 characters from input into the array buf and assigns 80 to rchar, unless an error or end-of-file occurs.
Library Call fwrite()
The general format is:
int fwrite(ptr,size,nitems,fp) char *ptr; int size,nitems; FILE *fp;
The function fwrite() appends at the most nitems item of data of size size in the file to which the file pointer fp points to, from the array to which the pointer ptr points to.
The function returns the number of items written on success, otherwise EOF if an error is encountered. The file position indicator is advanced by the number of characters successfully written. For example,
wchar=fwrite(buf,sizeof(char),80,output);
writes 80 characters from the array buf to output, advances the file position indicator for output by 80 bytes. and assigns 80 to wchar unless an error or end-of-file occurs. One of the most useful applications of fread() and fwrite() involves the reading and writing of user defined data types, especially structures.
A simple mailing_list program using fread() and fwrite() is given below. The functions load() and save() perform the loading and saving operations of the database.
# include <stdio.h> # include <string.h> # define SIZE 100 void int_list(void); void enter(); void display(void); void save(void); void load(void); void menu(); int i,t; struct list_type { char name[20]; char street[2]; char city[10]; char state[3]; char pin[10]; }list[SIZE]; void main(void) { char choice; printf(“Enter choice (e/d/s/l/q)”); scanf(“%c”,&choice); for(;;) { switch(choice) { case 'e': enter(); break; case 'd': display(); break; case 's': save(); break; case 'l': load(); break; case 'q': exit(); break; } } } void int_list(void) /* initialize the list */ { register int t; for(t=0;t<100;t++) strcpy(list[t].name,"\0");/*zero length signifies empty */ } void enter(void) { register int i; for(i=0;i<SIZE;i++) if(!*list[i].name) break; if(i==SIZE) { printf("list full\n"); return; } printf("name"); gets(list[i].name); printf("Street:"); gets(list[i].street); printf("State:"); gets(list[i].state); printf("Pin:"); gets(list[i].pin); } /* display the list */ void display(void) { register int t; for(t=0;t<SIZE;t++) printf("%s\n",list[t].name); /* printf all the information the same way */ } /* save the list */ void save(void) { FILE *fp; if((fp=fopen("maillist","w+"))==NULL) { printf("Cannot open file \n"); return; } } /* load the file */ void load(void) { FILE *fp; register int i; if((fp=fopen("maillist","r+"))==NULL) { printf("Cannot open file \n"); return; } } void menu(void) { /* print choices and return appropriate choice */ }
Error Handling Functions
The error handling functions provide facilities to test whether EOF returned by a function indicates an end-of-file or an error.
The function feof()
Because the buffered file system is designed to handle both text and binary files, it is necessary that there should be some way other than the return value of getc() to determine that the end-of-file mark is also a valid integer value that could occur in a binary file.
The general format is:
int feof(FILE *fp);
Where fp is a valid file pointer. The function feof()returns true (non-zero) if the end of the file pointed to by fp has been reached otherwise it returns zero.
The function ferror()
The general format is:
int ferror(FILE *fp);
The function ferror() returns a non-zero value if the error indicator is set for the stream fp and 0 otherwise.
The function perror()
The general format is:
void perror(const char *s);
The function perror() writes to the standard error output stderr the string s followed by a colon and a space and then an implementation-defined error message corresponding to the integer in errno, terminated by a newline character. The program given below receives records from keyboard, writes them to a file and also display them on the screen.
#include<stdio.h> void main(void) { FILE *fp,*fpr; char another='Y'; struct emp { char name[40]; int age; float bs; }; struct emp e; fp=fopen("emp.dat","w"); if(fp==NULL) { puts("Cannot open file"); exit(0); } while(another=='Y') { printf("\n enter name , age basic salary\n"); scanf("%s%d%f",&e.name,&e.age,&e.bs); fwrite(&e,sizeof(e),1,fp); printf("Add another record (Y/N)"); fflush(stdin); another=getchar(); } fclose(fp); fpr=fopen("emp.dat","r"); if(fpr==NULL) { puts("Cannot open file"); exit(0); } while(fread(&e,sizeof(e),1,fpr)==1) printf("%s %d %f \n",e.name,e.age,e.bs); fclose(fpr); }
File Positioning
A file may be accessed sequentially or randomly. In a sequential access, all the preceding data is accessed before accessing a specific portion of a file. Random access permits direct access to a specific portion of a file. fseek(), ftell() and rewind() are the functions used in random access of a file.
The function fseek()
The general format is:
int fseek(FILE *fp,long offset, int ptrname);
fseek() sets the position of the next input or output operation in the file to which the file pointer fp points to. The new position is at the signed distance offset bytes from the beginning , from the current position or from the end of the file depending upon the value of the ptrname. The third argument can be either SEEK_CUR, SEEK_END or SEEK_SET.
The function returns 0 when successful otherwise a nonzero value.
- SEEK_END means move the pointer from the end of the file.
- SEEK_CUR means move the pointer from the current position.
- SEEK_SET means move the pointer from the beginning of the file.
Here are some examples of calls to fseek() and their effect on the file position indicator.
fseek(fp,n,SEEK_CUR) | sets cursor ahead from current position by n bytes |
---|---|
fseek(fp,-n,SEEK_CUR) | sets cursor back from current position by n bytes |
fseek(fp,0,SEEK_END) | sets cursor to the end of the file |
fseek(fp,o,SEEK_SET) | sets cursor to the beginning of the file |
The Function ftell()
The general format is:
long ftell(FILE *fp);
The function ftell() returns the current value of the file position indicator associated with fp.
The function rewind()
The general format is:
void rewind(FILE *fp);
The function rewind() resets the current value of the file position indicator associated with fp to the beginning of the file.
The call:
rewind(fp);
has the same effect as:
void fseek( fp,0,SEEK_SET);
The use of rewind() allows a program to read through a file more than once without having to close and open the file again.
Command Line Arguments (Using ARGC and ARGV Parameters)
The main() function takes two arguments called argv and argc.
The general format is:
main(argc,argv) int argc; char *argv[ ];
The integer argc (argument count) contains the number of arguments in the command line, including the command name.
argv (argument vector) is an array which contains addresses of each arguments. When there is a need to pass information into a program while you are running it, then the information can be passed into the main() function through the built in arguments argc and argv.
Consider an example that will print your name on the screen if you type it directly after the program name.
/* Program that explains argc and argv */ # include <stdio.h> main(argc,argv) int argc; char *argv[ ]; { if (argc==1) { printf(“ You forgot to type your name \n”); exit(); } printf(“Hello %s”, argv[1]); }
Output:
% Hello Message You forgot to type your name % Hello Message Boston’s Hello Boston’s
Program to copy one file to another using command line arguments:
/* This program copies one file to another using command line arguments */ #include <stdio.h> main(int argc, char *argv[ ]) { char ch; FILE *fp1, *fp2; if ((fp1=fopen(argv[1],”r”))==NULL) { printf(“Cannot open file %s \n”,argv[1]); exit(); } if ((fp2=fopen(argv[2],”w”))==NULL) { printf(“Cannot open file %s \n”,argv[2]); exit(); } while((ch=getc(fp1))!=EOF) /* read a character from one file */ putc(ch,fp2); fclose(fp1); fclose(fp2); }
Output:
mcopy pr1.c pr2.c (pr1.c will get copied to pr2.c)