Docstoc

File Handling and Streams in C++

Document Sample
File Handling and Streams in C++ Powered By Docstoc
					                          File Handling and Streams in C++

Writing more advanced code in C/C++ might not always be a trivial task and due to
this you might have to run several times a application until you manage to eliminate all
problems and make it complete. Now in the case of most of the applications some input
is required at a basic level at least, this can turn out to be quite large so typing inside
the console each time is definitely a good way to waste precious time of yours.

However, programming in C++ should be about looking over the details and see the
big pictures instead of trivial task like typing in the same input repeatedly. The solution
to evade this kind of practices is to use an input that will not be cleared after the
console closes. Off course, we are talking about files, preferably in text format.

Throughout this article, you will learn how to handle a good data flow in both in and out
ways with the help of the streams. These are member of a greater collection of streams
in the library section named Iostream.

I strongly recommend reading my introduction articles about streams that appeared
here on the Developer Shed Network under the names Introduction to Stream and
Basic IO in C++ because these will provide a better view about streams if you do not
have yet one about them. However is your call if you want to follow through them or
not. On their own, the articles should be just as useful also (independently).

If you want to know more about this search up my article about streams in general in
Introduction to Streams or in a little more detailed about the structure of the library in
the article titled Basic IO in C++ (this also treats the cin/cout streams).

The header where all of this is included is named <fstream> so be sure to have it
included each time you want to use the library. Adding the using namespace std will
make your typing easier and the readability of your code will increase. For start to use
them, like anything in C++, you need to declare a type of it.

Basic Tasks

Inside the Iostream is declared the three types of existing file streams: the input only
stream as “ifstream”, the output only stream as “ofstream”, and a combination of the
two under the name of “fstream”.

You have two different methods of opening/binding files to these streams. The first and
the most convenient place to do so are in the constructor. Here you can simply pass
further the file name that also contains the extension type and let the rest to be
resolved internally.

ofstream out (“In.txt”);

Of course, this will be created in the default directory (that is where the application is
running) but you can give as parameter the path also to specify directly where to
create the file. Alternatively, in case of an ifstream where to find the file and try to
open it. Note that it has an additional constructor in within as second argument you can
pass on some open flags but about this, we will talk later on.

The second place where you can bind a file to the stream is by calling the open member
function. This has the same capabilities as the constructor and will act just as so. You
provide the input place while the library will take care of all processes needed in order
to open that for reading or writing into it.

ofstream out;
out.open(“In.txt”); // this will have the same effect as the
                  //upper code

Here I should note that both of types require having as argument a “C style” string
array due some historical events mainly. In event you got your path inside a STL
based string you can/need simply to ask from the string a char* ( or wchar_t*) pointer
using the “c_str()” member function as follows:

string name(“C:\\In.txt”); // note that \\ = \ for the string
ofstream out(name.c_str());

Sometimes the file cannot be created (not enough space) or just cannot be opened for
write because the file is read only. Checking if any errors occurred in the open process
can be checked easily by checking if the stream is valid.

if(!out)
{
// here we react to the invalid file open
}

Whenever an error occurs during any process of a stream (be that a file stream also)
there is a fail flag inside the class that will be set to false. Until this is turn on any task
will be invalid and most of the functions will just return an eof, just as if the stream has
no more to offer. You can reset this by calling the clear function:

out.clear() // reset the error state of the stream

Generally closing a stream is not required explicitly as when the program finishes
during the destruction of the stream the file will be automatically closed by an advised
move is to do this explicitly whenever you are not using the file. Let others to use it or
just avoid clumsy errors that could come if you decide later to other tasks on a different
file and reuse this stream.

Yes, you read that right! You can re-use a declared stream once you close it and call
the clear function is case of any errors might have popped up earlier. Let’s assume we
have a couple file names inside a vector of strings and we want to read all data inside a
single string. Act as follows.

std::vector<string> files;
// fill up the vector
std::vector<string>::iterator it = files.begin();
std::vector<string>::iterator end = files.end();
ifstream in;
string temp;

while( it != end)
{
in.open (it->c_str());
if(in)
while(in >> temp /* = in*/)
{// do whatever you want with the input
}
in.close();
in.clear();

}

You can observe that in fact after we read in a value from the stream the while will
check not the input data, rather than the input stream. To comprehend this you should
know that the line could be translated to a member function like this:
in.operator<<(temp).

This function returns the stream itself to allow a chain construction of the insertion and
extraction operators. Therefore, the “while” will check the stream what is reduced to
checking if no error flag is turned on.
The way C++ tracks what have you written/read in a file is that at the opening process
of a file beside associating to the stream one existing file on the HDD and open that in
an appropriate way it will also create inside the stream object a file position indicator
(FPI). This will help the stream tracking where you are inside the file, what have you
written/read recently.

Ignoring some input characters is possible by using the ignore() member function what
gets as argument the number of characters to ignore. This is for those cases when you
may have some invalid input and you want to just pass it, not get the ugly error state
every time. Moving through the file is possible with the seekg(), seekp(), tellg().


The Mode Flags

Each time you open a file via a stream the open procedure and how the file behave all
controlled via some internal flags of the fstream class. Even when you specify nothing
clearly, the default values will be used. The file modes are just integral values
essentially so we can combine them with a bitwise operator.

Here is a list of what are available and what their effect on the file stream is:

    => in - open for input
    => out - open for output
    => app - seek to the end before every write
    => ate -seek to the end immediately after the open
    => trunc - truncate an existing stream when opening it
    => binary - do the I/O procedures in binary mode (the stream will consist of ones
and zeros)


Of course, not all of them have the same effect on the three types of file streams and
some do not even have a sense on a specific. The trunk flag will eventually delete the
previous data inside the file. Opening an ofstream file with just the out flag is
essentially just as you would have declared the trunk flag also as it would clear
anything that it has been there before.

The solution, if you only want to append into the file, is to use the app flag with the
ofstream. In case of the fstream, the trunc no longer is there by default so all of the
previous data inside the file will remain.

The Binary Flag

The binary flag may seem a little unnecessary at first. It exists for performance
reasons. Windows works much faster in this system as it does not have to worry about
the characters, whitespaces, and so on. Here it just reads a sequence of bits, less effort
invested, faster done. Everyone is happy.

Here is a prime example of this. How do you acquire how many characters are inside a
file? You need nothing else just the number of characters and you need it really fast.
The complete solution is below. I used all that we learned until now and in addition the
read function.

This is a low-level operating function letting us read chunks of bytes instead proper
strings. However as follows it is using C arrays instead of strings, so I had to construct
one that will serve as a buffer for the application:


#include     <iostream>
#include     <fstream>
#include     <iomanip>
#include     <vector>
#include <iterator>
#include <time.h>

using namespace std;

#define BUFFER_SIZE 100000 // how many bytes to read at once
#define ADD_ITEM 100000 // how many additional items to add

int main()
{
   // open the file
   fstream inputFile("C:\\In.txt",
      std::ios_base::out | std::ios_base::app);

   // this will result in different random numbers at
   //different run time
   srand((unsigned)time(0));

   // just add the new characters -> convert to char the ASCI
   for (unsigned long int i = 0; i < ADD_ITEM; ++i )
     {
         inputFile << (char) ((rand()%125)+3) ;

     }
// prepare the file for opening a different file
// or the same just with different flags
   inputFile.clear();
   inputFile.close();

// now open for read
         inputFile.open( "C:\\In.txt",
      std::ios_base::binary | std::ios_base::in);
      inputFile >> noskipws;

   string number; // this will hold the last input

   // allocate space for the buffer
   char* block = (char*) calloc(BUFFER_SIZE+1, sizeof(char));

   int size = 0;    // first just count the number of reads

   while ( inputFile.read( block, BUFFER_SIZE) ) // read

   {
       block[BUFFER_SIZE] = '\0'; // assure no false data at end
       number = block;           // save the block
       size++;                   // increase the read count

   }
   number.append( block, inputFile.gcount() );//what remains
                              //in the end add also
   if(size) //calculate the number of read characters
   size = (size-1)*(BUFFER_SIZE )+ number.size() ;
   else
      size = number.size();

   cout << size << endl;     // print the result
    inputFile.close(); //close the file


}

504049
Press any key to continue . . .




Here is the file size I ended up in the end after the completion of the program. As you
can see here the number of characters determinate, the size of the file on the disk but
this is OS dependent and if you build up your application on this, it will not be portable.

The problem may be absurd or you may find better solutions also (if you do so, please
post it on the blog!) but I think shows good where comes in handy the binary flag and
shows a good way to read into the memory large text files fast.

Again thank you for reading through my article, I hope you learned from it a lot and all
that remains to me is to invite you to post on the blog here or join the Devhardware
forums and express there your questions. Until next time: Live with Passion!