Using Stream I/O - Security - C++ For Dummies (2014)

C++ For Dummies (2014)

Part V

Security

Chapter 23

Using Stream I/O

In This Chapter

arrow Performing input/output

arrow Rediscovering stream I/O as an overloaded operator

arrow Examining the other methods of the file class

arrow Using stream buffer I/O

Programs appearing before this chapter read from the cin input object and output through the cout output object. Perhaps you haven't really thought about it much, but this input/output technique is a subset of what is known as stream I/O.

In this chapter, I describe stream I/O in more detail. I must warn you that stream I/O is too large a topic to be covered completely in a single chapter — entire books are devoted to this one topic. Fortunately for both of us, there isn’t all that much that you need to know about stream I/O to write the vast majority of programs.

How Stream I/O Works

Stream I/O is based on overloaded versions of operator>>() and operator<<(). The declaration of these overloaded operators is found in the include file iostream, which are included in all the programs in this book beginning with Chapter 1. The code for these functions is included in the standard library, which your C++ program links with.

The following code shows just a few of the prototypes appearing in iostream:

//for input we have:
istream& operator>>(istream& source, char *pDest);
istream& operator>>(istream& source, string &sDest);
istream& operator>>(istream& source, int &dest);
istream& operator>>(istream& source, double &dest);
//...and so forth...

//for output we have:
ostream& operator<<(ostream& dest, char *pSource);
ostream& operator<<(ostream& dest, string &sDest);
ostream& operator<<(ostream& dest, int source);
ostream& operator<<(ostream& dest, double source);
//...and so it goes...

When overloaded to perform I/O, operator>>() is called the extractor and operator<<() is called the inserter. The class istream is the basic class for input from a file or a device such as the keyboard. C++ opens the istream object cin when the program starts. Similarly, ostream is the basis for output. The prototypes above are for inserters and extractors for pointers to null terminated character strings (like “My name”), for string objects, for ints, and for doubles.

Default stream objects

C++ adds a chunk of code to the front of your program that executes before main() gets control. Among other things, this code creates the default input/output objects shown in Table 23-1.

Table 23-1 Standard Stream I/O Objects

Object

Class

Purpose

cin

istream

Standard char input

wcin

wistream

Standard wchar_t “wide char” input

cout

ostream

Standard char output

wcout

wostream

Standard wchar_t “wide char” output

cerr

ostream

Standard error output

wcerr

wostream

Standard error wchar_t “wide char” output

clog

ostream

Standard log

wclog

ostream

Standard wchar_t “wide char” log

You've seen cin and cout as they read input from the keyboard and output to the display, respectively. The user can reroute standard input and standard output to a file when he executes a program as follows:

C:>MyProgram <InputFile.txt >DefaultOut.txt

Here the operator is saying “Execute MyProgram but read standard input from InputFile.txt instead of the keyboard and send what would otherwise go to the standard output to the file DefaultOut.txt.

image Rerouting input and output works from the DOS prompt in Windows and under all versions of Unix and Linux. It's the easiest way to perform file input/output when you're trying to write something quick and dirty.

By default, the cerr object outputs to the display just like cout, except it is rerouted separately — rerouting cout-type default output to a file does not reroute cerr output. This allows a program to display error messages to the operator even if cout has been rerouted to a file.

image Error messages should be sent to cerr rather than cout just in case the operator has rerouted standard output.

The wcin, wcout, and wcerr are wide version of standard input, output, and error, respectively. These are designed to handle Unicode symbols:

cout << "This is narrow output" << endl;
wcout << L"This is wide output" << endl;

Stream Input/Output

The classes ifstream and ofstream defined in the include file fstream are subclasses of istream and ostream designed to perform stream input and output to disk files. You can use the same extractors and inserters on ifstream and ofstream objects that you've been using on cin and cout.

image The ifstream is actually an instantiation of the template class basic_ifstream<T> with T set to char. I discuss template classes in Chapter 26. The basic_ifstream<T> template class is instantiated with other types as well to provide different types of input classes. For example, the wide stream file class wifstream is based on the same basic_ifstream<T> with T set to wchar_t. The ofstream is the same as basic_ofstream<char>.

The classes ifstream and ofstream provide constructors used to open a file for input and output, respectively:

ifstream::ifstream(const char *pszFileName,
ios_base::openmode mode = ios_base::in);
ofstream::ofstream(const char *pszFileName,
ios_base::openmode mode = ios_base::out|ios_base::trunc);

The first argument is a pointer to the name of the file to open. The second argument specifies the mode. The type openmode is an integer type defined in ios_base. Also defined within ios_base are the possible values for mode listed in Table 23-2. These are bit fields that the programmer bitwise ORs together. (See Chapter 4 for an explanation of the ORing of bit fields.) The default mode for ifstream is to open the file for input with the pointer set to the beginning of the file (that's logical enough).

Table 23-2 Constants that Control How Files Are Opened

Flag

Meaning

ios_base::app

Seek to end-of-file before each write.

ios_base::ate

Seek to end-of-file immediately after opening the file, if it exists.

ios_base::binary

Open file in binary mode (alternative is text mode).

ios_base::in

Open file for input (implied for istream).

ios_base::out

Open file for output (implied for ostream).

ios_base::trunc

Truncate file, if it exists (default for ostream).

The default for ofstream is to open for output and to truncate the file if it exists already. The alternative to truncate is ios_base::app, which means append new output onto the end of the file if it exists already. Both options create a file if it doesn't already exist.

For example, the following StreamOutput program opens the file MyName.txt and then writes some important and absolutely true information to that file:

// StreamOutput - simple output to a file
#include <fstream>
using namespace std;

int main(int nNumberofArgs, char* pszArgs[])
{
ofstream my("MyName.txt");
my << "Stephen Davis is suave and handsome\n"
<< "and definitely not balding prematurely"
<< endl;
return 0;
}

The destructor for the file stream classes automatically close the associated file. In my simple example, the MyName.txt file was closed when the my object went out of scope upon returning from main(). Global objects are closed as part of program termination.

Open modes

Table 23-2 shows the different modes that are possible when opening a file. However, you need to answer three basic questions every time you open a file:

· Do you want to read from the file or write to the file? Use ifstream to read and ofstream for writing. If you intend to both write to and read from the same file, use the fstream and set mode to in|out, but good luck — it's much better to write to a file completely and then close it and reopen it for reading as a separate object.

· If you are writing to the file and it already exists, do you want to add to the existing contents (in which case, open with ate set) or truncate the file and start over (in which case use trunc)?

· Are you reading or writing text or binary data? Both ifstream and ofstream default to text mode. Use binary mode if you are reading or writing raw, non-text data.

The primary difference between binary and text mode lies in the way that newlines are handled. The Unix operating system was written in the days when typewriters were still fashionable (when it was called “typing” instead of “keyboarding”). Unix ended sentences with a linefeed followed by a carriage return.

Subsequent operating systems saw no reason to continue using two characters to end a sentence, but they couldn't agree on which character to use. Some use the carriage return, others used the linefeed, now renamed newline. The C++ standard is the single newline.

When a file is opened in text mode, the C++ library converts the single newline character into what is appropriate for your operating system on output, whether it's a carriage return plus linefeed, a single carriage return, a linefeed, or something else entirely. It performs the opposite conversion while reading a file. The C++ library does no such conversions for a file opened in binary mode.

image Always use binary mode when manipulating a file that's not in human-readable format. Otherwise, if a byte in the data stream just happens to be the same as a carriage return or a linefeed, the file I/O library will modify it.

Hey, file, what state are you in?

A constructed fstream object (including ifstream and ofstream) becomes a proxy for the file that it is associated with. For example, the stream object maintains state information about the I/O process. The member function bad() returns true if something “bad” happens. That nebulous term means that the file couldn't be opened, some internal object was messed up, or things are just generally hosed. A lesser error fail() indicates that either something bad() happened or the last read failed — for example, if you try to read an int and all the program can find is a character that rates a fail() but not a bad(). The member function good() returns true if both bad() and fail() are false.

Attempts to input from or output to a stream object that has an error set are ignored. The member function clear() zeros out the fail flag to give you another chance if the error is temporary — in general, clear() clears “failures” but not “bad” things. All attempts to output to an ofstreamobject that has an error have no effect.

image This last paragraph is meant quite literally — no input or output is possible as long as the internal error state of the stream object you're using is non-zero. The program won't even try until you call clear() to clear the error flags if the error is temporary and you can clear it.

Can you show me an example?

The following example program demonstrates how to go about using the ifstream class to extract a series of integers:

// StreamInput - simple input from a file using fstream
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
using namespace std;

ifstream& openFile()
{
ifstream* pFileStream = 0;
for(;;)
{
// open the file specified by the user
string sFileName;
cout << "Enter the name of a file with integers:";
cin >> sFileName;

//open file for reading
pFileStream = new ifstream(sFileName.c_str());
if (pFileStream->good())
{
pFileStream->seekg(0);
cerr << "Successfully opened "
<< sFileName << endl;
break;
}
cerr << "Couldn't open " << sFileName << endl;
delete pFileStream;
}
return *pFileStream;
}

int main(int nNumberofArgs, char* pszArgs[])
{
// get a file stream
ifstream& fileStream = openFile();

// stop when no more data in file
while (!fileStream.eof())
{
// read a value
int nValue = 0;
fileStream >> nValue;

// stop if the file read failed (probably because
// we ran upon something that's not an int or
// because we found a newline with nothing after
// it)
if (fileStream.fail())
{
break;
}

// output the value just read
cout << nValue << endl;
}

cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

The function openFile() prompts the user for the name of a file to open. The function creates an ifstream() object with the specified name. Creating an ifstream object automatically opens the file for input. If the file is opened properly, the function returns a reference to the ifstream object to use for reading. Otherwise, the program deletes the object and tries again. The only way to get out of the loop is to enter a valid filename or abort the program.

image Don't forget to delete the pFileStream object if the open fails. These are the sneaky ways that memory leaks creep in.

The program reads integer values from the object referenced by fileStream until either fail() or the program reaches the end-of-file as indicated by the member function eof().

image Let me warn you one more time: Not only is nothing returned from reading an input stream that has an error, but also the buffer comes back unchanged. This program can easily come to the false conclusion that it has just read the same value it previously read. Furthermore, eof()will never return a true on an input stream that has an error.

The output from this program appears as follows (I added boldface to my input):

Enter the name of a file with integers:chicken
Couldn't open chicken
Enter the name of a file with integers:integers.txt
Successfully opened integers.txt
1
2
3
4
5
6
Press Enter to continue...

image Code::Blocks for Windows opens the console application in the project directory so all you need to enter is the file name as shown. Code::Blocks for Macintosh opens the console window in your user directory so you need to enter the entire path to the file:Desktop/CPP_Programs_from_Book/Chap23/StreamInput/integers.txt (assuming that you installed the source files in the default location).


Don't overflow that buffer!

If you look closely at the openfile() method in the StreamInput example program, you'll see yet another way to make sure that the operator doesn't overflow the character buffer. Let's review. I could have used something like the following:

char szFileName[80]; // any array size is possible
cin >> szFileName; // input the name of the file to open

You can probably find code like this in the early chapters of this book (when you were still wearing your C++ training wheels). The problem with this approach is that nothing tells the extractor that the buffer is only 80 characters long — it will continue to read until it sees a newline, which might be thousands of characters later.

Well, 80 characters is a bit small. How about we increase the buffer size to 256 characters? That sort of misses the point; the implicit assumption you are making with this type of approach is that any buffer overflow is the result of an honest mistake (and a very long filename!). More and more this is not the case. Malicious users find ways to overflow these fixed size buffers all the time. Several major worms have been launched on the backs of buffer overflow attacks. (I will explain buffer overflows in detail in Chapter 28.)

One approach to avoiding buffer overflow that you have seen in earlier chapters is to use the getline() method to limit to the size of the buffer the number of characters that the program will read:

char szFileName[80];
cin.getline(szFileName, 80); // read not more than 80 chars

This code segment says read a line of input (up to the next newline character) but not more than 80 characters since that's the size of the buffer. Any characters not read are left for the next call to getline().

Another approach is to make the buffer size fit the number of available characters. The extractor for the string class is smart enough to dynamically resize the buffer to fit the available data:

string sFileName;
cin >> sFileName; // string sizes buffer to fit amount of data input


Other Methods of the Stream Classes

The istream and ostream classes provide a number of methods, as shown in Table 23-3 (this is not a complete list). The prototypes for these functions reside in the fstream include file. They are described in the remainder of this section.

Table 23-3 Major Methods of the I/O Stream Classes

Method

Meaning

bool bad()

Returns true if a serious error has occurred.

void clear(iostate flags = ios_base::goodbit)

Clears (or sets) the I/O state flags.

void close()

Closes the file associated with a stream object.

bool eof()

Returns true if no more characters are left in the file to be read.

iostate exception()

Returns the conditions that will cause an exception.

void exception(iostate)

Sets the conditions that will cause an exception. Multiple conditions can be ORed together; e.g., exception(ios_base::badbit|ios_base::failbit). See Chapter 24 for a discussion of exceptions.

char fill()char fill(char newFill)

Returns or sets the fill character.

fmtflags flags()fmtflags flags(fmtflags f)

Returns or sets format flags. (See the “Controlling format” section.)

void flush()

Flushes the output buffer to the disk.

int gcount()

Returns the number of bytes read during the last input.

char get()

Reads individual characters from the file.

char getline( char* buffer, int count, char delimiter = '\n')

Reads multiple characters either until the end-of-file, until a delimiter is encountered, or until count - 1 characters read. Tack a null onto the end of the line read. Do not store the delimiter read into the buffer.

bool good()

Returns true if no error conditions are set.

void open( const char* filename, openmode mode = default)

Same arguments as the constructor. Performs the same file open on an existing object that the constructor performs when creating a new object.

streamsize precision() streamsize precision( streamsize s)

Reads or sets the number of digits displayed for floating-point variables.

ostream& put(char ch)

Writes a single character to the stream.

istream& read( char* buffer, streamsize num)

Reads a block of data. Reads either num bytes or until an end-of-file is encountered, whichever occurs first.

istream& seekg( pos_type position)istream& seekg( off_type offset, ios_base::seekdir)

Positions the read pointer either position bytes from the beginning of the file or offset bytes from the current position.

istream& seekp( pos_type position)istream& seekp( off_type offset, ios_base::seekdir)

Positions the write pointer.

fmtflags setf(fmtflags)

Sets specific format flags. Returns old value.

pos_type tellg()

Returns the position of the read pointer.

pos_type tellp()

Returns the position of the write pointer.

fmtflags unsetf(fmtflags)

Clears specific format flags. Returns old value.

int width()int width(int w)

Reads or sets the number of characters to be displayed by the next formatted output statement.

ostream& write( const char* buffer, streamsize num)

Writes a block of data to the output file.

Reading and writing streams directly

The inserter and extractor operators provide a convenient mechanism for reading formatted input. However, sometimes you just want to say, “Give it to me; I don't care what the format is.” Several methods are useful in this context.

The simplest function, get(), just returns the next character in the input file. Its output equivalent is put(). The function getline() returns a string of characters up until some terminator — the default is a newline. getline() strips off the terminator but makes no other attempt to reformat or otherwise interpret the input.

The member function read() is even more basic. This function reads the number of characters that you specify, or less if the program encounters an end-of-file. The function gcount() always returns the actual number of characters read. The output equivalent is write().

The following example program uses the read() and write() functions to create a backup of any file you give it by making a copy with the string “.backup” appended to the name:

// FileCopy - make backup copies of the files passed
// to the program
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
using namespace std;

int main(int nNumberofArgs, char* pszArgs[])
{
// repeat the process for every file passed
for (int n = 1; n < nNumberofArgs; n++)
{
// create a filename and a ".backup" name
string szSource(pszArgs[n]);
string szTarget = szSource + ".backup";

// now open the source for reading and the
// target for writing
ifstream input(szSource.c_str(),
ios_base::in|ios_base::binary);

ofstream output(szTarget.c_str(),
ios_base::out|ios_base::binary|ios_base::trunc);
if (input.good() && output.good())
{
cout << "Backing up " << szSource << "...";

// read and write 4k blocks until either an
// error occurs or the file reaches EOF
while(!input.eof() && input.good())
{
char buffer[4096];
input.read(buffer, 4096);
output.write(buffer, input.gcount());
}
cout << "finished" << endl;
}
else
{
cerr << "Couldn't copy " << szSource << endl;
}
}

cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

The program iterates through the arguments passed to it, remembering that pszArgs[0] points to the name of the program itself. For every source file passed as an argument, the program creates the target filename by tacking “.backup” onto the end. It then opens the source file for binary input and the target for binary output, specifying to truncate the target file if it already exists.

If either the input or output object has an error set, the program outputs a “Couldn't copy” message without attempting to figure out what went wrong. If both objects are good(), however, the program enters a loop in which it reads 4K blocks from the input and writes them out to theoutput.

Notice that in the call to write(), the program uses the value returned from gcount() rather than hardcoding 4096. This is because, unless the source file just happens to be an integer multiple of 4096 bytes in length, the last call to read() will fetch less than the requested number of bytes before encountering end-of-file.

Controlling format

The flags(), setf(), and unsetf() methods are all used to set or retrieve a set of format flags maintained within the istream or ostream object. These format flags get set when the object is created to a default value that represents the most common format options. The options are shown in Table 23-4.

Table 23-4 The I/O Stream Format Flags

Flag

If flag is true, then …

boolalpha

Displays bool as either true or false rather than 1 or 0.

dec

Reads or writes integers in decimal format (default).

fixed

Displays floating point in fixed point as opposed to scientific (default).

hex

Reads or writes integers in hexadecimal.

left

Displays output left justified (i.e., pads on the right).

oct

Reads or writes integers in octal.

right

Displays output right justified (i.e., pads on the left).

scientific

Displays floating point in scientific format.

showbase

Displays a leading 0 for octal output and leading 0x for hexadecimal output.

showpoint

Displays a decimal point for floating-point output even if the fractional portion is 0.

skipws

Skips over whitespace when reading using the extractor.

unitbuf

Flushes output after each output operation.

uppercase

Replaces lowercase letters with their uppercase equivalents on output.

The following code segment has been used in the past to display numbers in hexadecimal format (see the BitTest program in Chapter 4):

// read the current format flags
// (this is important when you need to restore the output
// format at a later time)
ios_base::fmtflags prevValue = cout.flags();

// clear the decimal flag
cout.unsetf(cout.dec);

// now set the hexadecimal flag
cout.setf(cout.hex);

// ...do stuff..

// call flags() to restore the format flags to their
// previous value
cout.flags(prevValue);

In this example, the program must both set the hexadecimal flags using setf() and unset (that is, clear) the decimal flag using unsetf() because the decimal, octal, and hexadecimal flags are mutually exclusive.

The final call to flags() restores the format flags to their previously read value. This is not necessary if the program is about to terminate anyway.

Further format control is provided by the width() method that sets the minimum width of the next output operation. In the event that the field does not take up the full width specified, the inserter adds the requisite number of fill characters. The default fill character is a space, but you can change this by calling fill(). Whether C++ adds the fill characters on the left or right is determined by whether the left or right format flag is set.

For example, the following segment

int i = 123;
cout.setf(cout.right);
cout.unsetf(cout.left);
cout.fill('+');
cout << "i = [";
cout.width(10);
cout << i;
cout << "]" << endl;

generates the following output:

i = [+++++++123]

image Notice that the width() method applies only to the very next output statement. Unlike the other formatting flags, the width() must be reset after every value that you output.

What's up with endl?

Most programs in this book terminate an output stream by inserting the object endl. However, some programs include \n within the text to output a newline. What's the deal?

The \n is, in fact, the newline character. The expression cout << “First line\nSecond line; outputs two lines. The endl object outputs a newline, but continues one step further.

Disks are slow devices. Writing to disk more often than necessary will slow down your program considerably. To avoid this, the fstream class collects output into an internal buffer known as a cache (pronounced like “cash”). The class writes the contents to disk when the buffer is full (this is known as flushing the cache). The endl object outputs a newline and then flushes the output cache. The member function flush() flushes the output cache without tacking a newline onto the end.

Note that the standard error object cerr does not buffer output.

Positioning the pointer within a file

The istream class maintains a read pointer that is the location within the file of the next byte to read. This is measured as “number of bytes from the beginning of the file.” You can retrieve this using the tellg() method. (Similarly, the tellp() returns a pointer to the next location to write in an ostream object.) Having saved off the location, you can later return to the same location by passing the value to seekg().

An overloaded version of seekg() takes not an absolute position but an offset and a seek direction. The legal value for the seek direction is one of the following three constants:

· ios_base::beg (beg for beginning of file): The offset must be positive and is taken to be the number of bytes from the beginning of the file.

· ios_base::end (end for end of file): The offset must be negative and is taken to be the number of bytes from the end of the file.

· ios_base::cur (cur for current position): The offset can be either positive or negative and is the number of bytes to move the pointer (either forward or backward) from its current position.

Moving the read (or write) pointer around in a file can be very slow (in computer terms), so be judicious in the use of this feature.

Using the stringstream Subclasses

The stream classes give the programmer mechanisms for easily breaking input among int, float, and char array variables (among others). A set of so-called stringstream classes allow the program to read from an array of characters in memory as if it were reading from a file. The classesistringstream and ostringstream are defined in the include file sstream.

image The older versions of these classes are istrstream and ostrstream defined in the include file strstream.

The stringstream classes have the same semantics as the corresponding file-based classes. This is demonstrated in the following StringStream program, which parses account information from a file:

// StringStream - read and parse the contents of a file
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <sstream>
#include <iostream>
using namespace std;

// parseAccountInfo - read a passed buffer as if it were
// an actual file - read the following
// format:
// name, account balance
// return true if all worked well
bool parseString(const char* pString,
char* pName, int arraySize,
long& accountNum, double& balance)
{
// associate an istrstream object with the input
// character string
istringstream inp(pString);

// read up to the comma separator
inp.getline(pName, arraySize, ',');

// now the account number
inp >> accountNum;

// and the balance
inp >> balance;

// return the error status
return !inp.fail();
}

int main(int nNumberofArgs, char* pszArgs[])
{
// must provide filename
char szFileName[128];
cout << "Input name of file to parse:";
cin.getline(szFileName, 128);

// get a file stream
ifstream* pFileStream = new ifstream(szFileName);
if (!pFileStream->good())
{
cerr << "Can't open " << pszArgs[1] << endl;
return 0;
}

// read a line out of file, parse it and display
// results
for(int nLineNum = 1;;nLineNum++)
{
// read a buffer
char buffer[256];
pFileStream->getline(buffer, 256);
if (pFileStream->fail())
{
break;
}
cout << nLineNum << ":" << buffer << endl;

// parse the individual fields
char name[80];
long accountNum;
double balance;
bool result = parseString(buffer, name, 80,
accountNum, balance);
if (result == false)
{
cerr << "Error parsing string\n" << endl;
continue;
}

// output the fields we parsed out
cout << "Read the following fields:" << endl;
cout << " name = " << name << "\n"
<< " account = " << accountNum << "\n"
<< " balance = " << balance << endl;

// put the fields back together in a different
// order (inserting the 'ends' makes sure the
// buffer is null terminated
ostringstream out;
out << name << ", "
<< balance << " "
<< accountNum << ends;

string oString = out.str();
cout << "Reordered fields: " << oString << endl;
}

cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

This program begins by opening a file called Accounts.txt containing account information in the format of name, accountNumber, balance,\n. Assuming that the file was opened successfully, the program enters a loop, reading lines until the contents of the file are exhausted. The call to getline() reads up to the default newline terminator. The program passes the line just read to the function parseString().

parseString() associates an istringstream object with the character string. The program reads characters up to the ',' (or the end of the string buffer) using the getline() member function. The program then uses the conventional extractors to read accountNum and balance.

After the call to parseString(), main() outputs the buffer read from the file followed by the parsed values. It then uses the ostringstream class to reconstruct a string object with the same data but a different format (just for the fun of it).

The result from a sample execution appears as follows:

Input name of file to parse:Accounts.txt
1:Chester, 12345 56.60
Read the following fields:
name = Chester
account = 12345
balance = 56.6
Reordered fields: Chester, 56.6 12345
2:Arthur, 34567 67.50
Read the following fields:
name = Arthur
account = 34567
balance = 67.5
Reordered fields: Arthur, 67.5 34567
3:Trudie, 56x78 78.90
Error parsing string

4:Valerie, 78901 89.10
Read the following fields:
name = Valerie
account = 78901
balance = 89.1
Reordered fields: Valerie, 89.1 78901
Press Enter to continue ...

Reflect a second before continuing. Notice how the program was able to resync itself after the error in the input file. Notice, also, the simplicity of the heart of the program, the parseString() function. Consider what this function would look like without the benefit of the istringstreamclass.

Manipulating Manipulators

You can use stream I/O to output numbers and character strings by using default formats. Usually the defaults are fine, but sometimes they don’t cut it.

For example, I was less than tickled when the total from the result of a financial calculation from a recent program appeared as 249.600006 rather than 249.6 (or, better yet, 249.60). There must be a way to bend the defaults to my desires. True to form, C++ provides not one but two ways to control the format of output.

image Depending on the default settings of your compiler, you may get 249.6 as your output. Nevertheless, you really want 249.60.

First, you can control the format by invoking a series of member functions on the stream object. For example, the number of significant digits to display is set by using the function precision() as follows (see Table 23-3):

#include <iostream>
void fn(double interest, double dollarAmount)
{
cout << "Dollar amount = ";
cout.precision(2);
cout << dollarAmount;
cout.precision(4);
cout << interest << endl;
}

In this example, the function precision() sets the precision to 2 immediately before outputting the value dollarAmount. This gives you a number such as 249.60, the type of result you want. It then sets the precision to 4 before outputting the interest.

A second approach uses what are called manipulators. (Sounds like someone behind the scenes of the New York Stock Exchange, doesn’t it?) Manipulators are objects defined in the include file iomanip to have the same effect as the member function calls. (You must include iomanip to have access to the manipulators.) The only advantage to manipulators is that the program can insert them directly into the stream rather than resort to a separate function call.

The most common manipulators and their corresponding meanings are shown in Table 23-5.

Table 23-5 Common Manipulators and Stream Format Control Functions

Manipulator

Member Function

Description

dec

setf(dec)

Sets radix to 10

hex

setf(hex)

Sets radix to 16

oct

setf(oct)

Sets radix to 8

setfill(c)

fill(c)

Sets the fill character to c

setprecision(n)

precision(n)

Sets display precision to n

setw(n)

width(n)

Sets width of field to n characters*

* This returns to its default value after the next field is output.

If you rewrite the preceding example to use manipulators, the program appears as follows:

#include <iostream>
#include <iomanip>
void fn(double interest, double dollarAmount)
{
cout << "Dollar amount = "
<< setprecision(2) << dollarAmount
<< setprecision(4) << interest << endl;