 |
Department of Engineering |
 |
 |
C++ Input/Output
Users want a high degree of control over
formatting, but also want high performance and ease of use.
Before we see how C++ attempts to reconsile these needs, let's consider
I/O in more detail in an attempt to justify C++'s solution.
- streams -
I/O uses the concept of streams - a flow of characters. These
characters may be 1 byte or 2 byte ('wide'), the latter necessary for
languages with many characters. streams may flow into or out of files.
They can also flow into and out of strings. C++ tries to offer the
same set of commands whatever the nature of the source and destination.
- buffers -
It would be inefficient to update a file on disc each time a character
was added. The character is written into a buffer, and when
enough characters are in the buffer to make updating worthwhile, the
buffer is flushed and the file on disc updated. C++ offers ways
to control the behaviour of buffers, but often the default is ok.
- state - Each stream has state information indicating whether
an error has occured, etc.
- locale -
The natural language required by the user has an influence on output -
should a bool value be printed out as true or
vero? Such preferences are controlled by the locale,
which is usually set up appropriately by default. The locales section has a example of using this feature.
True to form, C++ represents each distinct concept with a class, and
tries to 'factor out' components that are needed in more than one class.
Usually users don't have to worry too much about this (skip to
the Input section now if you want), but some
awareness of this structure will help you understand the online
documentation and help you decide which include files to use.
|
|
The class hierarchy (inheritance tree) is as follows
ios_base
ios
/\
/ \
/ \
istream ostream
/ | \ / | \
/ | | | \
/ / | \ \
/ / | \ \
/ / | \ \
istring ifstream iostream ofstream ostring
stream /\ stream
/ \
fstream stringstream
so if the user used a ifstream to get input from a file,
the istream (input) features, state information (in
ios_base) and format/buffer control (via ios)
would be available.
The following streams are predefined:
- cin - the standard source of input (often the keyboard)
- cout - the standard destination for output (often the terminal window)
- cerr - the standard destination for error messages (often the terminal window). Output through this
stream is unit-buffered, which means that characters are
flushed after each block of characters is sent.
- clog - like cerr, but its output is buffered.
To input data you first need to create a stream and associate
it with a file or string so that the stream knows where to get
the characters from. Unless you're using cin (the standard
input stream - usually the keyboard) you'll need to
use the open command, or a constructor. You can provide extra arguments,
but usually you just need to provide a filename.
As a simple example consider the following, which reads then displays 3 numbers
from a file.
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
int i;
ifstream fin;
fin.open("test"); // test contains 3 ints
for (int j=0;j<3;j++)
{
fin >> i;
cout << i << endl;
}
fin.close();
}
This program lacks error-checking -
if the file doesn't exist, or if it contains less than 3 integers then the
program won't work well. Even if the file's ok, it's rather dangerous to
ever read from it before checking first. For instance, in the program
above if you put the
fin.close(); line inside the loop (at the end), the stream will be closed
after
the first number is read, but the program will still happily print erroneous
values out for the last two numbers and won't complain.
There are various way to check on the success of a call. Here are a few -
see the Exceptions section for an alternative
- If fin is 0 after fin.open("test") then the file
couldn't be opened.
- One can check on a routine's success by subsequently calling the
stream's good() function.
To read a line of text (including spaces) into a string, use getline()
#include <string>
#include <fstream>
#include <iostream>
using namespace std;
int main()
{
string s;
ifstream fin;
fin.open("/etc/passwd");
while(getline(fin,s))
cout << s << endl;
}
The file will be opened at its start. If you don't want to access the
contents sequentially, you can use seekg() to set the
file-position indicator, and tellg() to tell you the current
position. The following program prints the first and last characters
of the file given to it as an argument. Note the use of ios::beg
(beg is defined in ios) to show that you're offsetting
from the beginning of the file.
#include <string>
#include <fstream>
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
string fileName;
bool flag=false;
if (argc==2)
fileName=argv[1];
else {
cerr << "Provide a single filename next time" << endl;
return 1;
}
ifstream fin;
fin.open(fileName.c_str());
while(fin.good() == false)
{
cout << "Unable to open file. Enter a new name: ";
cin >> fileName;
fin.open(fileName.c_str());
}
istream ist(fin.rdbuf());
ist.seekg(0,ios::beg);
streampos pos=ist.tellg();
cout << "Initial Position=" << pos << endl;
char c;
ist.get(c);
cout << "Char=" << c<< endl;
ist.seekg(-1,ios::end);
pos=ist.tellg();
cout << "Final Position=" << pos << endl;
ist.get(c);
cout << "Char=" << c <<endl;
return 0;
}
As usual, care needs to be taken when using these routines in case they fail. If get can't read a character it sets a failure flag, which causes the member function fail() to subsequently return true. When the file is in
this state calling seekg has no effect and
tellg returns -1 indicating failure.
As well as being able to use ">>" as usual on an open file
there are also various forms of the get command (to read raw data)
as well as getline to read a line of text with spaces. You need
to read the documentation carefully. In particular, input routines differ
in how they deal with a final new-line character. '>>' will stop
reading input when it reaches a final new-line character, but it won't
remove the new-line character from the stream. This might confuse the
next input routine, which on reading the initial new-line character
might assume that it denotes an empty line (though '>>' skips any
leading newlines). If you try the following, you won't have a chance to enter
a character array.
#include <iostream>
using namespace std;
int main() {
char s[100];
char c;
cout << "Enter a character: ";
cin >> c;
cout << "Enter a string: ";
cin.getline(s,25);
}
One way to cure this is by adding
cin.ignore(INT_MAX, '\n' );
before trying to read the string. This removes the newline character.
Note also that text files on different types of operating system may have
different ways of indicating the end of a text line. See our End-of-line characters in text files page.
Other member functions include
- ignore(n,d) - throws away up to n characters. Stops if it
reaches the character specified by 'd'.
- gcount() - returns the number of characters extracted by the last
unformatted input function.
- peek() - returns the next character without extracting it
- putback(c) - attempts to put back the just-read character 'c'
- clear() - clears the status flags. You'll need to use this if
you want to perform some operation (e.g. a seekg command) after
having reached the end of the file.
And remember to call the close() member function when you've
finished with a file.
When, during output, you use "<< endl" you are using a
manipulator. There are a few input manipulators too
- cin >> noskipws; (or cin >> ws; in older versions) - don't skip white space
- cin >> hex; - base 16
- cin >> dec; - base 10
- cin >> oct; - base 8
To output data you first need to create a stream and associate
it with a file or string so that the stream knows where to deliver
the characters. You can use a constructor or the open
member function to open a file for writing.
Once the stream is in operation you can use "<<" as usual.
So
...
ofstream fout;
fout.open("MyFilename");
fout << "test";
should work.
Other member functions include
- write(const char* ptr, int n); (for raw data)
- seekp(off,dir) (repositions the 'put' pointer)
- tellp() (returns the 'put' pointer value)
This requires C++ knowing how to convert values of various types
into characters. C++ can deal with the standard types automatically,
but even with these the user may wish to format the output.
Formatting is controlled by flags in ios_base. These can be
set using the member functions below or by using manipulators
- fill(c) - pad fields with the 'c' character (usually the space character)
- width(i) - sets the field width to i
- adjustfield - a flag to set alignment in fields: left,
right or
internal
- precision(i) - sets the number of significant digits displayed
in floating point numbers
- floatfield - a flag to set the style for floating point numbers:
scientific (exponential notation) or
fixed.
The setf function can be called to set flags, and the following
manipulators are available
- cout << endl; - newline, flushing the buffer (i.e. sending
the outstanding output to its destination). Using cout << "\n"; produces a newline without flushing.
- cout << ends; - newline (when writing to a string)
- cout << flush; - flush buffer (i.e. send
the outstanding output to its destination)
- cout << hex; - base 16
- cout << dec; - base 10
- cout << oct; - base 8
- cout << setprecision(5); - set floating point format
Here's a short example
#include <iostream>
#include <iomanip> // to use the setprecision manipulator
using namespace std;
int main()
{
cout << 1331 <<endl;
cout << "In hex " << hex<< 1331 <<endl;
cout << 1331.123456 <<endl;
cout.setf(ios::scientific,ios::floatfield);
cout <<1331.123456 <<endl;
cout << setprecision(3) << 1331.123456 <<endl;
cout << dec << 1331 <<endl;
cout.fill('X');
cout.width(8);
cout << 1331 <<endl;
cout.setf(ios::left,ios::adjustfield);
cout.width(8);
cout << 1331 <<endl;
return 0;
}
You can print out bools as numerical values or as words.
The following should print out "1" then "true"
(translated into your chosen language).
cout << true << endl;
cout << boolalpha;
cout << true << endl;
You can output user-defined types directly using cout, but you need to
write an extra routine first. Here's an example
#include <iostream>
#include <string>
using namespace std;
class test {
public:
int age;
string name;
};
ostream& operator<<(ostream &os, const test &t) {
return os << t.name << " is " << t.age << " years old";
}
int main() {
test t;
t.age = 11;
t.name ="Jan";
cout << t << endl;
}
If you don't know what exceptions are, just skip to the
next section - you can survive without them for the moment.
By default, errors involving streams don't generate exceptions. Exceptions can
be enabled individually. Earlier on, good()
was used to check on success. Here's another way to check whether file-opening succeeds.
#include <iostream>
#include <fstream>
#include <exception>
using namespace std;
int main() {
try {
fstream fin;
fin.exceptions( fstream::failbit );
fin.open("test");
}
catch (std::exception &e) {
cout << "Exception caught: " << e.what() << endl;
}
}
Wherever possible, C++ strings (rather than character arrays) have been used
here. In most of the situations above, character arrays can be used instead
of strings. Points worth noting are
Once you can do I/O to and from files, I/O to and from strings is very similar, except that you
don't need an "open()" call. The following example writes (i.e. converts) an integer into a string.
#include <sstream>
#include <string>
#include <iostream>
using namespace std;
int main()
{
stringstream s;
string t;
int x = 10;
s << x;
s >> t;
cout << "t=" << t << endl;
return 0;
}
You probably won't need to use locales explicitly. The list of available locales is system-dependent - on Unix, typing locale -a should list them. Here's
an example of their use, showing how output is affected.
#include <iostream>
#include <locale>
using namespace std;
float f=1234.567;
int main()
{
cout.imbue(locale("uk_UA"));
cout << "Ukranian " << f << endl;
cout.imbue(locale("en_GB"));
cout << "UK English " << f << endl;
cout.imbue(locale("it_IT"));
cout << "Italian " << f << endl;
return 0;
}
In situations where each character requires 2 bytes, you can use
wide characters.
#include <iostream>
using namespace std;
int main()
{
wstring str(L"hello");
wcout << str << endl;
}
cplusplus.com's iostream library
reference describes the functions available, with sections on
iostream,
fstream,
stringstream, etc.