Tony Houghton
In C, standard I/O (input/output) operations are provided by the <stdio.h> library. In C++, this has been superseded by the streams library. Although the numerous complex classes and their interrelations make learning them daunting, their use can make program source easier to understand.
As with the functions in <stdio.h>, there are streams specialised for the console (display and keyboard), files and strings.
The use of streams is centred around the operators << and >>, which are overloaded (see Part 4) for streams to mean output and input respectively. In this context, the operators are known as inserters (output) and extractors (input), or more simply put to and get from. The result of each of these operators is the stream to which they were applied. So, as the operators have low precedence, and associate from left to right they can be chained. For example:
cout << "Hello " << "world\n";
is equivalent to
cout << "Hello "; cout << "world\n";
An important feature of streams is that they are buffered, so writing to an output stream will not necessarily cause anything to appear at the output (such as on screen) straight away. This makes use with files more efficient, but it can be a nuisance. Fortunately, it is possible to program streams to read and write straight through the buffer.
The full details of the streams libraries are too long for this article, which is intended as a thorough introduction to using the existing facilities provided by the libraries. The Acorn C/C++ manual contains a very good reference chapter for the streams library, and you should read it after this if you have that package and are interested in extending streams (e.g. writing one to use Wimp_ReportError).
When including C++ header files, note that you may get compiler warnings saying "Non-ANSI #include", because the latest version of Acorn C++ preprocesses in an ANSI-compatible mode. This is nothing to worry about.
Simple output
The main class for output functions is ostream, defined in <iostream.h>. It has a number of overloaded versions of << already defined for built-in types such as int, float and char* (strings), and other types can be added. Pointers other than char * are treated as unsigned integers. ostream can be used as the base class for classes that write to files, strings and the screen (or anywhere more advanced users care). By adding operations to ostream, you allow them to be used for all these outputs.
<iostream.h> provides a standard object derived from ostream for writing to the screen, called cout. Examples of its usage are:
cout << "Hello world\n"; cout<<"Here is a number: "<<4<<'\n';
Try them in a short program. The second example highlights that there are separate operators for outputting int and char, the former converts the number to a string (like %d in C's printf()) and the latter displays the char as a character.
Adding operators for user-defined types
Output operators for the built-in types are provided as members of ostream. A subset of ostream is:
class ostream //... { //... public: ostream &operator<<(int); // Returns reference to itself };
We should not consider adding operations for our own types to ostream this way, because we would have to alter the library, nor can we add them to a user-defined derived class because it won't be usable in other library classes derived directly from ostream. This leaves us with globally overloading operator<<. The class complex is a good example, its output operator might be:
ostream &operator<<(ostream &s, complex z) { return s << '(' << real(z) << ',' << imag(z) << ')'; }
which outputs a complex number as a comma-separated pair of numbers in brackets.
Simple input
The class istream is a close parallel of ostream used for input. Inputting values introduces the problem of what should be considered as the terminator or separator of values. For all types, a whitespace (defined as a space, tab or newline etc) is considered the terminator, for numbers anything that is not considered a numerical character is a terminator. Whitespace at the beginning of any value is skipped.
Therefore, although we have written "Hello world\n" as one string, if this was read back it would be as two strings, "Hello" and "world".
Stream states - ios
During input, it is possible that we will encounter errors, e.g. a string of letters being present when we were expecting a number. Therefore, before discussing input further, we need to look at aspects of the state of a stream. The state of a stream is handled by the base class ios. The simplest way to read the state of a class is with its member operators ! and void *. These are defined as (members of ios):
int operator!(); operator void *();
! returns non-zero if the stream is in a state of error and void * returns non-zero if there is no error (the pointer value should not actually be used). This allows you to write something like:
if (cin) // No error if (!cin) // There has been an error
cin is the standard input stream (from the keyboard), the complement of cout.
The state is represented internally by a set of flags manipulated by the following (public) members of ios:
enum io_state { goodbit, // These represent bits eofbit, // Actual values depend failbit, // on implementation badbit }; int rdstate(); void clear(int state=0);
The bits represented by io_state represent, in the order shown: no errors; no errors but end of file has been reached; failure, but stream is still usable; stream has become unusable.
rdstate returns a value made up from the io_state bits. To test for a particular condition, you should mask the value with bitwise and (&):
if (cin.rdstate() & ios::goodbit) // OK to continue
clear() is rather confusingly named - it actually sets the error state to its argument. It is called clear because the default with no argument is to set the state to good (clear all errors).
Members are provided to test for specific error states in a more elegant way (NZ means returns non-zero):
int good(); // NZ if no errors int eof(); // NZ if eofbit set int fail(); // NZ if failbit or // badbit set int bad(); // NZ if badbit set
Input of user defined types
This is similar to adding output operators, but we should be careful to deal with errors. To complement the output of complex we'll look at an input operator that can deal with the brackets and comma. Note that the second argument of an input operator must be a reference, or nothing will appear to happen:
istream &operator>>(istream &s, complex &z) { /* Failed input operations must leave their target variable unaltered so we read values into temporaries, and c must be initialised to anything other than '(' */ double re, im; char c=0; s >> c; if (c=='(') { s >> re >> c; if (c==',') { s >> im >> c; if (c==')') { z = complex(re, im); // In practice, this operator // would be a friend of // complex to allow it to set // z's members directly for // efficiency return s; } } } if (!s.fail()) s.clear(ios::failbit); return s; }
Formatting
The states of a stream handled by ios do not only concern errors, the other main set of states is concerned with formatting. Just as characters in the format string of a printf or scanf call can be used to change attributes of the output (or expected input), such as base and number of decimal places, flags in ios are used for streams. Unfortunately, there is insufficient space to cover the flags here; where possible, it is better to use manipulators (see below) anyway. The Acorn C++ manual has a very good reference chapter for streams; if you have Easy C++ or GCC with no other documentation, you may be lucky and find that the header file <iostream.h> is sufficiently commented. The enum group of flags for formatting usually begins with skipws.
The members of ios for accessing format flags are:
long flags(); // Returns value long flags(long b); // Sets value of flags to b and // returns old value of all flags long setf(long b); // Sets just the flag b and // returns old value of that flag long unsetf(long b); // As setf(long b) but clears b long setf(long b, long f); // Sets group of flags represented // by f to b, returns old value of // group
The flags fall into three main groups, so the last of the above members is used to set a group of flags at a time. The groups are:
f=basefield - base of numerical values (decimal, hex or octal)
f=adjustfield - alignment of numerical values
f=floatfield - floating point notation.
Two other important flags in the same enum, but not strictly concerned with formatting, are unitbuf and stdio. When unitbuf is set, the stream is flushed after every operation (but not necessarily for every character), this makes sure output appears on the screen etc straight away. If stdio is set, the standard streams are flushed after every operation.
Other members of ios
There are three more members of ios for controlling format by setting and returning various values. In each case, the argument is optional; where present, it sets the value as well as returning the old value:
int precision(int);
the number of significant digits to use for floating point types (the default is 6).
int width(int);
the minimum number of characters used to output a value; if a value needs more than the setting, it will overflow the width, and if it needs less, it will be padded with the fill character.
char fill(char);
the fill character used for padding out to width (default is a space).
As well as formatting, you can control the behaviour of a stream.
ostream *tie(ostream *); // Argument optional, // zero to untie
allows a stream to be 'tied' to an ostream, meaning that when an operation is performed on one of the streams, the other is flushed. By default, cin is tied to cerr to ensure that all the text written to the screen actually appears in sequence with text read from the keyboard.
static void sync_with_stdio();
causes all the standard streams (including cin and cerr) to be attached to their stdio counterparts and unit-buffered. This allows code using streams to be freely mixed with code using stdio with no problems of characters appearing or being consumed out of sequence.
Manipulators
Manipulators allow the state of a stream to be changed more conveniently by outputting or inputting predefined objects (actually, they are functions, but the implementation is beyond the scope of this article) to or from a stream.
The simpler manipulators are dec, hex, oct, endl (o), ends (o), flush (o), ws (i); where (o) means it applies only to ostream, and (i) means it applies only to istream. dec, hex and oct set the base of the next value; endl and ends set the end of a line or string with a '\n' or zero respectively, and flush the stream; flush flushes the stream; ws strips any further whitespace characters before the next value.
You can write:
cout << hex << 33 << endl;
to cause:
21
to be written on the screen (including a newline).
To read the same string back as a hex value into a variable number_in, you would write:
cin >> hex >> number_in;
More complicated manipulators taking arguments, implemented by function templates, are defined in <iomanip.h>. The predefined ones duplicate the members of ios previously described:
setprecision(int) - floating point precision
setfill(int) - fill character
setw(int) - width
setiosflags(long) - set format flags
resetiosflags(long) - clear format flags
So an alternative to writing:
cout.width(10); cout << PI;
is:
cout << setw(10) << PI;
Note that PI is not defined by default in C or C++.
Members of istream and ostream
So far, we have only looked at members of istream and ostream provided by their base classes and the inserters and extractors. The stream classes themselves provide a few members to make i/o easier.
iostream has some members to give more control over input:
istream &get(char &c);
extracts a single character and stores it in c.
istream &get(char *s, int n, char d='\n');
extracts up to n characters into the string s, stopping if it reaches the delimiter character d, which is left in the stream. n is also optional, allowing the length of the string to be ignored (considered to be an unsafe thing to do). The string is terminated.
istream &getline(char *s, int n, char d='\n');
As above, but the delimiter is also extracted and added to s, and there is no version without n.
istream &read(char *s, int n);
as get(char *, int, char); but with no delimiter.
istream &ignore(int n, int d=EOF);
as get(char *, int, char); but characters are ignored.
int peek();
returns the next character (or EOF, defined as -1) in the stream without extracting it.
istream &putback(char);
attempts to put back the last character read from the stream and change the internal pointer, so it is as if the character was never read. Do not attempt to use it for any other purpose.
Such fine control is not needed for output, so the only members of ostream complementing the above members of istream are:
ostream &put(char c);
puts a single character, provided only for symmetry.
ostream &write(char *s, int n);
writes n characters from s which can contain zeros.
Both istream and ostream provide members for reading and writing the position of the internal pointer. This is most meaningful when applied to file streams and is analogous to the fseek() and ftell() functions in <stdio.h>, but it can also be applied to other streams, strings in particular.
For ostream, the members are (with the p suffix standing for put):
ostream &seekp(streampos);
sets the position to the streampos argument; streampos is an integral type (Acorn C++ defines it as typedef long).
ostream &seekp(streamoff, seek_dir);
sets the position relative to a position defined by the seek_dir which is defined as a type member of ios:
enum seek_dir { beg, // beginning of file cur, // current position end // end of file }; streampos tellp();
returns the current position.
istream contains identical members, but the p suffix is replaced by g (for get), e.g. tellg() and ostream & results are replaced by istream &. The reason for distinguishing between input and output with p and g is because there is a class iostream derived from istream and ostream.
Stream buffers
The actual buffer to which a stream is attached depends on what sort of stream it is (e.g. file, string). Buffers are derived from the class streambuf. A streambuf must be attached to a stream when it is initialised by passing a streambuf * to its constructor. All the practical streams derived from the basic stream classes (such as those used by cin and cout) do this for you, and we need not concern ourselves with implementation details of streambuf here.
cin and cout are of little use to Wimp programmers, so we shall now look at some streams that are.
File streams
These are probably the type of stream you will use most in applications. The classes provided for file streams in <fstream.h> are ofstream (output), ifstream (input) and fstream (input and output). Much of the way they deal with files is common, so each member will only be described once for an imaginary class xstream where x can mean if, of or f.
The constructors of an xstream are:
xstream();
constructs a file stream without opening a file.
xstream(const char *name, int mode, int prot);
constructs the stream, attaching it to a file called name, using mode. prot is ignored by RISC OS. mode takes values from flags in enum open_mode, a member of ios. Possible values are:
in - input; default value for ifstream.
out - output; default value for ofstream. Implies trunc.
ate - (at end) once file is opened, its pointer is set to the end.
app - (append) as ate, but also implies out.
trunc - (truncate) causes any existing data to be discarded.
nocreate - open will fail (setting failbit) if file doesn't already exist.
noreplace - open will fail if file does already exist (only usable in conjunction with out).
There are other constructors, but they will not be covered here.
If you create an xstream without opening a file, it must subsequently be opened by the member:
void open(const char *, int, int); // Arguments as for constructor
The member:
void close();
closes any file attached to the stream. This is also done by the destructor, so you don't usually have to explicitly close files.
A minimal example program to copy one file to another with no error checking would be:
#include <fstream.h> int main(int argc, char *argv[]) { /* Copy file named by first argument to one named by second argument */ ifstream from(argv[1]); // Default open_mode is OK ofstream to(argv[2]); char ch; while (from.get(ch)) to.put(ch); // get will fail after eof }
String streams
Streams using strings (cf printf() and sscanf()) can be found in <strstream.h>. The classes are istrstream for input, ostrstream for output and strstream for both. The constructors are:
istrstream(char *s);
Characters will be extracted from the string s which is assumed to be zero-terminated; the eof condition is set when the terminator, which is not considered part of the sequence, is reached.
istrstream(char *s, int len);
As above, but the string has a predefined length of len.
ostrstream(char *s,int len,int mode); strstream(char *s,int len,int mode);
Characters will be inserted into the array of len bytes. The mode is as for file streams; if ios::app or ios::ate are set, the string should be null-terminated within len and inserting begins at that point.
ostrstream(); strstream();
The string is created by ostrstream or strstream and dynamically reallocated as necessary.
Where the string is dynamically allocated, we need access to it once operations are complete. ostrstream and strstream each have a member:
char *str();
which returns the string created. Once this is called, the string is no longer attached to the stream, so the stream is no longer usable. Also, it is the caller's responsibility to delete[] the string when finished with; before the call to str(), the stream's destructor does this.
If you need to know the length of a non-terminated string written to by an ostream, you can call:
int pcount();
to find the number of bytes written.
Stdio streams
A stdiostream, defined in <stdiostream.h> (truncated by RISC OS to <stdiostrea.h>), can be created for use on files controlled by <stdio.h>, but it should only be used to allow mixing of C and C++ code. A stdiostream is unit-buffered and has the constructor:
stdiostream(FILE *fp);
where fp is opened and closed by C code using <stdio.h>.
Exercise (advanced)
If you have adequate documentation, try finding out how to derive a buffer from streambuf and use it to make a stream that outputs via Wimp_ReportError at every flush. Provide manipulators (be careful when choosing global names) for setting the error number, error flags and the error box title. Also provide an inserter for _kernel_oserror * (and/or os_error *, depending on the libraries you use); it can bypass the actual insertion process, and just use the stream's flags and title values, and do nothing for null errors (make the operator, or at least an initial part that checks for zero, inline for efficiency).