Previous | Table of Contents | Next |
It is easy to make all the names from the standard library available by saying
using namespace std;
after including the relevant header files. Our example program could therefore be corrected either by saying
#include <iostream> main() { std::cout << Hello, world! << std::endl; }
or
#include <iostream> using namespace std; main() { cout << Hello, world! << endl; }
In this chapter, we have consistently assumed that the programmer did the latter of these, and we shall continue to do so throughout this section. Moreover, we will not clutter up the exposition by naming the appropriate headers to use in each example, because the presentation is enough of an overview that we expect programmers to have to consult a more detailed description of the library in order to use it anyway. This section aims to introduce enough of the main ideas behind the library to make it possible to use a detailed library description without becoming hopelessly lost.
C++ actually has two I/O libraries: It inherited one of them from C, and acquired a new one along the way. Both libraries are part of the C++ standard, but the native C++ library is generally recommended over the C library.
The main reason to write a new library is extensibility. The most commonly used function in the C library is printf, which relies on a language facility that is used for almost no other purpose: the ability to define functions that take variable numbers of arguments. For example, using the C library, we might say
printf(The square root of 2 is %g\n, sqrt(2.0));
The first argument to printf is a string literal (or, more precisely, a pointer to the initial character of a null-terminated array of characters) that describes what printf is to print. Characters other than % stand for themselves; a % introduces a format specifier that corresponds to one or more of the succeeding arguments to printf and also says how those arguments are to affect the output. So, for example, %g means take a double value from the argument list and print it in the most general form for floating-point values.
The form that printf uses is concise and easy to understand. However, it has three significant disadvantages:
The native C++ I/O library uses overloading to deal with all these problems. The library class ostream has overloaded operator<< members for all the fundamental types, and the istream class has similarly overloaded operator>> members for input. Moreover, additional library classes, such as strings and complex numbers, define their own overloaded operator<< and operator>> functions as appropriate. Operator overloading therefore makes it possible for authors of other library classes to define I/O for those classes without interfering with each other.
The I/O library is so large that few people will ever use more than a small part of it. The most important classes are certainly istream and ostream, which represent input and output files respectively. From each of those classes is then derived a family of classes that represent particular kinds of files. For example, class ifstream is derived from class istream to represent an input file that is identified by its name. If, for example, we have a file named foo, we can prepare to read that file by creating an appropriate ifstream object:
ifstream ourfile(foo);
After that, saying
ourfile >> x;
will read a value from the file foo into x; the exact way in which it does so will depend on the type of x. The destructor for class ifstream will automatically release any resources that were acquired when we created ourfile, so it is generally not necessary to close files explicitly.
At the beginning of this article, we saw two manipulators called setw and endl. There are many more manipulators, all of which are based on a common idea. A manipulator is nothing more than a function that can act on an I/O stream. So, for example, endl is really a function defined along the following lines:
ostream& endl(ostream& o) { // start a new line on output stream o // flush the output stream o return o; }
The reason it is possible to say
cout << endl
is that among the definitions of overloaded << in class ostream is one that accepts a (pointer to a) function. It prints that function by calling the function with the stream itself as its argument. In effect, we have
class ostream { public: // ... ostream& operator<<(ostream& func(ostream&)) { return func(*this); } // ... };
The effect of this is that saying
cout << endl
is equivalent to saying
endl(cout)
Previous | Table of Contents | Next |