C Archives

Chapter 18 - Complete I/O in C++

Visual C++ 6: The Complete Reference
Chris H. Pappas and William H. Murray, III
  Copyright 1998 The McGraw-Hill Companies

Designing Unique Manipulators
The concept of stream manipulators was first introduced in Chapter 12. Manipulators are used with the insertion, <<, and extraction, >>, operators, exactly as if they represented data for output or variables to receive input. As the name implies, however, manipulators can carry out arbitrary operations on the input and output streams.
Several of the example programs used the built-in manipulators dec, hex, oct, setw, and setprecision. Now you will learn how to write your own custom manipulators. To gradually build your understanding of the syntax necessary to create your own manipulators, the example programs begin with the simplest type of manipulator, one with no parameters, and then move on to ones with parameters.
Manipulators Without Parameters
A custom manipulator can be created anytime to repeatedly insert the same character sequence into the output stream. For example, maybe your particular application needs to flag the user to an important piece of data. You may even want to beep the speaker to get the user’s attention just in case he or she isn’t looking directly at the monitor. Without custom manipulators, your output statements would look like this:
cout << ‘\a’ << “\n\n\t\tImportant data: ”
    << fcritical_mass << endl;
Every time you wanted to grab the user’s attention, you would repeat the bell prompt, ‘\a’, and the “...Important data: ” string. An easier approach is to define a manipulator, called beep, that automatically substitutes the desired sequence. The beep manipulator also makes the statement easier to read:
cout << beep << fcritical_mass << endl;
The following program demonstrates how to define and use the beep( ) function:
//
//  beep.cpp
//  C++ program demonstrates how to create your own
//  non-parameterized manipulator
//  Copyright (c) Chris H. Pappas and William H. Murray, 1998
//

#include <iostream.h>

ostream& beep(ostream& os) {
 return os << ‘\a’ << “\n\n\t\t\tImportant data: ”;
}

void main(void)
{
double fcritical_mass = 12459876.12;

cout << beep << fcritical_mass;
}
The globally defined beep( ) function uses an ostream& formal parameter and returns the same ostream&. beep( ) works because it is automatically connected to the stream’s << operator. The stream’s insertion operator, <<, is overloaded to accept this kind of function with the following inline function:
Inline ostream& ostream::operator<<(ostream& (*f)(ostream&)) {
 (*f)(*this);
 return *this;
}
The inline function associates the << operator with the custom manipulator by accepting a pointer to a function passed an ostream& type and that returns the same. This is exactly how beep( ) is prototyped. Now when << is used with beep( ), the compiler dereferences the overloaded operator, finding where function beep( ) sits, and then executes it. The overloaded operator returns a reference to the original ostream. Because of this, you can combine manipulators, strings, and other data with the << operators.
Manipulators with One Parameter
The iostream class library, prototyped in IOMANIP.H, defines a special set of macros for creating parameterized macros. The simplest parameterized macro you can write accepts either one int or long parameter.
The following application shows a prototype for such a manipulator, fc. The example program demonstrates the syntax necessary to create a single-parameter custom manipulator:
//
//  manip1.cpp
//  C++ program demonstrating how to create and use
//  one-parameter custom manipulators.
//  Copyright (c) Chris H. Pappas and William H. Murray, 1998
//

#include <iostream.h>
#include <iomanip.h>
#include <string.h>
#define iSCREEN_WIDTH 80

ostream& fc(ostream& os, int istring_width)
{
 os << ‘\n’;
 for(int i=0; i < ((iSCREEN_WIDTH - istring_width)/2); i++)
   os << ‘ ‘;
 return(os);
}

OMANIP(int) center(int istring_width)
{
 return OMANIP(int) (fc, istring_width);
}

void main(void)
{
 char
*psz = “This is auto-centered text!”;
 cout << center(strlen(psz)) << psz;
}
The center custom-parameterized manipulator accepts a single value, strlen(psz), representing the length of a string. IOMANIP.H defines a macro, OMANIP(int), and expands into the class __OMANIP_int. The definition for this class includes a constructor and an overloaded ostream insertion operator. When function center( ) is inserted into the stream, it calls the constructor that creates and returns an __OMANIP_int object. The object’s constructor then calls the fc( ) function.
Manipulators with Multiple Parameters
The next example should be familiar. Actually, it is the same code (sqrt.cpp) seen earlier in the chapter to demonstrate how to format numeric output. However, the program has been rewritten using a two-parameter custom manipulator to format the data.
The first modification to the program involves a simple structure definition to hold the format manipulator’s actual parameter values:
struct stwidth_precision {
 int iwidth;
 int iprecision;
};
When you create manipulators that take arguments other than int or long, you must use the IOMANIPdeclare macro. This macro declares the classes for your new data type. The definition for the format manipulator begins with the OMANIP macro:
OMANIP(stwidth_precision) format(int iwidth, int iprecision)
{
 stwidth_precision stWidth_Precision;
 stWidth_Precision.iwidth = iwidth;
 stWidth_Precision.iprecision = iprecision;
 return OMANIP (stwidth_precision)(ff, stWidth_Precision);
}
In this example, the custom manipulator is passed two integer arguments, iwidth and iprecision. The first value defines the number of spaces to be used by format, and the second value specifies the number of decimal places. Once format has initialized the stWidth_Precision structure, it calls the constructor, which creates and returns an __OMANIP object. The object’s constructor then calls the ff( ) function, which sets the specified parameters:
static ostream& ff(ostream& os, stwidth_precision
                  stWidth_Precision)
{
 os.width(stWidth_Precision.iwidth);
 os.precision(stWidth_Precision.iprecision);
 os.setf(ios::fixed);
 return os;
}
The complete program follows. All of the code replaced by the call to format has been left in the listing for comparison. Notice how the format custom manipulator streamlines each output statement.
//
//  manip2.cpp
//  This C++ program is the same as sqrt.cpp, except
//  for the fact that it uses custom parameterized
//  manipulators to format the output.
//  A C++ program that prints a table of
//  numbers, squares, and square roots for the
//  numbers from 1 to 15. Program uses the type
//  double. Formatting aligns columns, pads blank
//  spaces with ‘0’ character, and controls
//  precision of answer.
//  Copyright (c) Chris H. Pappas and William H. Murray, 1998
//
#include <iostream.h>
#include <iomanip.h>
#include <math.h>

struct stwidth_precision {
 int iwidth;
 int iprecision;
};

IOMANIPdeclare(stwidth_precision);

static ostream& ff(ostream& os, stwidth_precision
                  stWidth_Precision)
{
 os.width(stWidth_Precision.iwidth);
 os.precision(stWidth_Precision.iprecision);
 os.setf(ios::fixed);
 return os;
}
OMANIP(stwidth_precision) format(int iwidth, int iprecision)
{
 stwidth_precision stWidth_Precision;
 stWidth_Precision.iwidth = iwidth;
 stWidth_Precision.iprecision = iprecision;
 return OMANIP (stwidth_precision)(ff, stWidth_Precision);
}

main( )
{
 double number,square,sqroot;

 cout << “num\t” << “square\t\t” << “square root\n”;
 cout << “____________________________________\n”;

 number=1.0;

//cout.setf(ios::fixed);       // use fixed format
 for(int i=1;i<16;i++) {
   square=number*number;      // find square
   sqroot=sqrt(number);       // find square root
   cout.fill(‘0’);            // fill blanks with zeros
//  cout.width(2);             // column 2 characters wide
//  cout.precision(0);         // no decimal place
   cout << format(2,0) << number << “\t”;

//  cout.width(6);             // column 6 characters wide
//  cout.precision(1);         // print 1 decimal place
   cout << format(6,1) << square << “\t\t”;

//  cout.width(8);             // column 8 characters wide
//  cout.precision(6);         // print 6 decimal places
   cout << format(8,6) << sqroot << endl;

   number+=1.0;
 }
 return (0);
}
With the discussion of advanced C++ object-oriented I/O completed, you are ready to tackle object-oriented design philosophies. Chapter 19 explains how important good class design is to a successful object-oriented program solution.

Books24x7.com, Inc 2000 –  


Visual C++ 6(c) The Complete Reference
Visual Studio 6: The Complete Reference
ISBN: B00007FYGA
EAN: N/A
Year: 1998
Pages: 207

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net