Department of Engineering

IT Services

C++ Input/Output

Concepts

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.

Classes

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.

Predefined streams

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.

Input

Initialisation

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

  • One can check on a routine's success by subsequently calling the stream's good() or fail() function. After the call to open in this code add
     if(input_stream.fail()) {
            cout <<"cannot open file" << endl;
     }
    
  • After the call to open in this code add
     if(!fin) {
            cout <lt;"cannot open file" << endl;
     }
    
    Note that the "!" operator does something special here. It's been redefined so that it returns true if either failbit or badbit is set, and false otherwise, which is what the fail member function does.

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.

Routines

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.

Manipulators

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

Output

Initialisation

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.

Routines

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)

Manipulators and Formatting

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; }

Booleans

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;

Output of User-defined types

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; }

Exceptions

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; } }

Strings

std::string and character arrays

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

  • As well as the getline routine used above in the Input section there's another getline routine (a member function of istream) which does a similar thing but only accepts character arrays. i.e. in the following, s can't be a string.
    ifstream fin; fin.open("/etc/passwd"); while(fin.getline(fin,s)) ...
  • istream's open routine needs a character array. If you have a C++ string s then s.c_str() returns a character array.

Writing to and reading from Strings

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; }

locales

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; }

Wide characters

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; }

See also

cplusplus.com's iostream library reference describes the functions available, with sections on iostream, fstream, stringstream, etc.