Streams and Files - III


The following topics are covered in this section:


Modes of Opening Files

When you open a file (for writing or reading), there are 6 modes that you can use to specify how you want the file to be opened. The usually used modes are:

ios::app
ios::ate
ios::binary
ios::in
ios::out
ios::trunc

So far we've seen the file opening as follows:

ofstream out;
out.open("test");         // This means test is opened in normal mode.

By default this is the same as

out.open("test", ios::out); // opened specifically for output

We can combine two modes using the bitwise OR operator:

out.open("test", ios::out | ios::binary );

This will open a stream in output and in binary mode.

The different methods for opening a file are summarized in the table below:

Opening Mode

Function

ios::in

File opened in input mode (for ifstream objects)

ios::out

File opened in output mode (for ofstream objects)

ios::app

Append to the file. All output operations (like writing to file) are done at the end of the file.

ios::trunc

Delete file if it exists and create a new file.

ios::ate

Open file and go to the end of the file.

ios::binary

Open file in binary mode (by default files are opened in text mode).

ios::nocreate

Only if file exists will it open

ios::noreplace

The file should not exist (otherwise open fails).

An example is shown below:

#include <iostream.h>
#include <fstream.h>

int main( )
{
ifstream istr("test.txt",ios::in| ios::nocreate); //error if file not present
if (istr.fail( ) )
{
cout<<"failed to open - no such file";
}
return 0;
}

The fail( ) function is inherited by the ofstream, fstream and ifstream classes. Hence we can check whether the open( ) operation was successful or not using this function.

‘fstream’ objects can be used to operate in either input or output modes.


What are Binary Files?

Reading and writing formatted text is easy and is always used for displaying data to the user. But sometimes you may need to work with unformatted data (i.e. in binary or raw form) and not as text. When we refer to formatted text it means that the information has been processed to make the output understandable (like using the ASCII table for conversion). If you want to perform binary operations on a file, you should open it in binary mode. If a file is opened in text mode (or normal mode) and you make use of these functions (that are intended for binary mode), the functions will work but some character translations will occur. This might seem to be really confusing.

Basically there are two types of files: binary files and text files. Binary files can contain any data representation format. Text files contain only alphanumeric data as represented by the compiler's character set (for example ASCII). By default, all files are opened in text mode unless you specify ios::binary. When in text mode, the file is considered as containing a number of lines each ending with a "\n". As a binary file, it is considered simply as a sequence of bytes. Unless, the OS maintains different file formats for a binary file and for a text file, these modes of opening are irrelevant. But is always advisable to use the binary mode while opening files (even text files). An example of how character translation can occur is while using the ‘\n’ (newline character). In a binary file this will be stored as a single character. If the OS distinguishes between text and binary files, then when you store a ‘\n’ in a text file it will be stored as two characters. Certain functions used in file I/O will work only in binary files; so it is better to always work on binary files.

Another simpler example is the case of storing numbers in a file. Let’s say we have:

int x = 124;

If you write ‘x’ to a file in text mode then the program will convert 124 into it’s equivalent ASCII form (i.e. it will take each digit separately, convert the digit to the ASCII value and then store the ASCII value in the file). If you read this file using Notepad you’ll see the number 124 displayed on the screen.

Suppose you store the same number to a file in binary mode, the program will convert 124 directly into its binary value and store this binary value in the file. Now if you try to read the file in Notepad you’ll see some weird character on your screen (because Notepad will apply ASCII decoding since it thinks that everything you open in Notepad is stored in ASCII format).


Writing to a File

The << operator can be used to write to a file and this is the easiest method of writing to a file.

#include <iostream.h>
#include <fstream.h>

int main ( )
{
ofstream out("trial.txt", ios::out);         // Create a stream for output linked to test.txt
out<<"First "<<1<<endl;
out<<"Second "<<2<<endl;
out.close( );
return 0;
}

In the above program, instead of writing to the screen, we want to write to a particular file. So we create a stream called ‘out’ for output and open the file trial.txt. It is better to clearly specify the method of opening the file (ios::out).

out<<"First "<<1<<endl;

does the following things: it writes the word ‘First’ and the number ‘1’ to the stream ‘out’. Remember that ‘out’ has been linked to the file ‘trial.txt’. Hence ‘First’ and ‘1’ are actually written to the file. Similarly ‘Second’ and ‘2’ are written on the next line of the file because of the endl keyword. ‘Endl’ will lead to a newline character being written at the end of the line. Finally we close the stream.


Reading a File

In a similar way we can use the >> operator to read data from a file. The file created in the earlier program can be read using this program.

#include <iostream.h>
#include <fstream.h>
int main ( )
{
ifstream in("trial.txt", ios::in);
if(!in)             // If file doesn't exist then the program will quit.
{
    cout<<"File cannot be opened";
    return 1;
}

char string[15];
int num;
in>>string>>num;
cout<<string<<num<<endl;
in>>string>>num;
cout<<string<<num<<endl;
in.close( );
return 0;
}

>> is the extraction operator. As you know, it is used for obtaining an input from the keyboard along with ‘cin’ (console in). Suppose you declare an integer ‘x’, you can get the value from the user using the statement:

cin>> x ;

What the user types is stored in the variable ‘x’. Similarly in the above program what the code:

in>>string>>num;

reads from the stream called ‘in’. It will store what it reads in the variables ‘string’ and ‘num’. The process is as follows:

The file is opened. The system reads the file (trial.txt which has been opened). It reads the first 15 characters. The first word in the file is ‘First’. After this it encounters a blank space. Hence the array ‘string’ is terminated (because the >> operator will not save whitespaces). The word ‘First’ is stored in the character array ‘string’. Next comes a number in the file. This is stored in the variable ‘num’ that we have declared.

The next statement is for displaying whatever was read from the file. After this remember that the program has reached the end of the first line of the file. It now automatically moves to the second line of the file. Hence when we repeat the statement:

in>>string>>num ;

and the program will read the second line of the file. The process just repeats and the word ‘Second’ and ‘2’ are now stored in ‘string’ and ‘num’.

Remember: The << and >> operator will write the data to a file in text format irrespective of whether you open the file in text or binary format. To check this try to write an integer to the file and then open the file in Notepad (if it were written in binary format you would not see the same number on the screen). To handle files in binary format we need to use other functions like write( ), get( ), put( ) etc., which will be discussed later.


Accessing other Devices (printer and monitor) through streams

All streams work similarly even though they are connected to different devices (like files, printer or the monitor). Every stream is associated to a device (or a ‘file’) using the open function.

//program to print in the printer as well as on the screen

# include <iostream.h>
#include <fstream.h>
int main( )
{
ofstream monitor;
ofstream print;
print.open("LPT1");
print<<"Hi This is the printer.";
monitor.open("CON");
monitor<<endl<<"This is the monitor.";
monitor.close( );
print.close( );
return 0;
}

The output (in your monitor display) will be:

This is the monitor.

Your printer (if it is connected to the system) will print:

Hi This is the printer.

In the above program, we create two streams named ‘monitor’ and ‘print’. The open function links these streams to the CON (the console which is your monitor) and LPT1 (which is the computer port where your printer will be connected). Hence instead of linking the streams to a disk file we have linked it to the monitor and printer. But the mechanism for information flow is the same as for disk file streams.

print<<"Hi This is the printer.";

The information transfer follows the direction of the arrows and hence it flows into the stream called ‘print’. Since this is associated to the printer, the sentence will be printed in your printer.


Get ( )and Put ( )

In the earlier example program to read and write into files we knew what data was present in the file (i.e. we knew that there was a string followed by an integer and hence it was easy to read the data from the file). Usually you won’t know what is stored in a file and this is where the get ( ) and put ( ) functions will be handy. get and put functions read and write unformatted data. Both these functions operate on single characters only.
fet ( ) is used to read a character from a stream while put ( ) is used to write a character.

// Program to create a file, write to it and then display the contents of the file

#include <iostream.h>
#include <fstream.h>
int main( )
{
ofstream out("c:/test.txt",ios::out | ios::binary);
char letter;
cout<<"\nEnter what you want to store: ";
cin>>letter;

while (letter != '*')
{
        out.put(letter);
        cin>>letter;
}
out.close( );

//Now we want to read the file
cout<<"\nThe contents of the file are: ";
char ch;
ifstream in("c:/test.txt", ios::in | ios::binary);

while (in)
{
        in.get(ch);
        if (in)
        {
                cout<<ch;
        }
}

in.close( );
return 0;
}

The output is:

Enter what you want to store: check*
The contents of the file are: check

The function put( ) puts a character into the stream (actually into the file) and get ( ) gets a character from the stream. put ( ) and get ( ) cannot be used individually on their own. They are member functions of the stream classes and hence they have to be called using objects belonging to those classes. In the above program till an asterisk (*) is entered by the user, all the characters will be stored into the file.
Let us examine some of the main lines in the program:

while (in)

This condition becomes false only when the end of file is reached. So till the end of file is reached we keep getting the characters one by one and then displaying them one by one on the screen.

Assume that we have stored the word ‘check’ in the file test.txt. When the compiler opens the file for reading, it has a small bookmarker that points to the beginning of the file. The bookmarker points at the letter ‘c’. It gets the letter ‘c’ (using the get(ch) statement) and then displays it on the screen. The compiler knows it still hasn't reached the end of file. So, the next time it goes through the while loop, the bookmarker has moved to the next character (i.e to ‘h’). The process repeats till the end of file is reached.

Remember: You cannot save spaces (blank spaces) using put and get functions. Try it in the above program and see what happens!

If in the above program the input were as below would yield:

Enter what you want to store: A Fine Morning*
The contents of the file are: AFineMorning

As you can see, the whitespaces are not stored. The put( ) will basically ignore whitespaces. One more point to note in the above program:

ofstream out("c:/test.txt",ios::out | ios::binary);

Usually we refer to files by saying c:\test.txt but here we have used:

c:/test.txt

In C++ you should always use this slash within the double quotes whenever you are referring to directory/drive paths. Let’s suppose that we use the normal slash, then the statement would become:

ofstream out("c:\test.txt",ios::out | ios::binary);

If you notice, within the double quotes the compiler will encounter a ‘\t’ and this actually corresponds to an escape sequence. Hence the compiler will not go to the C drive itself.

Another alternative to c:/test.txt is to use double slashes as below:

ofstream out("c:\\test.txt",ios::out | ios::binary);

This method is also accepted in C++ programming.

Beware: Make use of forward slash (c:/test.txt) or double backslash (c:\\test.txt) instead of single backslash (c:\test.txt) when referring to file locations in C++.


Go back to the Contents Page 2


Copyright © 2004 Sethu Subramanian All rights reserved.