about
10/27/2022
C++ Story LibraryStreams
C++ Story Code C++ Story Docs

Chapter #10 - C++ Stream Libraries

stream types and example code

10.0 Prologue

The C++ language comes with a great collection of standard libraries. There are scores of very well designed facilities and more are coming with the next standardization in C++20. In this part we will be discussing the standard I/O streams libraries.
Fig 1. IOStream Hierarchy

10.1 Streams

The stream library is structured as shown in Fig 1. std::ios is a class responsible for managing formatting and error information. All its derived classes, std::istream, std::ostream, ... provide an invariant interface for streams. They make the language we use to interact with streams. The buffers std::streambuf and its derived classes have a number of virtual functions that support customization. This is a really elegant design. The interface is fixed so everyone knows how to deal with streams, but their semantics can be customized by implementing application specific buffer adapters. I did that as an example in a graduate course CSE776 - Design Patterns, using the "Adapter Pattern". That example supported sending socket messages through a stream interface. All of the real stream functionality is provided by the buffers. You can open, read, write, and close files, for example, just using an instance of std::filebuf.

10.2 Streams Formatting Code Examples

std::iostreams formatting is fairly simple. All the format settings are encoded in bits in a long int which you can access from the functions: long frmt = flags(); // retrieve the current format settings flags(frmt); // restore the original settings A lot of the settings can be activated by toggling individual bits when there are only two format choices. However, there are three formats that each have three choices:
  1. right, left, and internal adjust in a specified field width:
    std::cout.setf(ios::left, ios::adjustfield);
  2. scientific, fixed, and automatic for floating point numbers:
    std::cout.setf(ios::scientific, ios::floatfield);
  3. dec, oct, and hex for number formatting:
    std::cout.setf(ios::hex, ios::basefield);
Each of the choices is encoded in a single bit, so it is possible, for example, that both left and right adjust could be set, resulting in undefined behavior. The adjustfield, floatfield, and basefield arguments cause all of the bits for that format to be zeroed, then the one you want is set with ios::left, for example.
Formats can be set using flags(frmt) or setf(frmt, field), as shown in the example, below. They can also be set using iomanipulators. An iomanipulator is a function that sets some part of the std::ios state. That's done by inserting them into a stream. outstrm << setw(15); The insertion operator is overloaded to accept function pointers for just this purpose.

Table 1. - Partial list of io manipulators

- full list
manipulator function
<ios> header included in <iostream> header
boolalpha, noboolalpha set text or numeric representations, respectively
showpoint, noshowpoint decimal point is always included in floating-point representation
skipws, noskipws leading whitespace is skipped on input
internal, left, right placement of fill characters
dec, oct, hex sets base used for integer I/O
fixed, scientific, hexfloat, defaultfloat sets formatting used for floating-point I/O
<iostream>
ends emits trailing '\0' to terminate string
flush flushes output stream
endl emits '\n' and flushes output stream
<iomanip>
resetiosflags clears specified ios_base flags
setiosflags sets specified ios_base flags
setfill changes fill character which is ' ' by default
setprecision specifies number of digits after the point (not precision of output)
setw changes width of the next input or output field
get-time parses date/time value of specified format
put-time formats and outputs a date/time value using specified format
quoted inserts and extracts quoted strings with embedded spaces
The code example, below, shows most of the formatting mechanisms in iostreams.
Example: std::iostream formats Formats.cpp /////////////////////////////////////////////////////////// // formats.cpp // // demonstrate ios formating // // // // Jim Fawcett, 24 Mar 96, modified 04 Mar 02 // /////////////////////////////////////////////////////////// #include <iostream> #include <iomanip> #include <string> using namespace std; void main() { title("Demonstrating ios formatting",'='); title("formating integers"); ///////////////////////////////////////////////////////////// // flags: // // skipws showbase showpoint uppercase showpos // // unitbuf stdio // ///////////////////////////////////////////////////////////// // basefield: // // dec hex oct // ///////////////////////////////////////////////////////////// long save = cout.flags(); // save format state cout.setf(ios::showbase|ios::uppercase); cout << " " << 59 << " in hexadecimal is " << hex << 59 << dec << endl << " " << 59 << " in octal is " << oct << 59 << dec << endl; cout.flags(save); // restore original format state title("formating doubles"); ///////////////////////////////////////////////////////////////// // function: // // width() width(n) fill() fill(n) // // precision() precision(n) // ///////////////////////////////////////////////////////////////// // floatfield: // // scientific fixed automatic // ///////////////////////////////////////////////////////////////// cout.precision(6); cout.setf(ios::right, ios::adjustfield); cout.setf(ios::scientific, ios::floatfield); cout.setf(ios::showpoint); double d = 9; int i; for(i=0; i<5; i++) cout << setw(14) << (d /= 3); cout << endl; cout.flags(save); title("adjust, fill, and width"); ///////////////////////////////////////////////////////////////// // adjustfield: // // left right internal // ///////////////////////////////////////////////////////////////// char *numbers[5] = { "zero", "one", "two", "three", "four" }; int nums[5] = { 0, 1, 2, 3, 4 }; cout.fill('.'); for(i=0; i<5; i++) { cout.setf(ios::left, ios::adjustfield); cout << " " << setw(30) << numbers[i]; cout.setf(ios::right, ios::adjustfield); cout << setw(30) << nums[i] << endl; } cout.flags(save); title("stream manipulators"); ///////////////////////////////////////////////////////////// // manipulators: // // iostream.h - dec, oct, hex, endl, ws, ends, flush, // // iomanip.h - // // setfill(char), setw(int), setprecision(int), // // setiosflags(long), resetiosflags(long) // ///////////////////////////////////////////////////////////// cout << " " << setprecision(5) << (d = 1.0/3.0) << endl; cout << " " << setfill('.') << setw(20) << d << endl; cout << " " << setiosflags(ios::left | ios::showpos) << setw(20) << d << endl; cout << " " << resetiosflags(ios::left | ios::showpos) << d << endl; cout.flags(save); cout << endl; } Output: ============================== Demonstrating ios formatting ============================== formating integers -------------------- 59 in hexadecimal is 0X3B 59 in octal is 073 formating doubles ------------------- 3.000000e+00 1.000000e+00 3.333333e-01 1.111111e-01 3.703704e-02 adjust, fill, and width ------------------------- zero.......................................................0 one........................................................1 two........................................................2 three......................................................3 four.......................................................4 stream manipulators --------------------- 0.33333 .............0.33333 +0.33333............ 0.33333

10.3 File Streams Code Examples

The next example illustrates working with file streams. Many of the things you will need to do when working with files are presented in the example. Files can be opened in two ways and closed:
  1. std::ifstream in(filename, ios::in);
  2. std::ifstream in; in.open(filename, ios::in);
  3. in.close();
When file streams go out of scope their destructors call close(), but it does no harm to close the file before it goes out of scope, freeing the internal OS file handle.
The example, below, illustrates standard error handling. When you attempt to open a file that may or may not succeed.
  1. The file name may be incorrect or the file may not exist.
  2. Other code may have that file opened.
  3. The user may not have permissions to open the file.
For this, standard streams have a simple error handling process. Streams can be in any of three states:
  1. good state, tested with the function bool good(). When good is true the stream operates normally.
  2. bad state, tested with bool bad(). Streams in a bad state are fully functional, but will not respond to any commands until the stream error is cleared with void clear(). A state can go bad when it attempts to read an end of file integer, eof. To restore normal operation, clear the error and back up at least one character by calling strm.seek(-1);
  3. fail state, tested with bool fail(). Streams in a fail state have been corrupted. The only thing you can do with a failed stream is to destroy it or re-initialize with an open(filename, ios attributes);.
You should always check filestream state after opening, before any attempt to use it. We often do that with: std::ofstream out(filename, ios::out);
if(!out.good()) { /* do some clean up and return */ }
Finally, here's the example:
Example: std::fstreams I/O Fileio.cpp /////////////////////////////////////////////////////////////// // fileio.cpp - demonstrate fstreams // // // // Jim Fawcett, 24 Mar 96, modified 04 Mar 02 // /////////////////////////////////////////////////////////////// #include <iostream> // cout, << #include <fstream> // ifstream(), <<, >> #include <cstdlib> // exit(1); #include <sstream> #include <string> using namespace std; //----< display titles >--------------------------------------- void title(const char *t, char ul = '-', ostream &out = cout) { int len = strlen(t) + 2; string line(len,ul); if(ul == '=') out << "\n " << line; out << "\n " << t << "\n " << line << endl; } //----< begin demonstration >---------------------------------- void main(int argc, char *argv[]) { ///////////////////////////////////////////////// title("Demonstrating Basic File Operations",'='); ///////////////////////////////////////////////// if(argc < 2) { cout << "\nplease enter name of text file on command line\n\n"; exit(1); } const int bufSize = 80; char buffer[bufSize]; // // create a scope with { } and define an input stream inside // then read line-by-line and send to standard output { ifstream in(argv[argc-1]); if(!in.good()) { cout << "can't open file " << argv[argc-1] << endl; exit(1); } else { ostringstream temp; temp << "processing file " << argv[argc-1] << " using istream::getline()"; title(temp.str().c_str()); ////////////////////////// } while(in.good()) { in.getline(buffer,bufSize); cout << buffer << endl; } } // in goes out of scope and is destroyed // define stream again at global level, get pointer to // its filebuf and stream input directly to output ostringstream temp; temp << "processing file " << argv[argc-1] << " using filebuf::rdbuf()"; title(temp.str().c_str()); ////////////////////////// ifstream in(argv[argc-1]); filebuf *bptr = in.rdbuf(); // get pointer to stream cout << bptr << endl; // stream input to output in.close(); // still in scope so close it // // open stream again, seek to end to find size, and // backup half way, then send last half to stdout in.open(argv[argc-1]); // open it again in.seekg(0, ios::end); // go to end of file streampos sp = in.tellg(); // size = number of bytes from beg in.seekg(-sp/2, ios::end); // go to middle in.getline(buffer,bufSize); // go to next newline ostringstream temp2; temp2 << "processing last half of this " << sp << " byte file"; title(temp2.str().c_str()); /////////////////////////// cout << in.rdbuf() << endl; // output from that point to end title("stream state goes bad when attempting to read past end of file"); //////////////////////////////////////////////////////////////////////// char ch; in >> ch; if(!in.good()) cout << " attempting to read past EOF makes stream state" << " not good\n"; title("can't read any more until we clear stream to good state"); ///////////////////////////////////////////////////////////////// in.clear(); if(in.good()) cout << " stream state good after clear()\n"; in.seekg(-1,ios::end); // back up to good char in >> ch; if(in.good()) cout << " after backing up and reading " << ch << " stream state still good\n"; in >> ch; if(!in.good()) cout << " after reading EOF stream state not good again\n"; cout << endl; } Output: ===================================== Demonstrating Basic File Operations ===================================== processing file test.txt using istream::getline() --------------------------------------------------- this is the first line second line third line fourth line fifth line this is the sixth and final line processing file test.txt using filebuf::rdbuf() ------------------------------------------------- this is the first line second line third line fourth line fifth line this is the sixth and final line processing last half of this 106 byte file -------------------------------------------- fifth line this is the sixth and final line stream state goes bad when attempting to read past end of file ---------------------------------------------------------------- attempting to read past EOF makes stream state not good can't read any more until we clear stream to good state --------------------------------------------------------- stream state good after clear() after backing up and reading e stream state still good after reading EOF stream state not good again

10.4 Filebuf I/O Code Examples

The next example illustrates that most things you can do with a standard stream object you can also do with its internal filebuf. In other words, the stream object presents a (non-virtual) interface and its filebuf does all the work.
Example: std::Filebuf I/O Filebuf.cpp ///////////////////////////////////////////////////////////////// // filebuf.cpp // // demonstrate low level input and output using streambufs // // built from FILE pointer and stdout // // // // Jim Fawcett, 24 Mar 96, modified 23 Mar 97, 01 Mar 04 // ///////////////////////////////////////////////////////////////// #include <iostream> // cout, << #include <fstream> // ifstream(), <<, >> using namespace std; //----< display titles >--------------------------------------- void title(const char *t, char ul = '-', ostream &out = cout) { int len = strlen(t) + 2; std::string line(len,ul); if(ul == '=') out << "\n " << line.c_str(); out << "\n " << t << "\n " << line.c_str() << endl; } //----< begin demonstration >---------------------------------- void main(int argc, char *argv[]) { ////////////////////////////////////////////////////////////// title("Demonstrating use of Stream Buffers with File IO",'='); ////////////////////////////////////////////////////////////// if(argc < 2) { cout << "\nplease enter name of text file on command line\n"; exit(1); } title("using streambuf for input from file"); ///////////////////////////////////////////// filebuf ifb; // create filebuf ifb.open(argv[argc-1],ios::in); // attach to file cout << &ifb << endl; // stream to cout cout.flush(); title("using streambuf for output to stdout"); ////////////////////////////////////////////// istream in(&ifb); // make stream in, attached to ifb in.seekg(0); // move to top of input buffer ostream out(cout.rdbuf()); // make stream out, attached to cout streambuf out << &ifb << endl; // stream it to out out.flush(); // could replace last statement with the lower level // gets and puts shown below title("using low-level streambuf interface for output to stdout"); ////////////////////////////////////////////////////////////////// streambuf* pOfb = cout.rdbuf(); in.seekg(0); char ch; while((ch = ifb.sbumpc()) != EOF) pOfb->sputc(ch); cout << "\n\n"; } Output: ================================================== Demonstrating use of Stream Buffers with File IO ================================================== using streambuf for input from file ------------------------------------- this is the first line second line third line fourth line fifth line this is the sixth and final line using streambuf for output to stdout -------------------------------------- this is the first line second line third line fourth line fifth line this is the sixth and final line using low-level streambuf interface for output to stdout ---------------------------------------------------------- this is the first line second line third line fourth line fifth line this is the sixth and final line

10.5 Read-Write Code Examples

The next example illustrates that you can read and write to the same file using a single filebuf for both reading and writing. You might do that if you were building a database record manager where you need to read existing data but also enter new data into the same file. In order to make this work for something useful you will have to devise a schema which defines regions of the file where you read and write specific columns of a record. That's what SQL databases do. This example just demonstrates that you can both read and write. It doesn't do any schema-based record management (but that wouldn't be too hard to do). The way we get that to work is to wrap a common filebuf with both an input stream and an output stream. Here's the details:
Read/Write Streams Readwrite.cpp /////////////////////////////////////////////////////////////// // readwrit.cpp - demonstrate read/write fstreams // // with buffer location seeking // // Jim Fawcett, 24 Mar 96, modified 23 Mar 97 // /////////////////////////////////////////////////////////////// #include <iostream> // cout, << #include <fstream> // ifstream(), <<, >> #include <cstdlib> // exit(1); #include <string> using namespace std; //----< display titles >--------------------------------------- void title(const char *t, char ul = '-', ostream &out = cout) { int len = strlen(t) + 2; string line(len,ul); if(ul == '=') out << "\n " << line; out << "\n " << t << "\n " << line << endl; } //----< begin demonstration >---------------------------------- void main(int argc, char *argv[]) { /////////////////////////////////////////////////////////////// title("Demonstration of Reading AND Writing to a Common File",'='); /////////////////////////////////////////////////////////////// if(argc < 2) { cout << "please enter file name on command line\n"; exit(1); } // /////////////////////////////////////////////////////////////// // file open modes: in out app ate // // nocreate noreplace trunc // /////////////////////////////////////////////////////////////// // save input file contents in a temporary file // for reading and writing ifstream masterin(argv[argc-1]); ofstream tempout("tmp.tmp"); tempout << masterin.rdbuf(); masterin.close(); tempout.close(); // open temporary for input AND output processing ifstream in("tmp.tmp", ios::in|ios::out); if(!in.good()) { cout << "can't open file tmp.tmp" << endl; exit(1); } // use input stream buffer for output stream too ostream out(in.rdbuf()); // now, try reading and writing title("this is a test file for reading and writing"); ///////////////////////////////////////////////////// cout << in.rdbuf(); out << "---\nthis text is added to the end\n"; // how many bytes in file? streampos sp = out.tellp(); out.seekp(-sp, ios::end); // back up from end out << "this text overwrites beginning of file\n"; cout << endl; // write it out to see what happened title("modified file:"); //////////////////////// in.seekg(ios::beg); // go to beginning cout << in.rdbuf() << endl; } Output: ======================================================= Demonstration of Reading AND Writing to a Common File ======================================================= this is a test file for reading and writing --------------------------------------------- this is the first line second line third line fourth line fifth line this is the sixth and final line modified file: ---------------- this text overwrites beginning of file rd line fourth line fifth line this is the sixth and final line--- this text is added to the end

10.6 String Streams Code Examples

The final example demonstrates stringstreams. These are particularly useful for doing object to string and string to object transformations. That's what a std::ostream does. If you write: double d{ 3.14159 };
std::cout << d;
std::cout transforms its double input into a sequence of characters written to your terminal, e.g., a string. All the standard streams do this, and, with stringstream objects, we can retrieve the string for processing rather than write it to a terminal. Here's the demo:
Example: std::stringstreams Strio.cpp /////////////////////////////////////////////////////////////// // strio.cpp - demonstrate stringstreams // // // // Jim Fawcett, 24 Mar 96, modified 04 Mar 02 // /////////////////////////////////////////////////////////////// #include <iostream> // cout, << #include <sstream> // istringstream(), ostringstream(), <<, >> #include <string> using namespace std; //----< display titles >--------------------------------------- void title(const char *t, char ul = '-', ostream &out = cout) { int len = strlen(t) + 2; string line(len,ul); if(ul == '=') out << "\n " << line; out << "\n " << t << "\n " << line << endl; } //----< begin demonstration >---------------------------------- void main() { /////////////////////////////////////////////////// title("Demonstrating stringstream operations",'='); /////////////////////////////////////////////////// title("reading from istringstream"); ///////////////////////////// istringstream source("15 3.1415927 Now is the hour"); cout << "\n " << source.str() << endl; title("writing to ostringstream"); //////////////////////////////////// ostringstream destin; destin << source.str(); stringbuf *pStringBuf = destin.rdbuf(); cout << "\n--source-----" << source.str(); cout << "\n--destin-----" << destin.str(); cout << "\n--stringBuf--" << pStringBuf->str() << endl; // title("writing to custom buffer no longer supported in ostringstream"); /////////////////////////////////////////////////////////////////////// title("parsing input buffer"); ////////////////////////////// int i; const int bufSize=50; double d; char savebuf[bufSize]; source.seekg(0); source >> i >> d; cout << endl; cout << " i = " << i << endl; cout << " d = " << d << endl; int j; // The following operation is dangerous! // extraction writes whole word into buffer, // even if word size is larger than buffer for(j=0; j<4; j++) { source >> savebuf; cout << " savebuf = " << savebuf << endl; } // Here is a saver, though less convenient way // to read strings from a stream title("safe parsing using destination string"); /////////////////////////////////////////////// source.clear(); source.seekg(0); string temp; while(source.good()) { source >> temp; cout << "\n " << temp; } cout << "\n\n"; title("char-by-char parsing"); ////////////////////////////// source.clear(); source.seekg(0); temp = ""; while(source.good()) { char ch = source.get(); if(!isspace(ch)) temp += ch; else if(temp.size() > 0) { cout << "\n " << temp.c_str(); temp = ""; } } if(temp.size() > 0) cout << "\n " << temp.c_str(); cout << "\n\n"; } Output ======================================= Demonstrating stringstream operations ======================================= reading from istringstream ---------------------------- 15 3.1415927 Now is the hour writing to ostringstream -------------------------- --source-----15 3.1415927 Now is the hour --destin-----15 3.1415927 Now is the hour --stringBuf--15 3.1415927 Now is the hour parsing input buffer ---------------------- i = 15 d = 3.14159 savebuf = Now savebuf = is savebuf = the savebuf = hour safe parsing using destination string --------------------------------------- 15 3.1415927 Now is the hour char-by-char parsing ---------------------- 15 3.1415927 Now is the hour 

10.7 Epilogue

Commentary to be added later

10.8 References

string formatting (see answer 15) - stackoverflow
  Next Prev Pages Sections About Keys