Code Capsules

C++ Streams

Chuck Allison


Chuck Allison is a regular columnist with CUJ and a software architect for the Family History Department of the Church of Jesus Christ of Latter Day Saints Church Headquarters in Salt Lake City. He has a B.S. and M.S. in mathematics, has been programming since 1975, and has been teaching and developing in C since 1984. His current interest is object-oriented technology and education. He is a member of X3J16, the ANSI C ++ Standards Committee. Chuck can be reached on the Internet atallison@decus.org, or at (801)240-4510.

Like C, C++ does not provide language facilities for input or output. They are, instead, part of the standard library. Although you can use C's stdio functions for input and output in C++, the stream classes are an efficient way to read and write data in C++. The stream classes of C++ offer three advantages to C's stdio functions.

First, streams are type-safe. The printf/scanf family of functions depends solely on a format string that the compiler cannot validate. Since the type of an object in C++ determines which action follows, you just can't do the wrong thing without hearing about it at compile time (unless you try really hard).

Second, streams provide a uniform interface for all input and output operations. Whether the source or destination is a file, a string, or a standard device, the operations are identical ("A stream is a stream is a stream...").

Finally, streams are extensible — and I don't just mean that you can derive new stream classes. Almost always, just overloading standard stream operators is all you need.

Standard Streams

There are four pre-defined streams: cin (standard input), cout (standard output), cerr (standard error), and clog (standard error). All but cerr are fully-buffered streams. Like stderr, cerr behaves as if it is unbuffered, but it is actually unit-buffered, meaning that it flushes its buffer automatically after processing each object, not after each byte. For example, with unit-buffering, the statement

cerr << "hello";
buffers the five characters and then flushes the buffer. An un- buffered approach sends a character at a time all the way through to its destination.

Simple I/0

The following program copies standard input to standard output:

// copy1.cpp: Copy standard
input to standard output
#include <iostream.h>

main()
{
    char c;

    while (cin.get (c))
        cout.put(c);

    return 0;
}
get, called an extractor, reads a single character or a string from a stream, depending on how it's called. (In this case, get reads a single character from the cin stream.) put, called an inserter, sends the character, or string, to an object. (In this case, put sends a character to the cout object.)

The get extractor stores the next byte from a stream into its char parameter. Like most stream member functions, get returns the stream itself. When a stream appears in a boolean context (like in the while loop above) it tests true if the data transferred successfully, and false if there was an error, such as an attempt to read beyond end-of-file. Although such simple boolean tests suffice most of the time, you can query the state of a stream anytime with the following member functions:

  • bad — severe error (stream is corrupted)
  • fail — conversion error (bad data but stream is OK)
  • eof — end-of-file
  • good — none of the above
  • The program in Listing 1 copies a text file line-by-line. The getline extractor reads up to BUFSIZ-1 characters into s, stopping if it finds a newline character, and appends a null byte. (It discards the newline.) Output streams use the left shift operator as an inserter. Any object, built-in or user-defined, can be part of a chain of insertions into a stream. (However, you must overload << yourself for your own classes.)

    Listing 2 contains a program that illustrates the extraction operator >>. Since in C you normally use stderr for prompts (because it is unbuffered), you might be tempted to use cerr in C++:

    cerr << "Please enter an integer: ";
    cin >> i;
    However, this is not necessary in C++ because cout is tied to cin. An output stream tied to an input stream is automatically flushed when input is requested. If necessary, you can use the flush member function to force a flush.

    Addresses print in an implementation-defined format (usually hexadecimal). Character arrays are, of course, an exception — the string value is printed, not the address. To print the address of a string, cast it to a void *:

    cout << (void *) s << '\n'; // prints address
    The >> extraction operator skips whitespace by default. The program in Listing 3 uses this feature to count the words in a text file. Extracting into a character string behaves like the %s edit descriptor in scanf. It is possible to turn off the skipping of whitespace when reading characters (see Listing 4) .

    Beyond Simple I/O

    Formatting

    ios::skipws in Listing 4 is an example of a format flag. Format flags are single-bit values that you can set with the member function setf, and reset with unset. (See Table 1 for a description.)

    The program in Listing 5 illustrates numeric formatting. The member function precision dictates the number of decimal places to display for floating-point values. Unless the ios::show-point flag is set, however, trailing zeroes will not appear. ios::showpos prints positive numbers with a leading plus sign. ios::uppercase displays the x in hexadecimal values and the e in exponential in upper case.

    Some formatting options can take on a range of values. For example, ios::basefield, which determines the numeric base for displaying integers, can be set to decimal, octal, or hexadecimal. (See Table 2 for a description of the three format fields available.) Since these are bit fields and not single bits, you set them with a two-parameter version of setf. For example, the program in Listing 6 changes to octal mode with the statement

    cout.setf(ios::oct,ios::basefield);
    With the flag ios::showbase set, octals print with a leading 0 and hexadecimals with a leading 0x (or 0X if ios::uppercase is also set).

    Manipulators

    endl, of a manipulator, inserts a newline character into the output stream and then flushes the stream. Instead of inserting an object into the stream, a manipulator changes a stream state. Table 3 contains built-in manipulators declared in iostream.h. The program in Listing 7, although functionally equivalent to the one in Listing 6, uses manipulators instead of explicit calls to setf. Manipulators often allow for more streamlined code.

    Other manipulators take parameters (see Table 4) . The program in Listing 8 uses the setw(n) manipulator to set the output width directly in the insertion sequence, so you don't need a separate call to width. The special field ios::width is reset to 0 immediately after every insertion. When ios::width is 0, values print with the minimum the number of characters necessary. As usual, numbers are not truncated, even if you don't allow enough space for them.

    You could replace the statement

    cout.fill('#');
    with the in-sequence manipulator

    ... << setfill('#') << ...
    but it seems cumbersome to do so in this case.

    Extractors usually ignore width settings. An exception is string input. You should set the width field to the size of a character array before extracting into it in order to avoid overflow. When processing the input line

    nowisthetimeforall
    the program in Listing 9 produces

    nowisthet,im,eforall
    Remember that extractors use whitespace as a delimiter, so if the input is

    now is the time for all
    then the output is
    now, is, the
    You can create your own manipulators by simply defining how a function takes a stream reference parameter and returns that same reference. For example, here is a manipulator that rings the bell at the console when you insert it into any output stream:

    // A manipulator that beeps
    #include <iostream.h>
    
    ostream& beep(ostream& os)
    {
        os << (char) 7; // ASCII BEL
        return os;
    }
    To use this, just insert it:

    cout << ... << beep << ...

    Beyond the Standard Streams

    String Streams

    To read from and write to strings in memory (like sscanf and sprintf do), use the string stream classes declared in strstream.h. The program in Listing 10 uses an anonymous output string stream to write to the string s. The line

    ostrstream(s,sizeof s) << "You entered " << i << ends;
    is a safe alternative to the C statement

    sprintf(s,"You entered %d",i);
    If you want the stream to persist beyond the statement, declare an object:

    ostrstream os(s,sizeof s);
    os << "You entered " << i << ends;
    Listing 11 contains a program that counts both the number of lines and the number of words in a text file. It reads a line at a time and then attaches a string stream to the line to extract each word. NOTE: the current release (v3.1) of the Borland C++ compiler requires a slight change in the while loop:

    while (line >> word && word[0])
        ++wc;
    (For some reason the string stream extractor doesn't set the state properly).

    The getline function normally extracts characters up to a newline. You can use another character as a delimiter by sup- plying the optional third argument. For example, the statement

    sstr.getline(s,sizeof s,',');
    reads characters into s up to the next occurrence of a comma, then discards the comma. This is useful for reading database files with delimited fields. The program in Listing 12 reads a comma-delimited file with fields representing name, address, city, state, and zip code. For example, with the following input:

    Bill CIinton, 1600 Penn. Ave.,Washington,DC,21234
    Ross Perot,1234 Feedback Dr.,Big Ears,TX,5678g
    Annymous,, Nowhere,, 00000
    it produces this output:

    Bill Clinton|1600 Penn. Ave.|Washington|DC|21234
    Ross Perot|1234 Feedback Dr.|Big Ears|TX|5678g
    Anonymous||Nowhere||00000

    File Streams

    The program in Listing 13 copies one file to another by explicitly opening file streams. You must enter both file names on the command line. The definition

    ifstream inf (argv [1] )

    attempts to open the file named in the first command-line argument and connects the input stream inf to it. If all went well, inf will test true as a boolean expression (analogously for outf).Both files close automatically when the file streams are destroyed as they go out of scope.

    Listing 14 contains a more flexible copy utility. It allows you to either name the files on the command line:

    copy fi1el fi1e2
    or to use redirection:

    copy <filel >file2
    or to leave one or both files connected to a standard device:

    copy file1 {displays file1 on the console)
    If you supply a file name it opens the file, otherwise it uses standard input and/or standard output. In order to attach a file stream to an open file you need a file handle, such as ileno returns. (See last month's "Code Capsule" for information on fileno and other POSIX functions). When a file stream goes out of scope, its destructor closes the associated file, unless attach made the connection (otherwise standard I/O would shut down!).

    The program in Listing 15 creates a file of fixed-length records, and then reads back and displays selected records. An fstream object allows simultaneous input and output on the same file. The flag ios::bin enables binary mode, which is necessary for accurate file positioning on non-UNIX systems. (Some compilers use ios::binary or ios: :notranslate, but the current working paper for standard C++ specifies ios::bin). The member functions read and write perform binary input and output, similar to their C counterparts read and write (except that the former process only one record at a time). seekg moves the file-read pointer to a specified byte position in the file. Its second argument controls positioning in the usual way:

  • ios::beg — forward from the beginning of the file
  • ios::cur — relative to the current position
  • ios::end — backward from the end of the file
  • Note: Classes that specifically support simultaneous input and output on a stream (namely, strearn, strstream, and iostream) are not part of the proposed standard for C++. The committee decided to simplify the standard library by eliminating the use of multiple inheritance. (Multiple inheritance is one of those necessary evils that you should only use when absolutely.) To open a file for both input and output in a future conforming implementation, you will have to replace

    int mode = ios::bin | ios::trunc | ios::out | ios::in;
    Fstream f("recs.dat',mode);
    with

    int mode = ios::bin | ios::trunc | ios::out | ios::in;
    ofstream f("recs.dat", mode);
    ifstream g(f.rdbuf(),ios: :bin);
    which causes streams and g to share the same internal buffer. (See Table 5 for descriptions of the file open modes.)

    Streams and User-Defined Objects

    To read or write a user-defined object usually reduces to processing built-in types which comprise the object (or its components, etc.). For example, suppose you wanted to define an inserter for a complex number class:

    class complex
    {
        double real, imag;
    public:
        friend ostream& operator<<(ostream&, const complex&);
        // Details omitted
    };
    To display a complex number in coordinate form (e.g., (2,7.5)), you simply use the built-in inserter on the components:

    ostream& operator<<(ostream& os, const complex& c)
    {
        os << '{' << c.real << ',' << c.imag << ')';
        return os;
    }
    The semantics of operator<< require returning the stream to allow chaining multiple insertions in a single statement. (For more detail on overloading inserters and extractors, see Dan Saks' column "Stepping Up to C++" in the May 1992 and July 1992 issues of CUJ.)

    There is more machinery to streams than what I have presented here. (If you're interested in what's under the hood, look up the streambu family of classes in your compiler's documention). I have illustrated the high-level features that should suffice for most non-systems applications. If things appear slightly more involved that what you're used to in C, that merely reflects the careful design that makes streams safer and more robust than the stdio functions.

    Table 1 Format flags

    Flag       Meaning                        Default
    -------------------------------------------------
    showbase   shows octal or hex prefix      off
    showpos    shows plus sign when positive  off
    showpoint  shows trailing zero decimals   off
    uppercase  OX for hex, E for scientific   off
    unitbuf    enables unit buffering         off
    skipws     > > skips whitespace           on

    Table 2 Format fields

    Field        Values                 Default
    -------------------------------------------
    adjustfield  left, right, internal  right
    basefield    dec, oct, hex          dec
    floatfield   fixed, scientific      fixed

    Table 3 Simple manipulators(iostream.h)

    Manipulator  Meaning
    -----------------------------------------------
    dec          Change numeric base to decimal
    oct          Change numeric base to octal
    hex          Change numeric base to hexadecimal
    ws           Skip over whitespace in input
    endl         Insert a newline and flush
    ends         Insert a `\0'
    flush        Flush the stream

    Table 4 Parameterized manipulators (iomanip.h)

    Manipulator       Meaning
    -------------------------------------------------------
    resetiosflags(n)  Reset all the flags that are set in n
    setios flags (n)  Set all the flags that are set in n
    setbase(n)        Same as setf (n, ios::basefield)
    setfill (c)       Same as fill (c)
    setprecision(n)   Same as precision(n)
    setw(n)           Same as width (n)

    Table 5 File open modes

    Mode    Meaning (stdio equivalent)
    ---------------------------------------------------
    in         Open for input ("r")
    out        Open for output ("w")
    ate        Position at end of file
    bin        Binary mode ("b")
    trunc      Discard existing contents
    app        Position for output at end of file ("a")
    nocreate   Fail if doesn't already exist
    noreplace  Fail if already exists
    (The last three are not part of draft standard C++)

    Listing 1 Copies standard input lines to standard output

    // copy2.cpp: Copy input lines
    # include <iostream.h>
    # include <stddef. h>
    
    main()
    {
       const size_t BUFSIZ = 128;
       char s[BUFSIZ];
    
       while (cin.getline(s,BUFSIZ))
            cout << s << '\n';
    
       return 0;
    }
    
    // End of File
    

    Listing 2 Prompts for a integer, echoes its value and address

    // int.cpp: Prompt for an integer
    # include <iostream.h>
    
    main()
    {
       int i;
    
       cout << ''Please enter an integer: '';
       cin >> i;
       cout << ''i == '' << i << '\n';
       cout << ''&i == '' << &i << '\n';
       return 0;
    }
    
    // Sample Execution:
    // Please enter an integer: 10
    // i == 10
    // &i == Oxfff4
    
    // End of File
    

    Listing 3 Counts the words in a text file

    // wc.cpp: Display word count
    #include <iostream.h>
    #include <stddef.h>
    
    main()
    {
       const size_t BUFSIZ = 128;
       char s[BUFSIZ];
       size_t wc = 0;
    
       while (cin >> s)
          ++wc;
    
       cout << wc << '\n';
       return 0;
    }
    
    // Output from the command "wc<wc.cpp"
    //35
    
    // End of File
    

    Listing 4 Identical to program copy1.cpp, but uses the extraction operator to read whitespace

    // copy3.cpp: Read whitespace with >>
    #include <iostream.h>
    
    main()
    {
       char c;
    
       // Don't skip whitespace
       cin.unsetf(ios::skipws);
    
       while (cin >> c)
          cout << c;
       return 0;
    }
    
    // End of File
    

    Listing 5 Illustrates numeric formatting

    // float.cpp: Format real numbers
    #include <iostream.h>
    
    main()
    {
       float x - 12345.6789, y = 12345;
    
       cout << x << ' ' << y << '\n';
    
       // Show two decimals
       cout.precision(2);
       cout << X << ' ' << y << '\n';
    
       // Show trailing zeroes
       cout.setf(ios::showpoint);
       cout << x << ' ' << y << '\n';
    
       // Show sign
       cout.setf(ios::showpos);
       cout << x << ' ' << y << '\n';
    
       // Return sign and precision to defaults
       cout.unsetf(ios::showpos);
       cout.precision(0);
    
       // Use scientific notation
       cout.setf(ios::scientific,ios::floatfield);
       float z = 1234567890.123456;
       cout << z << '\n';
       cout.setf(ios::uppercase);
       cout << z << '\n';
       return 0;
    }
    
    // Output
    // 12345.678711 12345
    // 12345.68 12345
    // 12345.68 12345.00
    // +12345.68 +12345.00
    // 1.234568e+09
    // 1.234568E+09
    
    // End of File
    

    Listing 6 Shows the bases of integers

    //base1.cpp: Shows the bases  of integers
    #include <iostreams.h>
    
    main()
    {
       int x, y, z;
    
       cout << "Enter three ints: ";
       cin >> x >> y >> z;
       cout << x << ',' << y << ',' << z << endl;
    
       // Print in different bases
       cout << x << ',';
       cout.setf(ios::oct,ios::basefield);
       cout << y << ',';
       cout.setf(ios::hex,ios::basefield);
       cout << z << endl;
    
       // Show the base prefix
       cout.setf(ios::showbase);
       cout << X << ',';
       cout.setf(ios::oct,ios::basefield);
       cout << y << ',';
       cout.setf(ios::hex,ios::basefield);
       cout << z << endl;
       return 0;
    }
    
    // Sample Execution
    // Enter three ints: 10 010 0x10
    // 10,8,16
    // 10,10,10
    // 0xa,010,0x10
    
    // End of File
    

    Listing 7 Changes numeric base with manipulators

    // base2.cpp:    Shows the bases of integers
    //               (Uses manipulators)
    #include <iostreams.h>
    
    main()
    {
       int x, y, z;
    
       cout << "Enter three ints: ";
       cin >> x >> y >> z;
       cout << x << ',' << y << ',' << z << endl;
    
       // Print in different bases
       cout << dec << x << ',' << oct << y << ','
           << hex << z << endl;
    
       // Show the base prefix
       cout.setf(ios::showbase);
       cout << dec << x << ',' << oct << y << ','
           << hex << z << endl;
       return 0;
    }
    
    // End of File
    

    Listing 8 Sets the output field width setw()

    // adjust.cpp: Justify output
    #include <iostream.h>
    #include <iomanip.h>
    
    main()
    {
       cout << '|' << setw(10) << "hello" << '|' << endl;
    
       cout.setf(ios::left,ios::adjustfield);
       cout << '|' << setw(10) << "hello" << '|' << endl;
    
       cout.fill('#');
       cout << '|' << setw(10) << "hello" << '|' << endl;
       return 0;
    }
    
    //Output
    //|     hello|
    //|hello     |
    //|hello#####|
    
    // End of File
    

    Listing 9 Controls the wiidth of input strings

    // width.cpp:   Control width of input strings
    #include <iostream.h>
    #include <iomanip.h>
    
    main()
    {
       char s1[10], s2[3], s3[20];
    
       cin >> setw(10) >> s1
          >> setw(3) >> s2
          >> s3;
    
       cout << s1 << ',' << s2 << ',' << s3 << endl;
       return 0;
    }
    
    // End of File
    

    Listing 10 Writes to a string stream

    // incore.cpp: Incore formatting
    #include <iostream.h>
    #include <strstream.h>
    #include <stddef.h>
    
    main()
    {
       const size t BUFSIZ = 128;
       char s[BUFSIZ];
       int i;
    
       cout << "Please enter an integer: ";
       cin >> i;
       ostrstream(s,sizeof s) << 'You entered "<< i << ends;
       cout << "s == \"" << s << "\"\n";
       return 0;
    }
    
    // Sample Execution
    // Please enter an integer: 10
    // s == "You entered 10"
    
    // End of File
    

    Listing 11 Reads words from a string stream

    // lwc.cpp: Display count of lines and words
    #include <iostream.h>
    #include <strstream.h>
    #include <stddef.h>
    
    main()
    {
       const size_t BUFSIZ = 128;
       char s[BUFSIZ];
       size_t lc = 0, wc = 0;
    
       while (cin.getline(s,BUFSIZ))
       {
          ++1c;
          istrstream line(s);
          char word[BUFSIZ];
          while (line >> word)
             ++wc;
       }
    
       cout << "Lines: "<< lc << ", words: "<< wc << endl;
       return 0;
    }
    
    // Output from the statement "lwc < lwc.cpp"
    // Lines: 24, words: 63
    
    // End of File
    

    Listing 12 Reads a comma-delimited file

    // comma.cpp: Extract comma-delimited tokens
    #include <iostream.h>
    #include <strstream.h>
    #include <stddef.h>
    
    main()
    {
    
       const_size t BUFSIZ = 128;
       char s[BUFSIZ];
    
       while (cin.getline(s,BUFSIZ))
       {
          char name[16], addr[26], city[16], state[5], zip[6];
          istrstream sstr(s);
    
          sstr.getline(name,sizeof name,',');
          sstr.getline(addr,sizeof addr,',');
          sstr.getline{city,sizeof city,',');
          sstr.getline(state,sizeof state,',');
          sstr.getline(zip,sizeof zip);
          cout << name << '|'
              << addr << ,|,
              << city << '|'
              << state << '|'
              << zip << endl;
       }
    
       return 0;
    }
    
    // End of File
    

    Listing 13 Uses file streams to copy files

    // copy4.cpp: Copy one file to another
    #include <iostream.h>
    #include <fstream.h>  // Required for file streams
    #include <std1ib.h>
    
    main(int argc, char *argv[])
    {
       if (argc == 3)
       {
          ifstream inf(argv [1] );
          ofstream outf(argv[2]);
          if (inf && outf)
          {
             char c;
    
             while (inf.get(c))
                outf.put(c);
    
             return EXIT_SUCCESS;
          } // Streams destroyed by this point
       }
       return EXIT_FAILURE;
    }
    
    // End of File
    

    Listing 14 Navigates a file of fixed-length records

    // records.cpp: Illustrate file positioning
    #include <iostream.h>
    #include <fstream.h>
    #include <strstream.h>
    #include <stddef.h>
    
    struct record
    {
       char last[16];
       char first[11];
       int age;
    };
    
    main()
    {
       const size_t BUFSIZ = 81;
       int nrecs = 0;
       char buf[BUFSIZ];
       struct record recbuf;
       int mode = ios::bin | ios::trunc | ios::out | ios::in;
       fstream f("recs.dat",mode);
    
       for(;;)
       {
          // Prompt for input line
          cout << "Enter LAST,FIRST,AGE: ";
          if (!cin.getline(buf,BUFSIZ))
              break;
    
          // Extract fields
          istrstream line(buf);
          line.getline((char *)&recbuf.last,sizeof recbuf.last,',');
          line.getline((char *)&recbuf.first,sizeof recbuf.first,',');
          line >> recbuf.age;
    
          // Write to file
          f.write((char *) &recbuf, sizeof recbuf);
          ++nrecs;
       }
    
       /* Position at last record */
       f.seekg((nrecs-1)*sizeof(struct record),ios::beg);
       if (f.read((char *) &recbuf,sizeof recbuf))
          cout<< "last: "<< recbuf.last
             << ", first: ' << recbuf.first
             << ", age: ' << recbuf.age
             << endl;
    
       /* Position at first record */
       f.seekg(0,ios::beg);
       if (f.read({char *) &recbuf,sizeof recbuf))
          cout << "last: " << recbuf.last
              << ", first: ' << recbuf.first
              << ", age: " << recbuf.age
              << end1;
       
       return 0;
    }
    
    // Sample execution
    // Enter LAST,FIRST,AGE: Lincoln,Abraham,188
    // Enter LAST,FIRST,AGE: Bach,Johann,267
    // Enter LAST,FIRST,AGE: Tze,Lao,3120
    // Enter LAST,FIRST,AGE: ^Z
    // last: Tze, first: Lao, age: 3120
    // last: Lincoln, first: Abraham, age: 188
    
    // End of File
    

    Listing 15 Copies files with or without redirection

    // copy5.cpp: Copy one file to another
    #include <iostream.h>
    #include <fstream.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    main(int argc, char *arg[])
    {
       ifstream inf;
       ofstream outf;
    
       // Open optional input file
       if (argc> 1)
          inf. open (argv [1] );
       else
          inf.attach(fileno(stdin));
    
       // Open optional output file
       if (argc > 2)
          outf.open(argv[2]);
       else
          outf.attach(fileno(stdout));
    
       if (inf && outf)
       {
          char c;
    
          while (inf.get(c))
                outf.put(c);
    
          return EXIT_SUCCESS;
       }
       else
          return EXIT_FAILURE;
    }
    
    // End of File