C++/How to read a text file in c which contains variable
Expert: Ralph McArdell - 6/15/2006
QuestionI am trying to make a code that can read a text file . Because this file has some float numbers which is the input of that programe. and i don't want to put those numbers in the main programe but i want to make simple code that it can read a text file where are those number and can use as an input variable.Please help me how to write a code for that.
AnswerThere are many ways to do this but the basic idea is to read the values from the file into one or more variables. You cannot directly use the data in the file as a variable in the program. I also have no idea how much C++ you know nor what dialect you might be using, so I shall assume ISO/ANSI standard C++.
As the data is stored in a text file the most natural type for the data is as a string. For it to be anything else requires that it be converted.
Luckily for us the C++ IOStreams library provides such conversions through the >> operators, for example:
input_stream >> float_var;
Of course there is a problem if the data read from the file is not convertible to the type of the provided object. In this case the stream's failbit is set and can be tested using the stream's fail() operation:
if ( input_stream.fail() )
{
// handle formatting error
}
See also the badbit, eofbit and goodbit and the stream bad() good() and eof() operations as well as the stream void* and ! operators, among other stream state support facilities.
Note that for brevity I may not show error checking in the examples below and that not all the examples have been compiled so may contain typos and errors - for which I apologise now.
So you need a stream object associated with your file. The appropriate type would be a std::ifstream std:: because it is part of the C++ standard library and ifstream because we want to input from a file (i and f prefixes in the ifstream name). To use these you should include <fstream>. That stream needs to be opened with the file name. This can be done either in the constructor or by using the open() operation. Likewise the stream is closed when the stream object is destroyed or when the close() operation is called:
std::ifstream input_stream( "floats.dat" );
Of course we should check to see that the stream is open:
if ( input_stream.is_open() )
{
// proceed with processing
}
else
{
// handle unable to open file error
}
The obvious way to extract data would be to pass the float variable to a function that reads from the stream, however this means the stream is accessible from the function as well:
void GetNextFloat( std::ifstream & in, float & value )
{
in >> value;
}
Or if you prefer:
float GetNextFloat( std::ifstream & in )
{
float value(0.0);
in >> value;
return value;
}
Note the latter example while maybe nicer to use is less efficient as it involves at least one additional float variable.
The problem here is that we need to pass around the stream all the time. These are ways around this. We could make the stream a static variable, opened the first time GetNextFloat is called:
void GetNextFloat( float & value )
{
static std::ifstream in;
if ( !in.is_open() )
{
in.open( "floats.dat" );
}
in >> value;
}
But this leaves us with the problem of how to close the stream. Maybe only when we read the end, but this would mean we would always have to read the whole file plus make a call that failed with eof (end of file) so we can detect this condition and close it. A solution would be to make the stream variable available across more than one function by making it global:
static std::ifstream gIn;
void GetNextFloat( float & value )
{
if ( !gIn.is_open() )
{
gIn.open( "floats.dat" );
}
gIn >> value;
}
Then any function that gIn can be seen from can manipulate it - e.g. close it. However local statics are bad enough and global variables are _never_ a good idea in the long run. Both suffer from the fact that there can only ever be one file providing values at any one time. This may be ok today but will this still be true say a year down the line?
So what else can we do? Well we can wrap the functionality in a class, here is one way to do it:
class FloatFileParameters
{
public:
explicit FloatFileParameters( char const * filename )
: iStrm( filename )
{
}
~FloatFileParameters()
{
iStrm.close();
}
float GetValue()
{
float value(0.0);
iStrm >> value;
return value;
}
private:
std::ifstream iStrm;
};
The above is very basic but show the idea. It can be used as below:
void SomeFunction()
{
FloatFileParameters floatParams( "floats.dat" );
float param1( floatParams.GetValue() );
float param2( floatParams.GetValue() );
float param3( floatParams.GetValue() );
} // floatParams goes out of scope, is destroyed and
//destructor closes file stream.
The interface to FloatFileParameters can be changed to take advantage C++ niceties. For example a dereference operator:
class FloatFileParameters
{
public:
// ...
float operator*()
{
float value(0.0);
iStrm >> value;
return value;
}
// ...
};
We can then write:
void SomeFunction()
{
FloatFileParameters floatParams( "floats.dat" );
float param1( *floatParams );
float param2( *floatParams );
float param3( *floatParams );
}
This makes floatParams act a little like it was a pointer to the values. Unlike a pointer each time we dereference the 'pointer' we effectively also increment it onto the next value.
This is very similar to C++ standard library iterators, except they work more like traditional pointers in that you have to explicitly increment them to move onto the next value.
Usefully, C++ standard library includes stream iterators for input and output. They require a pre-existing stream to be passed to them for use, and you can detect if one is in the end of file state by comparing it with default constructed instance. Like a lot of the C++ library classes they are in fact class templates requiring the type of items read in (or written out) as the template parameter. The previous example would look like as follows using std::istream_iterator:
#include <fstream>
#include <iterator>
void SomeFunction()
{
std::ifstream in( "floats.dat" );
std::istream_iterator<float> floatReader( in );
std::istream_iterator<float> floatReaderEOF;
float param1( *floatReader );
++floatReader;
float param2( *floatReader );
++floatReader;
float param3( *floatReader );
if (floatReader == floatReaderEOF )
{
// Handle end of file...
}
}
I added an example test for end of file just to show the form. Of course it would be more useful after each increment operation. If you like a terser programming style then you can increment after dereferencing:
float param1( *floatReader++ );
float param2( *floatReader++ );
float param3( *floatReader );
Or even before dereferencing, which is probably more efficient:
float param1( *floatReader );
float param2( *++floatReader );
float param3( *++floatReader );
So it you can live with the library stream iterator style there is no need to write anything yourself. You can just use them in you main function.
Hope this has been useful.