home *** CD-ROM | disk | FTP | other *** search
Text File | 1991-07-11 | 116.6 KB | 4,134 lines |
- This tutorial file is the second version of the notes that I wrote to
- attempt to explain how input and output are done in Borland C++ using the "new"
- streaming methods. (The first version was posted last January.)
-
- The tutorial consists of 5 chapters:
-
- 1) Header file
- 2) Output
- 3) Input
- 4) Manipulators
- 5) File I/O
-
- ============================================================================
- ============================================================================
-
- CHAPTER 1 -- Header File
-
- All C++ program examples in this tutorial include a file called "header.h".
- Within this file are all of the system header files and other information
- needed to compile any particular program example from these chapters as well
- as other chapters. Obviously having all of these files slows down the
- compilation time, but it's not really noticeable if you are using the
- pre-compiled header option.
-
- A listing of the header file follows:
-
- /*
- My private header file for all C++ and C programs.
-
- Note this define:
-
- OBJECT_FILE -- ON if a program is being compiled normally
- using Borland C++. This prevents the code in the
- header file from being compiled (which would negate
- the whole purpose of using the pre-compiled header
- option)
-
- OFF when it's only necessary to generate the .OBJ
- file for Borland C++ using a pseudo compilation. This
- .OBJ file will then be used by the linker.
- */
-
- #ifndef HEADER_H
- #define HEADER_H
- #endif
-
- #include <fstream.h>
- #include <iomanip.h>
- #include <strstream.h>
- #include <new.h>
- #include <alloc.h>
- #include <conio.h>
- #include <stdio.h>
- #include <string.h>
- #include <ctype.h>
- #include <stdlib.h>
- #include <math.h>
- #include <time.h>
- #include <limits.h>
- #include <dos.h>
-
- //////////////////////////////////
- // My private defines
-
- #define FALSE 0
- #define TRUE !FALSE
- #define AND &&
- #define OR ||
- #define NO 0
- #define YES !NO
- #define EQUALS ==
- #define IS_EQUAL_TO ==
- #define NOT !
- #define IS_NOT_EQUAL_TO !=
- #define NOT_EQUAL_TO !=
- //
- #define BLANK ' '
- #define SPACE ' '
- #define ASTERISK '*'
- #define DECIMAL '.'
- #define NEW_LINE '\n'
- #define NEWLINE '\n'
- #define NUL '\0'
- #define TAB '\t'
- #define BACKSPACE '\b'
- #define BEEP '\a'
- #define FORMFEED '\f'
- #define RETURN '\r'
- #define SINGLE_QUOTE '\''
- #define SINGLEQUOTE '\''
- #define DOUBLE_QUOTE '\"'
- #define DOUBLEQUOTE '\"'
- #define BACK_SLASH '\\'
- #define BACKSLASH '\\'
-
- // Declarations
-
- void PAUSE() ;
- void IOFLAGS(istream& = cin) ;
- typedef void* POINTER ;
- const char* ADDRESS(POINTER) ;
- extern ofstream POUT ;
- void FORMATFLAGS(ostream& = cout) ;
- void _NEW_HANDLER() ;
- void SET_NEW_HANDLER() ;
- istream& FLUSH(istream& strm) ;
- ostream& PRINTER(ostream&) ;
- ostream& SCREEN(ostream&) ;
- ostream& LEFT(ostream& str) ;
- ostream& RIGHT(ostream& str) ;
- ostream& INTERNAL(ostream& str) ;
- ostream& SHOWPOINT(ostream& str) ;
- ostream& SHOWBASE(ostream& str) ;
- ostream& NOSHOWBASE(ostream& str) ;
- ostream& FIXED(ostream& str) ;
- ostream& SCIENTIFIC(ostream& str) ;
- ostream& UPPERCASE(ostream& str) ;
- ostream& LOWERCASE(ostream& str) ;
- ostream& SHOWPOS(ostream& str) ;
- ostream& NOSHOWPOS(ostream& str) ;
-
- /////////////////////////////////////////////////////////////////
- // This part begins the actual code that cannot be part of the pre-compiled
- // headers that Borland C++ supports. For normal compilations the define
- // OBJECT_FILE must be ON
- /////////////////////////////////////////////////////////////////
-
- #ifndef OBJECT_FILE
-
- // My private PAUSE function
- void PAUSE()
- {
- cout << "Press any key to continue...\n" ;
- getch() ;
- }
-
- // This function prints the various I/O flags of an input stream. Defaults
- // to 'cin'.
-
- void IOFLAGS(istream& stream)
- {
- cout << "eof state is " << (stream.eof() ? "ON" : "OFF") << "\n" ;
- cout << "good state is " << (stream.good() ? "ON" : "OFF") << "\n" ;
- cout << "fail state is " << (stream.fail() ? "ON" : "OFF") << "\n" ;
- cout << "bad state is " << (stream.bad() ? "ON" : "OFF") << "\n" ;
- cout << "if(!instance) returns " << (!stream ? "TRUE" : "FALSE") << "\n" ;
- cout << "if(instance) returns " << (stream ? "TRUE" : "FALSE") << "\n" ;
- }
-
- // A function to print an address in segment:offset form for Borland C++.
- // CAUTION: Do NOT call this function more than oncein a single statement, e.g.,
-
- // int n1 , n2 ;
- // cout << ADDRESS(&n1) << endl << ADDRESS(&n2) << endl ;
-
- const char* ADDRESS(POINTER add)
- {
- static char buffer[15] ;
- ostrstream output(buffer , sizeof buffer) ;
- output.fill('0') ;
- output << setw(4) << FP_SEG(add) << ':'
- << setw(4) << FP_OFF(add) << ends ;
- return buffer ;
- }
-
- // Functions to handle an out-of-memory condition
-
- void _NEW_HANDLER()
- {
- cout << "MEMORY ALLOCATION ERROR\n" ;
- cout << coreleft() << " bytes remaining\n" ;
- PAUSE() ;
- cout << "Returning to DOS...\n" ;
- exit(1) ;
- }
-
- void SET_NEW_HANDLER()
- {
- _new_handler = _NEW_HANDLER ;
- }
-
- // This function prints the format flags
- // of an output stream. The default is
- // "cout"
-
- void FORMATFLAGS(ostream& stream)
- {
- static char* message[] =
- {
- "skipsw" ,
- "left" ,
- "right" ,
- "internal" ,
- "dec" ,
- "oct" ,
- "hex" ,
- "showbase" ,
- "showpoint" ,
- "uppercase" ,
- "showpos" ,
- "scientific" ,
- "fixed" ,
- "unitbuf" ,
- "stdio"
- } ;
-
- cout << "FORMAT FLAGS\n" ;
- long f = stream.flags() ;
- for(int i = 0 ; i < 16 ; ++i)
- {
- if(f & 0x0001)
- cout << message[i] << "\n" ;
- f >>= 1 ;
- }
- }
-
- // My private flush-the-input-buffer manipulator
-
- istream& FLUSH(istream& strm)
- {
- // Reset the error state
- strm.clear() ;
-
- // Two different types of flushing
- // are possible: keyboard and file
-
- // If the keyboard is being used,
- // simply flush all characters
- if(&strm EQUALS &cin)
- {
- // Find out how many characters remain in the stream
- int remain = strm.rdbuf()->in_avail() ;
- while(remain--)
- strm.get() ;
- }
-
- // If a disk file is being used, flush while characters remain or
- // until '\n' or EOF is found
- else
- {
- char ch ;
- while(strm.get(ch) AND ch != '\n' AND ch != EOF)
- continue ;
- }
-
- // Reset the error state again
- strm.clear() ;
-
- return strm ;
- }
-
- // Define a global instance of the class 'ofstream' called POUT that
- // can be tied to a DOS printer
-
- ofstream POUT("prn") ;
-
- // A manipulator that directs to the printer all subsequent output for
- // the one statement in which it is found
-
- ostream& PRINTER(ostream&)
- {
- return POUT ;
- }
-
- // A manipulator that directs to the screen all subsequent output for
- // the one statement in which it is found
-
- ostream& SCREEN(ostream&)
- {
- return cout ;
- }
-
- // A manipluator to set left justification
-
- ostream& LEFT(ostream& str)
- {
- str.setf(ios::left , ios::adjustfield) ;
- return str ;
- }
-
- // A manipluator to set right justification
-
- ostream& RIGHT(ostream& str)
- {
- str.setf(ios::right , ios::adjustfield) ;
- return str ;
- }
-
- // A manipluator to set internal justification
-
- ostream& INTERNAL(ostream& str)
- {
- str.setf(ios::internal , ios::adjustfield) ;
- return str ;
- }
-
- // A manipulator to show the decimal point
-
- ostream& SHOWPOINT(ostream& str)
- {
- str.setf(ios::showpoint) ;
- return str ;
- }
-
- // A manipulator to show the base on hex and octal output
-
- ostream& SHOWBASE(ostream& str)
- {
- str.setf(ios::showbase) ;
- return str ;
- }
-
- // A manipulator to suppress the showing of the base
-
- ostream& NOSHOWBASE(ostream& str)
- {
- str.unsetf(ios::showbase) ;
- return str ;
- }
-
- // A manipulator to show fixed point output
-
- ostream& FIXED(ostream& str)
- {
- str.setf(ios::fixed , ios::floatfield) ;
- return str ;
- }
-
- // A manipulator to show scientific point output
-
- ostream& SCIENTIFIC(ostream& str)
- {
- str.setf(ios::scientific , ios::floatfield) ;
- return str ;
- }
-
- // A manipulator to show uppercase output on hex and scientific numbers
-
- ostream& UPPERCASE(ostream& str)
- {
- str.setf(ios::uppercase) ;
- return str ;
- }
-
- // A manipulator to show lowercase output on hex and scientific numbers
-
- ostream& LOWERCASE(ostream& str)
- {
- str.unsetf(ios::uppercase) ;
- return str ;
- }
-
- // A manipulator to show a '+' on positive numbers
-
- ostream& SHOWPOS(ostream& str)
- {
- str.setf(ios::showpos) ;
- return str ;
- }
-
- // A manipulator to negate showing a '+' on positive numbers
-
- ostream& NOSHOWPOS(ostream& str)
- {
- str.unsetf(ios::showpos) ;
- return str ;
- }
-
- #endif
-
- ============================================================================
- ============================================================================
-
- CHAPTER 2 -- Output
-
-
- Introduction
-
- Whenever data gets sent to a text output device, it takes on the form
- of a stream of characters. For example, the floating point number 1.234 is
- not stored internally as 5 characters ('1' , '.' , '2' , '3' , '4'), but
- rather in a special format designed to accommodate floating point values.
- In character format, this value is meaningless. Yet, if you were to print
- this number, you certainly would want to have the aforementioned 5
- characters appear on your output device.
-
- The C++ input/output mechanism provided with AT&T release 2.0 provides
- a series of classes that have been created to handle the problem of
- sending and receiving data. This mechanism consists of many classes, some
- derived from others, and some contained within others. All of these
- classes are contained within a library caled iostream, and can be
- accessed by your program by writing:
-
- #include <iostream.h>
-
- A good explanation of how streams operate comes from Bryan Flamig,
- Turbo C++, A Self-Teaching Guide, page 326:
-
- Even though the stdio library provides for stream-oriented I/O, the
- iostream library takes advantage of the powerful objected-oriented
- features of C++, and implements the stream I/O in a manner closer to the
- conceptual stream model.
-
- For example, consider the following call to the stdio printf() routine,
- which inserts some data into the stdin stream:
-
- int x = 42 ;
- printf ("The answer is:%d" , x) ;
-
- With calls like this to printf(), you still think in terms of procedural
- programming. It's hard to conceptualize the stream model here. There are
- really two pieces of data being written, a character string "The answer
- is:", and the number 42. Yet the output of these pieces is lumped
- together into one function call.
-
- In contrast, with the iostream library, you think in terms of stream
- objects, and operators acting upon those objects. You read and write to
- stream objects piece by piece. For instance, the following code shows how
- to generate the equivalent output for our example, using the iostream
- library:
-
- int x = 42 ;
- cout << "The answer is: " << x ;
-
- In this statement, cout is a stream object that's analogous to stdout.
- The left-shift operator << has been overloaded, and is used to insert
- data into the stream. The direction of the arrow suggests the direction
- of data flow into the stream object: first the character string is
- inserted, and then the number 42.
-
- ==========================================================
-
- How to do simple output
-
- At the lowest level, the most fundamental operation is to manipulate
- the characters within some buffer, also known as a stream. This is done by
- the class streambuf. There are two other classes derived from
- streambuf: filebuf, which handles file-specific buffer operations, and
- strstreambuf, which performs in-memory I/O buffering.
-
- Because input and output must be formatted to be legible, another class
- called ios contains functions and data to handle this task. When an
- instance of the class ios comes into existence, it receives a pointer to
- some streambuf area. To send the actual formatted output to the user,
- another class called ostream (which is derived from ios) is used.
-
- Classes, like built-in (scalar) types, do not occupy any memory. What is
- needed, then, is an instance, or object, of that class type. In the
- hierarchy of derivation, the last class, ostream, is the one that used to
- create the instance, as follows:
-
- ostream cout ;
-
- Since the instance cout is an instance of a class that has been
- derived from parent classes, by definition it has inherited all of the
- data members of its parent classes, and has all of the functionality of
- those classes. Also, since this instance is defined at global scope, your
- program has unlimited access to it at all times. Output operations are
- initiated using the instance cout.
-
- In addition to data members, classes can contain functions to operate
- upon these members. In the class ostream, the function you will use the
- most often is called the insertion operator, and its name is operator<<.
- The argument to this function is the data item that you wish to output.
- The name ╥insertion╙ comes from the fact that you are ╥inserting╙ items
- into an output buffer.
-
- In a manner similar to that of accessing a function member of any user-
- defined class, function members of class ostream are also accessed using
- the direct member operator (the dot operator). This is done by first
- writing the instance, followed by the dot operator, and then the function
- name with any arguments enclosed within parentheses. For example, to
- output a simple message, you would code:
-
- cout.operator<<("THE ANSWER IS ") ;
-
- where "THE ANSWER IS " is the argument to the function operator<<.
- Similarly, to output the number 65, you would code:
-
- cout.operator<<(65) ;
-
- To output a new-line character, you would code:
-
- cout.operator<<('\n') ;
-
- or, if you wish, a string containing nothing but a new-line character:
-
- cout.operator<<("\n") ;
-
- If you really think about it, you should be asking the obvious
- question, how can a function take a single argument of different types?
- In other words, in the first call above a string (type char*) was passed
- as the argument, in the second call an integer (type int) was passed, and
- in the third a character (type char) was passed. How can this be? The
- answer is that the function operator<< has been overloaded, so that many
- versions of the same function exist within the class ostream. The
- compiler is smart enough to distinguish one version from another (through
- a process known as argument matching), so that your function call
- correctly accesses the function specifically written to handle the type of
- argument that you provide.
-
- Concatenating function calls
-
- The next obvious question you should be asking is whether you have to
- code a series of such function calls if more than one data item is to be
- output. In other words, in the examples above, are three separate function
- calls really necessary? Fortunately, the answer is no. The way the
- operator<< function is written, a reference (address) to the calling
- instance (cout) is returned by the function, and can therefore be used as
- the calling instance for a concatenated function call. That is, the three
- function calls can be written:
-
- cout.operator<<("THE ANSWER IS ").operator<<(65).operator<<('\n') ;
-
- That's the good news. The bad news is that it's still too much coding
- and still too awkward. With this in mind, the designers of C++ allow the
- programmer to abbreviate the notation:
-
- cout.operator<<(argument1) ;
-
- with the more convenient:
-
- cout << argument1 ;
-
- and the notation:
-
- cout.operator<<(argument1).operator<<(argument2) ;
-
- with the more convenient:
-
- cout << argument1 << argument2 ;
-
- As you can see, the dot operator has been eliminated, and the function
- operator<<() has been replaced with just the insertion operator <<.
-
- Note that the operator << in this context is the same operator that is
- used to shift bits to the left. This illustrates the C++ technique of
- operator overloading. Fortunately, the compiler infers the proper usage
- from the context in which the operator is located. In other words,
-
- cout << 1 ; // Output the constant 1 to the screen
- number << 1 ; // Shift 'number' 1 bit to the left
-
- Since white space in your program is ignored by the compiler, for
- stylistic purposes you could write each insertion operator on its own
- line. Try running this program.
-
- // EXAMPLE OUTPUT-01
-
- #include <header.h>
-
- int main()
- {
- cout << "THE ANSWER IS "
- << 65
- << "\n" ;
-
- return 0 ;
- }
-
- ==========================================================
-
- Note that only one such occurrence of cout needed to be written until the
- end of the statement is reached.
-
- Of course, you could restrict each statement to having just one
- insertion operator. This program produces exactly the same output as
- before.
-
- // EXAMPLE OUTPUT-02
-
- #include <header.h>
-
- int main()
- {
- cout << "THE ANSWER IS " ;
- cout << 65 ;
- cout << "\n" ;
-
- return 0 ;
- }
-
- ==========================================================
-
- In addition to a string, an integer, and a character, the function
- operator<< has been overloaded to accept arguments of type long, float,
- double, pointer, and so forth. In other words, all of the primitive types
- can easily be output.
-
- When overloading a built-in operator, you cannot change its precedence,
- and since the operator << has lower precendence than the arithmetic
- operators, statements such as:
-
- cout << 1 + 2 ;
- cout << 2 * 3 ;
- cout << number++ ;
-
- pose no problem because the arithmetice is done first. However, a fragment
- such as:
-
- cout << 123 ? 1 : 0 ;
-
- will output the number 123 instead of the number 1 because the line is
- interpreted as:
-
- (cout << 123) ? 1 : 0 ;
-
- since << has higher precedence than the conditional operator. Then the
- conditional operator is executed, but the result doesn't do anything
- useful.
-
- Another place to go wrong is with the use of function calls embedded in
- a series of calls to operator<<. There is no guarantee of the order of
- evaluation of items within an expression. Normally this does not pose a
- problem, unless the function affects the state of the output stream.
-
- // EXAMPLE OUTPUT-03
-
- // CAUTION: DON'T USE FUNCTION
- // CALLS THAT AFFECT THE STATE
- // OF THE OUTPUT STREAM
-
- #include <header.h>
-
- int f1()
- {
- cout << "Item 1\n" ;
- return 1 ;
- }
-
- int f2()
- {
- cout << "Item 2\n" ;
- return 2 ;
- }
-
- int f3()
- {
- cout << "Item 3\n" ;
- return 3 ;
- }
-
- ///////////////////////////////////
-
- int main()
- {
- cout << f1() << '\n'
- << f2() << '\n'
- << f3() << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- Item 3
- Item 2
- Item1
- 1
- 2
- 3
-
- ==========================================================
-
- Bit format flags
-
- Now think about a printf() function call. It usually consists of a
- control string argument and, optionally, a list of expressions to be
- output. The control string contains literals which will be output exactly
- as shown, and conversion specifications that indicate exactly how an
- expression from the list of expressions is to appear. Each conversion
- specification starts with a % and ends with a conversion character, e.g.,
- d for decimal format, c for character format, s for a string, etc.
- Between the start and end you may enter various flags, the field width,
- base formatting, justification, floating point precision, and so forth.
- Each conversion specification stands on its own; there is no connection to
- any other one.
-
- In the C++ AT&T version 2.0 stream I/O, all of the characteristics
- relating to how an expression should appear apply, not to each individual
- expression, but to the output stream as a whole. In other words, once you
- specify that decimal format is desired, all integer numbers from that
- point on are output in decimal format. No further action need be taken. If
- you decide to switch to hexadecimal output, then all integer numbers from
- that point on will be shown in their hex formats. The same is true for
- floating point precision. Once it is set, it stays set for all subsequent
- floating point numbers. (There is one important exception to the ╥set it
- and forget it╙ feature of C++ streaming that will be discussed later).
-
- Any binary characteristic of the output stream is stored in a long
- (protected) field in the class ios. In Borland C++ this field is called
- x_flags. (The fact that it's protected means that you cannot access it
- directly, as though it were private.) Each characteristic occupies one
- bit of this field, which simply means that it's either true or false; on
- or off; set or not set. For example, the output state of decimal is either
- on or off. The same can be said for the output states of hexadecimal and
- octal. Also, the state of left-justification is either set or not set, as
- is its opposite, right-justification. On the other hand, stream
- characteristics that require values, such as the field width and floating
- point precision, are stored in (protected) integer variables.
-
- Each binary characteristic is represented by a unique value in a field
- that defines an unnamed public enumerated type in the class ios. Note
- that such enumerated types, while still being within the scope of the
- class, do not necessarily have to be referenced via instances of the
- class. Instead, they may be referenced using explicit qualification, i.e.,
- the class name with the scope resolution operator. The name of the
- enumerated value itself is also local to the class. Each value is
- represented by exactly one bit in the field, and no two fields ever have
- the same bit on. Thus, no two characteristics will ever have the same bit
- position (ranging from 15 down to 0) set on. By ORing these bits into the
- field x_flags, the different states can be set with no conflict. This
- allows someone examining the field x_flags to infer with no ambiguity
- which characteristics are on and which are off.
-
- Each binary characteristic also has a name associated with it that you
- may reference. The complete list of all enumerated values is shown below.
- To repeat: Because these names exist within the class ios, the name of
- this class must be specified in conjunction with the scope resolution
- operator (::) to unambiguously access a specific value. These are the
- names and values that Borland uses:
-
- NAME VALUE MEANING
-
- ios::skipws 0x0001 Skip whitespace on input
- ios::left 0x0002 Left-justification of output
- ios::right 0x0004 Right-justification of output
- ios::internal 0x0008 Pad after sign or base indicator
- ios::dec 0x0010 Show integers in decimal format
- ios::oct 0x0020 Show integers in octal format
- ios::hex 0x0040 Show integers in hexadecimal format
- ios::showbase 0x0080 Show the base for octal and hex numbers
- ios::showpoint 0x0100 Ensure that the decimal point is
- shown for all floating point numbers
- ios::uppercase 0x0200 Show uppercase hex numbers
- ios::showpos 0x0400 Show + for positive numbers
- ios::scientific 0x0800 Use exponential notation on floating point numbers
- ios::fixed 0x1000 Use fixed decimal output on floating point numbers
- ios::unitbuf 0x2000 Flush all streams after insertion
- ios::stdio 0x4000 Flush stdout and stderr after insertion
-
- Because we will be examining these bits very carefully in the coming
- examples, it is very useful to have a function to clearly display the
- status for us. This has been done in the function FORMATFLAGS() which is
- contained in the file header.h. This function takes one argument: an
- instance of an output stream. If no argument is supplied, then the
- function defaults to using the name cout.
-
- Try running this test. Are any of these enumerated bits in the field
- x_flags already on when your program first gets control? This little
- program will provide the answer.
-
- // EXAMPLE OUTPUT-11
-
- // TEST THE DEFAULT OF x_flags
-
- #include <header.h>
-
- int main()
- {
- FORMATFLAGS() ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- FORMAT FLAGS
- skipws
- unitbuf
-
- Therefore, by default white space is skipped when doing input, and all
- streams are automatically flushed after each insertion. (NOTE: Refer to
- the chapter INPUT for an explanation of the skipws bit, and the chapter
- MANIP for an explanation of the unitbuf bit.)
-
- Manipulating the bit format flags
-
- The next item of concern is how to turn these settings on and off.
- Within the class ios there are several member functions provided that
- allow this to be done. The first of these functions is called setf().
- Remember: to call it, you must first specify the instance name, cout, the
- dot member operator, and then the function name. Thus, you would write:
-
- cout.setf( /* arguments */ ) ;
-
- The function setf() has been overloaded to accept either one or two
- arguments. In both cases, the first argument specifies which bits are to
- be turned ON in the field x_flags.
-
- For example, to turn on the ios::dec bit, you would code:
-
- cout.setf(ios::dec) ;
-
- The function setf() works by ORing its first argument into the field
- x_flags, thereby leaving any other bits in this field undisturbed. This
- means that it's possible to turn on more than one bit with just one call
- to setf by using an expression for the first argument that contains
- several bits ORed together. For example, to turn on the ios::dec and the
- ios::right bits, you would code:
-
- cout.setf(ios::dec | ios::right) ;
-
- How can you turn the bits off? Use the member function unsetf(). This
- function takes exactly one argument: the bit pattern to be turned off.
- Thus, to turn off the bit ios::dec, you would code:
-
- cout.unsetf(ios::dec) ;
-
- Like setf(), more than one bit at a time can be turned off by ORing the
- enumerated values together in the argument field.
-
- Unfortunately, the previous method of turning bits on and off is
- awkward because in many cases the bits are mutually exclusive, and it
- would normally take 2 function calls to (1) turn a bit off using
- unsetf(), and (2) turn another bit on using setf(). Fortunately, a
- better way exists by using the setf() function with 2 arguments. In this
- case the second argument represents those specific bits which are to be
- turned OFF just prior to having those bits turned ON that are specified by
- the bitwise AND of the first and second arguments. For example, to turn
- the bit ios::dec ON and ensure that the (mutually exclusive) bits
- ios::oct and ios::hex are OFF, you would code:
-
- cout.setf(ios::dec , ios::dec | ios::oct | ios::hex) ;
-
- To prove this, run this program:
-
- // EXAMPLE OUTPUT-12
-
- // TEST TURNING ON FORMAT FLAG BITS
-
- #include <header.h>
-
- int main()
- {
- cout.setf(ios::oct | ios::hex) ;
- FORMATFLAGS() ;
- cout.setf(ios::dec , ios::dec | ios::oct | ios::hex) ;
- FORMATFLAGS() ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- FORMAT FLAGS
- skipws
- oct
- hex
- unitbuf
- FORMAT FLAGS
- skipws
- dec
- unitbuf
-
- ==========================================================
-
- Because ios::dec, ios::oct and ios::hex are mutually exclusive bit
- fields (that is, you only want one of them on at any time), you would
- normally AND the one bit of the first argument of setf() with the OR of
- all 3 bits, as shown above. The second argument can also be specified as
- ios::basefield, where this value is pre-defined as:
-
- ios::dec | ios::oct | ios::hex.
-
- For example, program OUTPUT-12 can be re-written as:
-
- // EXAMPLE OUTPUT-13
-
- // TEST ios::basefield
-
- #include <header.h>
-
- int main()
- {
- cout.setf(ios::oct | ios::hex) ;
- FORMATFLAGS() ;
- cout.setf(ios::dec , ios::basefield) ;
- FORMATFLAGS() ;
-
- return 0 ;
- }
-
- The output of this program is the same.
-
- ==========================================================
-
- In a similar manner, the field ios::adjustfield represents the bit
- positions of ios::left, ios::right and ios::internal ORed together,
- and the field ios::floatfield represents the bit positions of
- ios::fixed and ios::scientific ORed together.
-
- Because the field x_flags in the class ios is protected, you cannot
- access it directly. However, there is a public member function called
- flags() that will return this field to you. If you provide a long
- integer as an argument to flags(), then the existing value of x_flags
- will be returned to you after your argument is used to provide a new
- setting for x_flags. For example:
-
- // EXAMPLE OUTPUT-14
-
- // TEST THE flags() MEMBER FUNCTION
-
- #include <header.h>
-
- int main()
- {
- long value = cout.flags(0) ;
- cout.setf(ios::hex , ios::basefield) ;
- cout << "value is "
- << value
- << '\n' ;
- FORMATFLAGS(cout) ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- value is 2001
- FORMAT FLAGS
- hex
-
- ==========================================================
-
- Note that value is the old value of x_flags, and is printed in
- hexadecimal. The '2' represents the unitbuf bit, and the '1' is the
- skipws bit. There is nothing shown under the heading FORMAT FLAGS
- except hex because all of the bits were turned off by the call to flags,
- then the hex bit was turned back on.
-
- Both the setf() and unsetf() functions return a value, which you are
- free to use or ignore. The value returned is a long integer representing
- the previous value of the field x_flags. However, you will probably never
- have occasion to use this value.
-
- ==========================================================
-
- The base setting and integer output
-
- Output formatting is important because you want to have complete
- flexibility in the manner in which your data appears. Let's start with the
- base in which integers will be shown. If a printf() function call, you
- have 3 choices: decimal, octal and hex. A decimal output can be obtained
- by using a conversion specification of %d or %i, an octal by using %o,
- and hex by using %x or %X. (How to emulate lower vs. upper case will be
- discussed later.) There are 3 bits in the enumerated values shown in the
- Bit Format Flags section that control the base setting:
-
- ios::dec 0x0010 Show integers in decimal format
- ios::oct 0x0020 Show integers in octal format
- ios::hex 0x0040 Show integers in hexadecimal format
-
- To guarantee that decimal output is used, you must turn on the bit
- ios::dec, and ensure that the remaining 2 bits are turned off. The same
- reasoning applies to octal and hex output. But we proved in example
- OUTPUT-11 that no bit pertaining to the output base is on by default.
- Therefore, which base will be used? The answer is that the compiler will
- default to decimal output if none of the 3 base field bits is on. But be
- careful! If more than one output base bit happens to be on, then the
- output is unpredictable. (Of course, you would never deliberately put
- yourself in this situation.). Remember: Once the base has been set, it
- stays set for all future integers unless it is subsequently changed.
-
- Recall that the field ios::basefield has been defined for you to
- contain all 3 base field bits ORed together, and should be used in the
- second parameter of setf to ensure that all 3 bits are turned off before
- altering them. So now let's create a program to output the number 65 using
- the default base, followed by the base in octal, hexadecimal, and decimal.
- (NOTE: a better way in which to write this program will be shown in the
- chapter on manipulators.)
-
- // EXAMPLE OUTPUT-21
-
- // HOW TO PRINT IN DECIMAL, OCTAL
- // AND HEXADECIMAL FORMATS
-
- #include <header.h>
-
- int main()
- {
- cout << 65 << '\n' ;
- cout.setf(ios::oct , ios::basefield) ;
- cout << 65 << '\n' ;
- cout.setf(ios::hex , ios::basefield) ;
- cout << 65 << '\n' ;
- cout.setf(ios::dec , ios::basefield) ;
- cout << 65 << '\n' ;
-
- return 0 ;
- }
-
- The output of this program, as expected, is:
-
- 65
- 101
- 41
- 65
-
- ==========================================================
-
- One final point: In a printf() function call, the use of the flag #
- causes the base of an octal or hexadecimal number to appear (0 and 0x,
- respectively). The same effect can be achieved in C++ by setting on the
- bit ios::showbase. Here is example OUTPUT-21 again, but this time the
- base of the octal and hexadecimal numbers is shown. To turn off this
- feature, use the unsetf function.
-
- // EXAMPLE OUTPUT-22
-
- // HOW TO PRINT IN DECIMAL, OCTAL
- // AND HEXADECIMAL FORMATS AND SHOW
- // THE BASE FOR OCTAL AND HEX
- // NUMBERS
-
- #include <header.h>
-
- int main()
- {
- cout.setf(ios::showbase) ;
- cout << 65 << '\n' ;
- cout.setf(ios::oct , ios::basefield) ;
- cout << 65 << '\n' ;
- cout.setf(ios::hex , ios::basefield) ;
- cout << 65 << '\n' ;
- cout.setf(ios::dec , ios::basefield) ;
- cout << 65 << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- 65
- 0101
- 0x41
- 65
-
- ==========================================================
-
- Note that on positive decimal output, a '+' sign is assumed. If you
- want this sign to appear, turn on the bit ios::showpos. (Of course, if
- the number is negative, the '-' sign will always appear.) To turn off this
- feature, use the unsetf() function. In this example the number 65 is
- displayed with a '+' sign.
-
- // EXAMPLE OUTPUT-23
-
- // HOW TO SHOW THE SIGN OF A POSITIVE
- // NUMBER
-
- #include <header.h>
-
- int main()
- {
- cout.setf(ios::showpos) ;
- cout << 65 << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- +65
-
- ==========================================================
-
- There is one other option you can employ with hexadecimal numbers. By
- default any hex digit, as well as the 'x' in the base, appears in lower-
- case. The same rule applies to the 'e' when printing in scientific
- notation. If you want to see upper-case, turn on the bit ios::uppercase.
- To revert back to lower-case, use the unsetf() function.
-
- This example prints the number 171 in hexadecimal, and shows all hex
- digits in upper-case.
-
- // EXAMPLE OUTPUT-24
-
- // HOW TO PRINT HEX DIGITS IN
- // UPPER CASE LETTERS
-
- #include <header.h>
-
- int main()
- {
- cout.setf(ios::uppercase | ios::showbase) ;
- cout.setf(ios::hex , ios::basefield) ;
- cout << 171 << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- 0XAB
-
- ==========================================================
-
- Character output
-
- Integer output pertains to the display of decimal, octal and
- hexadecimal numbers. The next question is: Can you emulate %c in a
- printf() function call to set the base for character output, i.e., set it
- so that all integral values appear in their character representations?
- Unfortunately, the answer is no. However, the problem only arises when a
- non-character value needs to be displayed in its character form, because
- if you define a character using type char, then it will automatically be
- shown in its character format. Thus, to make a non-char type appear in
- character format, an explicit cast to type char is required.
-
- For example, this program prints the letter A five times:
-
- // EXAMPLE OUTPUT-31
-
- // HOW TO PRINT A CHARACTER AND A
- // NON-CHARACTER IN CHARACTER
- // FORMAT.
-
- #include <header.h>
-
- int main()
- {
- // No cast needed here
- char ch1 = 'A' ;
- cout << ch1 << '\n' ;
- char ch2 = 65 ;
- cout << ch2 << '\n' ;
-
- // Cast needed here
- int ch3 = 65 ;
- cout << (char)ch3 << '\n' ;
- int ch4 = 0101 ;
- cout << (char)ch4 << '\n' ;
- int ch5 = 0x41 ;
- cout << (char)ch5 << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- A
- A
- A
- A
- A
-
- ==========================================================
-
- How about the opposite? That is, suppose you want a character to be
- shown in its decimal, octal, and hexadecimal representations. Once again,
- a cast is required, this time to type int. By doing this cast in
- conjunction with the proper base setting, the desired result can be
- obtained. In this example, the character ch is shown in its decimal,
- octal, and hexadecimal representations.
-
- // EXAMPLE OUTPUT-32
-
- // HOW TO PRINT A CHARACTER AS A
- // DECIMAL, OCTAL AND HEXADECIMAL
- // VALUE.
-
- #include <header.h>
-
- int main()
- {
- char ch = 'A' ;
-
- cout << (int)ch << '\n' ;
- cout.setf(ios::oct , ios::basefield) ;
- cout << (int)ch << '\n' ;
- cout.setf(ios::hex , ios::basefield) ;
- cout << (int)ch << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- 65
- 101
- 41
-
- ==========================================================
-
- There is another way to guarantee that an integral value gets shown in
- its character format. That is with the use of the member function called
- put() (think of the C function putchar()). This function always outputs
- its one argument in character format, regardless of how it was defined.
- This example prints the character A three times.
-
- // EXAMPLE OUTPUT-33
-
- // HOW TO USE THE MEMBER FUNCTION
- // put TO OUTPUT A CHARACTER
-
- #include <header.h>
-
- int main()
- {
- char ch1 = 'A' ;
- int ch2 = 65 ;
-
- cout.put(ch1) << '\n' ;
- cout.put('A') << '\n' ;
- cout.put(ch2) << '\n' ;
-
- return 0 ;
- }
-
- ==========================================================
-
- Setting the field width
-
- The field width in C++ works in a similar manner to that of C. If the
- total number of characters needed for output is less than the specified
- width, then the extra spaces will be filled with the current fill
- character. If the number of characters is greater than the specified
- width, then the width is "expanded" to accommodate the entire field. (In C
- the fill character in a printf() function call can only be either a zero
- or a space; in C++ it can be any character you desire. This topic is
- dicussed following width.)
-
- If no width is ever specified, then the default value of zero is
- assumed (just as it is in C). To change the field width, use the member
- function width() with one argument: the width value itself. Then the next
- field to be output will use this value.
-
- For example, this program prints the number 1 right-justified and
- preceded by 4 blanks, while the number 23 has 3 preceding blanks.
-
- // EXAMPLE OUTPUT-41
-
- // HOW TO SET THE FIELD WIDTH
-
- #include <header.h>
-
- int main()
- {
- cout.width(5) ;
- cout << 1 << '\n' ;
- cout.width(5) ;
- cout << 23 << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- 1
- 23
-
- ==========================================================
-
- Something should strike you as odd about this example. Why was it
- necessary to write the line cout.width(5) twice? The answer is that the
- width specification only applies to the next field to be output. To prove
- this statement, let's modify this example slightly and remove the second
- width setting.
-
- // EXAMPLE OUTPUT-42
-
- // NOTE THAT THE WIDTH SETTING
- // ONLY APPLIES TO THE NEXT FIELD
- // TO BE OUTPUT
-
- #include <header.h>
-
- int main()
- {
- cout.width(5) ;
- cout << 1 << '\n' ;
- cout << 23 << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- 1
- 23
-
- Now the number 23 appears left-justified because the width reverted back
- to its default value of 0.
-
- ==========================================================
-
- In addition to setting the field width, the width() function also
- returns the value of the width just prior to the function call. If you
- wish to return this value and leave it alone, then call the width()
- function with no argument specified.
-
- IMPORTANT NOTE: Under the current implementation of Borland C++,
- the field width specification does not apply to character
- fields that are output. This does not mean that the width reverts back to
- zero upon encountering a character field, but instead is applied to the
- next non-character field that is encountered. For example:
-
- // EXAMPLE OUTPUT-43
-
- // SHOW THE BUG WITH THE width()
- // FUNCTION
-
- #include <header.h>
-
- int main()
- {
- cout.width(5) ;
- cout << 'A' << 123 << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- A 123
-
- whereas if the width(5) call had been applied to the 'A', the output
- would have been:
-
- A123
-
- ==========================================================
-
- Specifying the fill character
-
- If the total number of characters needed to display a field is less
- than the current field width, the extra output spaces will be filled with
- the current fill character. In a printf() function call, the default fill
- character is a blank, and you only have the option to change it to a zero.
-
- In C++, however, you now have the option for any character to serve as
- the fill character. As before, the default is a blank. The member function
- fill() is used to specify a new fill character. Once it is specified, it
- remains as the fill character unless it is subsequently changed. The
- function takes a single argument: the new fill character, and returns the
- previous fill character. As with width(), it may be called with no actual
- argument if you merely want to return the previous fill character.
-
- This example outputs the default fill character, changes it to an
- asterisk, and then proves that the current fill character is, indeed, an
- asterisk.
-
- // EXAMPLE OUTPUT-51
-
- // HOW TO SET THE FILL CHARACTER
-
- #include <header.h>
-
- int main()
- {
- char old_fill = cout.fill('*') ;
- cout << "Old fill character is "
- << SINGLE_QUOTE
- << old_fill
- << SINGLE_QUOTE
- << '\n' ;
-
- cout << "It was changed to "
- << SINGLE_QUOTE
- << cout.fill()
- << SINGLE_QUOTE
- << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- Old fill character is ' '
- It was changed to '*'
-
- ==========================================================
-
- Now let's re-run example OUTPUT-41, but this time we'll fill the first
- field with zeroes, and the second with asterisks.
-
- // EXAMPLE OUTPUT-52
-
- // A COMBINATION OF SETTING THE
- // FIELD WIDTH AND SPECIFYING
- // THE FILL CHARACTER
-
- #include <header.h>
-
- int main()
- {
- cout.width(5) ;
- cout.fill('0') ;
- cout << 1 << '\n' ;
- cout.width(5) ;
- cout.fill('*') ;
- cout << 23 << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- 00001
- ***23
-
- ==========================================================
-
- Field justification
-
- Whenever a field gets output, and the field width is greater than the
- number of characters needed to display the field, the data is always
- right-justified with the fill character used as padding to the left. (Of
- course, if the field width is less than or equal to the number of
- characters needed, no justification occurs and the fill character is
- ignored.)
-
- Recall that there are 3 bits which are used to set the field
- justification:
-
- ios::left 0x0002 Left-justification of output
- ios::right 0x0004 Right-justification of output
- ios::internal 0x0008 Pad after sign or base indicator
-
- If no bit is set in the field x_flags, then the justification defaults
- to right. Once the justification has been set, it remains set unless it is
- subsequently changed. As with setting the base, there is a field called
- ios::adjustfield which has been defined with all 3 justification bits
- turned on. When setting the justification, this field should be used as
- the second argument in the setf() member function call to ensure that the
- other 2 bits are turned off. To set the justification to left, use the
- member function setf() with the bit ios::left, and to change it back to
- right, use the bit ios::right.
-
- Here is example OUTPUT-41 again, this time with both fields left-
- justified.
-
- // EXAMPLE OUTPUT-61
-
- // HOW TO LEFT-JUSTIFY A FIELD
-
- #include <header.h>
-
- int main()
- {
- cout.setf(ios::left , ios::adjustfield) ;
- cout.width(5) ;
- cout.fill('0') ;
- cout << 1 << '\n' ;
- cout.width(5) ;
- cout.fill('*') ;
- cout << 23 << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- 10000
- 23***
-
- ==========================================================
-
- The justification resulting from the ios::internal bit means that
- padding with the fill character, if any, will occur after the base of the
- number has been shown (for octal and hexadecimal numbers) and before the
- number itself. In the case of decimal numbers, the padding will occur
- after the sign ('+' or '-') and before the number itself. That is, instead
- of padding occurring on the left or on the right, it occurs "in the
- middle".
-
- In this example, the base is shown, the fill character is set to '=',
- the internal bit is set on, the field width is set to 10, hexadecimal
- output is requested, and the number 65 is printed. Then the same number is
- printed again, but with left justification.
-
- ==========================================================
-
- // EXAMPLE OUTPUT-62
-
- // HOW TO DO INTERNAL JUSTIFICATION
-
- #include <header.h>
-
- int main()
- {
- cout.setf(ios::showbase) ;
- cout.fill('=') ;
- cout.setf(ios::internal , ios::adjustfield) ;
- cout.width(10) ;
- cout.setf(ios::hex , ios::basefield) ;
- cout << 65 << '\n' ;
- cout.setf(ios::left , ios::adjustfield) ;
- cout.width(10) ;
- cout << 65 << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- 0x======41
- 0x41======
-
- ==========================================================
-
- Floating Point Output
-
- Floating point numbers are output in C++ just like any other type of
- number. However, the formatting is certainly different, and default values
- are not the same as you would get from using a printf() function call.
-
- In this example some floating point constants are output.
-
- // EXAMPLE OUTPUT-71
-
- // DISPLAY SOME FLOATING POINTS
- // CONSTANTS WITHOUT ANY FORMATTING
-
- #include <header.h>
-
- int main()
- {
- cout << 1.2300 << '\n' ;
- cout << 4.00 << '\n' ;
- cout << 5.678E2 << '\n' ;
- cout << 0.0 << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- 1.23
- 4
- 567.8
- 0
-
- For the first constant, note that the 2 trailing zeroes were not
- printed. This is certainly different from printf() in which the default
- is to show 6 positions after the decimal point. In the second case, not
- only do the trailing zeroes not show, but even the decimal point does not
- appear. In the third case, the number prints in fixed point notation
- despite being keyed in scientific notation. In the final case, at least
- one significant digit will always appear.
-
- Thus, we can infer that by default, all trailing zeroes, even the
- decimal point, will be suppressed. If you really want to emulate how the
- printf() function works, you need to turn on the ios::showpoint bit. To
- revert back to the default value, use the function unsetf() to turn it
- off. Here is the same example with this bit now on in the field x_flags.
-
- // EXAMPLE OUTPUT-72
-
- // HOW TO EMULATE printf()
-
- #include <header.h>
-
- int main()
- {
- cout.setf(ios::showpoint) ;
- cout << 1.2300 << '\n' ;
- cout << 4.00 << '\n' ;
- cout << 5.678E2 << '\n' ;
- cout << 0.0 << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- 1.230000
- 4.000000
- 567.800000
- 0.000000
-
- ==========================================================
-
- The next step is to override the default of 6 decimal positions. To do
- this, use the member function precision() in which the one argument is
- the number of decimal positions to be shown. This function also returns
- the previous value of the precision. If it is called without an argument,
- it merely returns the current value of the precision and does not alter
- it. The default precision is 0.
-
- Here is the same example with the precision now set to 1.
-
- // EXAMPLE OUTPUT-73
-
- // HOW TO EMULATE printf() AND SET
- // THE PRECISION
-
- #include <header.h>
-
- int main()
- {
- cout.setf(ios::showpoint) ;
- cout.precision(1) ;
- cout << 1.2300 << '\n' ;
- cout << 4.00 << '\n' ;
- cout << 5.678E2 << '\n' ;
- cout << 0.0 << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- 1.2
- 4.0
- 5.7e+02
- 0.0
-
- ==========================================================
-
- Note that the third answer is displayed in scientific notation. To
- guarantee that all output is shown in either fixed decimal or scientific
- notation, recall that the following bits are pre-defined in the class
- ios:
-
- ios::scientific 0x0800 Use exponential notation on floating point numbers
- ios::fixed 0x1000 Use fixed decimal output on floating point numbers
-
- If neither bit is turned on, then the compiler emulates the %g
- conversion specification in a printf() function call. Also recall that
- there is a constant called ios:floatfield that is the value of these two
- bits ORed together, and may be used as the second argument in a setf()
- function call.
-
- // EXAMPLE OUTPUT-74
-
- // HOW TO EMULATE printf(), SET
- // THE PRECISION, AND GUARANTEE
- // FIXED OR SCIENTIFIC OUTPUT
-
- #include <header.h>
-
- int main()
- {
- cout.setf(ios::showpoint) ;
- cout.precision(2) ;
-
- // Guarantee fixed decimal
- cout.setf(ios::fixed , ios::floatfield) ;
- cout << 1.2300 << '\n' ;
- cout << 4.00 << '\n' ;
- cout << 5.678E2 << '\n' ;
- cout << 0.0 << '\n' ;
-
- // Guarantee scientific
- cout.setf(ios::scientific , ios::floatfield) ;
- cout << 1.2300 << '\n' ;
- cout << 4.00 << '\n' ;
- cout << 5.678E2<< '\n' ;
- cout << 0.0 << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- 1.23
- 4.00
- 567.80
- 0.00
- 1.23e+00
- 4.00e+00
- 5.68e+02
- 0.00e+00
-
- ==========================================================
-
- Printing addresses
-
- The address of a variable (or instance of a class) can be generated by
- using the address operator (&). Because the address operator can be
- applied to a wide variety of types (both built-in and user-defined), the
- type of argument can theoretically be "pointer to int" or "pointer to
- float" or even "pointer to my class type". To accommodate all of these
- various types, the class ostream contains an overloaded function
- operator<< to handle all such argument types. This function is
- prototyped to accept an argument of type void* which, according to the
- rules of argument matching, is acceptable to the compiler as a "match".
-
- In Borland C++, this address is always shown in 32-bit (4
- byte) hexadecimal form, even though the default output base is decimal.
-
- ==========================================================
-
- // EXAMPLE OUTPUT-81
-
- // HOW TO PRINT AN ADDRESS
-
- #include <header.h>
-
- int main()
- {
- int number ;
-
- cout << "Address of number as a\n"
- << " 32-bit hex is "
- << &number
- << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- Address of a number as a
- 32-bit hex is 0x204cfff4
-
- ==========================================================
-
- Since the 8086 microprocessor chip uses only 16-bit addressing, the
- highest address possible is 65,535. This obviously is insufficient to
- handle the addresses on a computer with 1MB of RAM. What is really needed
- is a 20-bit address. Therefore, the address is divided between 2 hardware
- registers, the first called the segment register, and the second called
- the offset register. A segment is a 64K region of RAM that starts on an
- even multiple of 16 bytes. The location of any byte within a segment is
- determined by the offset. Thus, the physical 20-bit address of any
- specific byte within the computer is determined by shifting the segment
- address left 4 bits (1 hex digit, or a value of 16) and adding the offset
- value. The resulting address is usually shown in "segment:offset" form,
- just like the %p conversion specification in a printf() function call.
-
- Here is a program that prints an address in "segment:offset" form. The
- header file dos.h is needed here, and included in the file header.h.
-
- // EXAMPLE OUTPUT-82
-
- // HOW TO PRINT AN ADDRESS IN
- // SEGMENT:OFFSET FORM
-
- #include <header.h>
-
- int main()
- {
- int number ;
-
- cout << "Address of number in\n"
- << " segment:offset form "
- << "is\n "
- << FP_SEG(&number)
- << ":"
- << FP_OFF(&number)
- << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- Address of number in
- segment:offset form is
- 7846:65524
-
- ==========================================================
-
- Also in the file header.h is a function called ADDRESS() that does
- the same thing. Caution: do not call this function more than once in a
- single statement using cout.
-
- // EXAMPLE OUTPUT-83
-
- // HOW TO PRINT AN ADDRESS
- // USING A FUNCTION
-
- #include <header.h>
-
- int main()
- {
- int number ;
-
- cout << "Address of number in\n"
- << " segment:offset form "
- << "is\n "
- << ADDRESS(&number)
- << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- Address of number in
- segment:offset form is
- 7846:65524
-
- ==========================================================
-
- When dealing with a string, a problem arises. It's the same problem
- that occurred in C in a program fragment such as:
-
- char* ptr = "ABC" ;
- printf("%s" , ptr) ;
-
- No doubt the user will see ABC as the output. The problem is how to print
- the address contained within the pointer variable ptr. The solution in C
- is to provide a different conversion specification, namely %p.
-
- In C++ this program fragment:
-
- char* ptr = "ABC" ;
- cout << ptr ;
-
- would also output ABC because the argument ptr is of type char* which
- matches exactly an overloaded operator<< that accepts an argument of
- type char*. To emulate the %p in C++, you must override the built-in type
- of char* with the type void* so that the operator<< function that
- outputs an address will be called instead. The ADDRESS manipulator
- automatically casts every address it sees into void*.
-
- // EXAMPLE OUTPUT-84
-
- // HOW TO PRINT THE ADDRESS OF
- // A STRING
-
- #include <header.h>
-
- int main()
- {
- char* ptr = "ABC" ;
-
- cout << "The string itself is "
- << ptr
- << '\n' ;
-
- cout << "The address of the "
- << "string is "
- << ADDRESS(ptr)
- << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- The string itself is ABC
- The address of the string is 7848:604
-
- ==========================================================
-
- Binary output
-
- It's possible to take any internal representation of a C++ type and
- output it as though it were just an array of characters. For a type such
- as float, this will produce meaningless output, but it may be useful for
- integers. To do this, the member function write() must be used. This
- function takes 2 arguments: The first is the address of the data to be
- output, and the second is the number of bytes to be shown. Note that in
- the case of a string, the null byte is treated just like any other byte.
-
- Because the function write() is declared to accept an argument of type
- char*, if the item you wish to print is not of this type, then the
- address must be generated using the address operator, and then cast to
- type char*.
-
- ==========================================================
-
- // EXAMPLE OUTPUT-91
-
- // HOW TO DISPLAY THE BINARY REPRE-
- // SENTATION OF A NUMBER
-
- #include <header.h>
-
- int main()
- {
- long number = 0x414243 ;
-
- cout.write((char*)&number , sizeof(number)) ;
- cout << '\n' ;
-
- return 0 ;
- }
-
- Note that the output of this program is CBA, because the address of a
- long refers to the low-order byte.
-
- ==========================================================
-
- Incore output
-
- Incore output refers to how you can send output to an in-memory buffer
- of type char* instead of to the screen.
-
- In C this is done by using the function sprintf(), where the first
- argument specifies the address of the buffer area where the data is to be
- stored. For example:
-
- // EXAMPLE OUTPUT-95
-
- // HOW TO DO INCORE OUTPUT USING C
-
- #include <header.h>
-
- int main(void)
- {
- int number = 123 ;
- char buffer[80] ;
-
- sprintf(buffer,"number is %d", number) ;
- printf ("buffer contains: "
- "%c%s%c\n" ,
- DOUBLE_QUOTE ,
- buffer ,
- DOUBLE_QUOTE) ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- buffer contains: "number is 123"
-
- ==========================================================
-
- In order to accomplish the same result in C++ you must first include
- the header file strstream.h (this is already done in the file
- header.h). This file contains the declaration of the class ostrstream,
- which you then use to create some instance. At the time of creation, you
- must provide two arguments: (1) the address of the buffer where the data
- is to be written, and (2) the maximum size of this buffer (which normally
- is the sizeof the buffer). For example,
-
- char buffer [80] ;
- ostrstream output(buffer , sizeof buffer) ;
-
- After this has been done, the instance output is used where you would
- normally use cout. All of the data is thus sent to the buffer area. If
- you subsequently want to send this buffer area to the screen, don't
- forget to append a null byte to make the buffer a legitimate string
- object. If you subsequently wish to place more output into this buffer
- area starting back at character position 0, you must use the (inherited)
- member function seekp() with an argument of 0.
-
- // EXAMPLE OUTPUT-96
-
- // HOW TO DO INCORE OUTPUT IN C++
-
- #include <header.h>
-
- int main()
- {
- int number = 123 ;
- char buffer[80] ;
-
- ostrstream output(buffer , sizeof buffer);
-
- output << "number is "
- << number
- << '\0' ;
- cout << DOUBLE_QUOTE
- << buffer
- << DOUBLE_QUOTE
- << '\n' ;
-
- output.seekp(0) ;
- output << "A new buffer stream "
- << "of characters"
- << '\0' ;
- cout << DOUBLE_QUOTE
- << buffer
- << DOUBLE_QUOTE
- << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- "number is 123"
- "A new buffer stream of characters"
-
- ==============================================================================
- ==============================================================================
-
- CHAPTER 3 -- Input
-
- Introduction
-
- In addition to being able to use classes to control output, C++ stream
- I/O classes also handle all input. Just as output consists of a stream of
- characters being sent to some device, input consists of characters coming
- in from some device and being translated into their proper defined type.
- In other words, the characters '1', '2' and '3' could be the string "123"
- or the integer 123, depending upon the type of the receiving field.
- Unlike output, the realm of possibilities for ╥formatting╙ simply does
- not exist when inputting data.
-
- The class istream is derived from the class ios, and it controls the
- input handling functions. The global instance that is defined for you is
- called cin, and is defined as:
-
- istream cin ;
-
- In addition to data members, the class istream contains functions to
- format the stream of characters received from some input device, and
- present them to you in the manner in which you expect to receive them.
- The function you will use the most often is called the extraction
- operator, and it is written as:
-
- operator>>
-
- The argument to this function is the variable name that you wish to
- contain the input data. The name ╥extraction╙ comes from the fact that
- you are ╥extracting╙ (taking) data from the input stream.
-
- For example, to input an integer, you would code:
-
- int number ;
- cin.operator>>(number) ;
-
- As with the insertion operator, this code can be replaced with the
- simpler form:
-
- int number ;
- cin >> number ;
-
- Note that the type of the input variable determines how the characters
- from the input stream are to be stored.
-
- For example, this program inputs a number, character and float, and
- then echoes them back.
-
- // EXAMPLE INPUT-01
-
- // HOW TO INPUT SOME SIMPLE TYPES
-
- #include <header.h>
-
- int main()
- {
- int number ;
- cout << "Enter a number: " ;
- cin >> number ;
- cout << "You entered: "
- << number
- << '\n' ;
-
- char ch ;
- cout << "Enter a character: " ;
- cin >> ch ;
- cout << "You entered: "
- << ch
- << '\n' ;
-
- float fl ;
- cout << "Enter a float: " ;
- cin >> fl ;
- cout << "You entered: "
- << fl
- << '\n' ;
-
- return 0 ;
- }
-
- ==========================================================
-
- The extraction operators, like the insertion operators, can be chained
- together.
-
- // EXAMPLE INPUT-02
-
- // EXTRACTION OPERATORS CAN BE
- // CHAINED TOGETHER
-
- #include <header.h>
-
- int main()
- {
- int number1 , number2 ;
- cout << "Enter 2 numbers: " ;
- cin >> number1 >> number2 ;
- cout << "You entered: "
- << number1
- << " and "
- << number2
- << '\n' ;
-
- return 0 ;
- }
-
- ==========================================================
-
- Integer input
-
- Recall from the discussion on output that the base setting for integer
- output is, by default, decimal. This setting can, of course, be changed
- by using the setf() function. In a similar manner, the default base
- setting for integer input is decimal. This means that for a variable
- defined as either int or long, only valid integer data is acceptable. An
- attempt to violate this rule will cause an error condition to occur. For
- example, entering A12 for an integer value will cause an error. However,
- entering 12A will cause the number 12 to be stored into the integer, and
- the letter A to remain in the input stream, so that this is not
- necessarily an error condition; it depends on what you do next.
-
- Even though the default input base setting is decimal, it is still
- possible to override this default if you wish to input either an octal or
- hexadecimal number. This can be done by explicitly keying the base of
- these numbers, i.e., 0 for octal and 0x (or 0X) for hex. To test this, in
- example INPUT-02 enter the numbers 012 and 0xA. Then the output is: You
- entered: 10 and 10.
-
- However, note what happens when the input base setting is explicitly
- set to decimal via a setf() function call (or via a manipulator). In this
- case only decimal input is allowed. Here is example INPUT-02 again, but
- with the base setting explicitly specified. If you enter the numbers 012
- and 0xA, then the output is: You entered: 12 and 0 (the xA remains in
- the buffer).
-
- // EXAMPLE INPUT-03
-
- // EXPLICITLY GIVE THE INPUT
- // BASE SETTING
-
- #include <header.h>
-
- int main()
- {
- cin.setf(ios::dec , ios::basefield) ;
- int number1 , number2 ;
- cout << "Enter 2 numbers: " ;
- cin >> number1 >> number2 ;
- cout << "You entered: "
- << number1
- << " and "
- << number2
- << '\n' ;
-
- return 0 ;
- }
-
- ==========================================================
-
- It is possible to input octal and hex numbers without having to go to
- the trouble of explicitly keying their respective bases. This can be done
- by changing the base of the input stream from its default setting of
- decimal to either octal or hexadecimal. Essentially, this is how the %o
- and %x conversion specifications in a scanf() function call can be
- emulated.
-
- For example, this program prompts the user for numbers in decimal,
- octal, and hexadecimal formats, then echoes each number back. Note that
- the output is always in decimal because the setting of the output base
- has not been affected. In other words, the base setting is stored
- separately for each stream. Obviously, if an illegal input value is
- entered, the value will not be stored. If you enter the number 65 for all
- three prompts, the first is taken as decimal, the second as octal, and
- the third as hex.
-
- // EXAMPLE INPUT-04
-
- // INTEGERS CAN BE INPUT IN ANY OF
- // 3 DIFFERENT BASES
-
- #include <header.h>
-
- int main()
- {
- int number ;
-
- cout << "Enter a decimal number: " ;
- cin >> number ;
- cout << "You entered: "
- << number
- << '\n' ;
-
- cout << "Enter an octal number: " ;
- cin.setf(ios::oct , ios::basefield) ;
- cin >> number ;
- cout << "You entered: "
- << number
- << '\n' ;
-
- cout << "Enter a hex number: " ;
- cin.setf(ios::hex , ios::basefield) ;
- cin >> number ;
- cout << "You entered: "
- << number
- << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- 65
- 53
- 101
-
- ==========================================================
-
- Character input
-
- The simplest way to read in a character is to use the extraction
- operator >>.Leading whitespace characters are bypassed, and the first
- non-whitespace character is fetched. Also, as you would expect, a
- reference to the invoking instance is returned, thereby allowing the
- input operations to be chained together. In this example, enter 2 non-
- whitespace characters, and intersperse them with lots of blanks and tabs.
- Then press <RETURN>. Regardless of the whitespace that you may have
- entered, what will be stored are the 2 non-whitespace characters.
-
- // EXAMPLE INPUT-11
-
- // DEMONSTRATE HOW TO READ IN A
- // CHARACTER AND BYPASS LEADING
- // WHITESPACE
-
- #include <header.h>
-
- int main()
- {
- cout << "Enter 2 characters: " ;
- char ch1 , ch2 ;
- cin >> ch1 >> ch2 ;
- cout << "You entered: "
- << SINGLE_QUOTE
- << ch1
- << SINGLE_QUOTE
- << " and "
- << SINGLE_QUOTE
- << ch2
- << SINGLE_QUOTE
- << '\n' ;
-
- return 0 ;
- }
-
- ==========================================================
-
- If you want to consider whitespace characters (including '\n') as
- being just as ╥good╙ as all other characters, then you may turn off the
- bit ios::skipws using the instance cin (not cout). Here is example
- INPUT-11 again, but now the program will store the first 2 characters
- entered.
-
- // EXAMPLE INPUT-12
-
- // NOW TURN OFF THE BIT ios::skipws
- // SO THAT ALL CHARACTERS ARE CON-
- // SIDERED EQUAL
-
- #include <header.h>
-
- int main()
- {
- cin.unsetf(ios::skipws) ;
- cout << "Enter 2 characters: " ;
- char ch1 , ch2 ;
- cin >> ch1 >> ch2 ;
- cout << "You entered: "
- << SINGLE_QUOTE
- << ch1
- << SINGLE_QUOTE
- << " and "
- << SINGLE_QUOTE
- << ch2
- << SINGLE_QUOTE
- << '\n' ;
-
- return 0 ;
- }
-
- ==========================================================
-
- Another method to read characters is provided by the member function
- get(). The previous example can be emulated by using this function. It
- takes a single argument ╤ the character itself, and returns a reference
- to the invoking instance so that the function calls can be chained
- together. The difference between the extraction operator >> and get() is
- that get() does not use the format flags, so by default it does not
- bypass leading whitespace. In this example, enter the same data that you
- used in example INPUT-12.
-
- // EXAMPLE INPUT-13
-
- // THE MEMBER FUNCTION get()
- // WITH AN ARGUMENT
-
- #include <header.h>
-
- int main()
- {
- cout << "Enter 2 characters: " ;
- char ch1 , ch2 ;
- cin.get(ch1).get(ch2) ;
- cout << "You entered: "
- << SINGLE_QUOTE
- << ch1
- << SINGLE_QUOTE
- << " and "
- << SINGLE_QUOTE
- << ch2
- << SINGLE_QUOTE
- << '\n' ;
-
- return 0 ;
- }
-
- ==========================================================
-
- The get() function has also been overloaded so that it can take no
- input argument (just like getchar() in C). In this form it returns a
- value of type int, which represents the character just read, or the EOF
- constant if either (a) end-of-file was detected, or (b) no character
- could be read.
-
- In this example get() is used to read in a character, after which a
- check for end-of-file is made. Because the variable ch must be defined as
- type int, don't forget the cast in order to display it as a character.
-
- // EXAMPLE INPUT-14
-
- // DEMONSTRATE HOW TO READ IN A
- // CHARACTER USING getch() WITH NO
- // ARGUMENT
-
- #include <header.h>
-
- int main()
- {
- cout << "Enter a character: " ;
- int ch = cin.get() ;
- if (ch != EOF)
- cout << "You entered: "
- << SINGLE_QUOTE
- << (char)ch
- << SINGLE_QUOTE
- << '\n' ;
- else
- cout << "End-of-file\n" ;
-
- return 0 ;
- }
-
- ==========================================================
-
- String input
-
- As with character input, strings may be entered using the extraction
- operator or the overloaded member function get(). With the extraction
- operator, leading whitespace is bypassed, and the first whitespace
- encountered terminates the input. This acts just like the function
- scanf().
-
- // EXAMPLE INPUT-21
-
- // DEMONSTRATE HOW TO READ IN A
- // STRING AND BYPASS WHITESPACE
- // TOTALLY (LOOKS LIKE scanf())
-
- #include <header.h>
-
- const int length = 100 ;
-
- int main()
- {
- char string [length] ;
-
- cout << "Enter a string: " ;
- cin >> string ;
- cout << "Your string: "
- << DOUBLE_QUOTE
- << string
- << DOUBLE_QUOTE
- << '\n' ;
-
- return 0 ;
- }
-
- ==========================================================
-
- But just like scanf(), you could have a program hang or crash if the
- operator enters more characters than can safely be accommodated by the
- string array. In other words, run this program and enter more than 10
- characters.
-
- // EXAMPLE INPUT-22
-
- // IT'S POSSIBLE TO OVERFLOW AN
- // ARRAY WHEN INPUTTING A STRING
-
- #include <header.h>
-
- const int max = 10 ;
-
- int main()
- {
- char string[max] ;
-
- cout << "Enter a string: " ;
- cin >> string ;
- cout << "You entered: "
- << DOUBLE_QUOTE
- << string
- << DOUBLE_QUOTE
- << '\n' ;
-
- return 0 ;
- }
-
- ==========================================================
-
- If it didn't bomb, consider yourself lucky. To guard against this
- disaster, you may set the width of the input stream to physically limit
- the number of characters that will be stored. This is done by using the
- member function width() in the class istream. The result is that only
- the number of characters (less 1) specified by the argument to width()
- will be extracted from the input stream; the remaining characters are
- left alone. Run this program and enter the letters 'A' through 'M'. Note
- that only the letters 'A' through 'I' got stored into the string. (The
- last byte is always reserved for the null character.)
-
- // EXAMPLE INPUT-23
-
- // HOW TO AVOID OVERFLOWING AN
- // ARRAY WHEN INPUTTING A STRING
- // WITH THE EXTRACTION OPERATOR
-
- #include <header.h>
-
- const int max = 10 ;
-
- int main()
- {
- char string[max] ;
-
- cout << "Enter a string: " ;
- cin.width(max) ;
- cin >> string ;
- cout << "You entered: "
- << DOUBLE_QUOTE
- << string
- << DOUBLE_QUOTE
- << '\n' ;
-
- return 0 ;
- }
-
- ==========================================================
-
- Caution: Just like the width() function used for output, the input
- width() function only applies to the next item to be input.
-
- Another way to read in strings is to use the member function get()
- with 3 arguments. Note the similarity to the C function fgets(). The
- first argument is the address of the string area, the second is the
- maximum number of characters (less 1) that can be read in, and the third
- specifies the terminating character (the one that will stop the transfer
- of characters from the buffer into the string array). This third argument
- defaults to the value '\n', which is the <RETURN> key. Note, however,
- that if it is changed to some other character, then the <RETURN> key
- must still be pressed for the input action to cease. Both leading
- whitespace and embedded whitespace are retained as part of the string
- value.
-
- // EXAMPLE INPUT-24
-
- // DEMONSTRATE HOW TO READ IN A
- // STRING AND RETAIN WHITESPACE,
- // BOTH LEADING AND EMBEDDED
-
- #include <header.h>
-
- const int length = 100 ;
-
- int main()
- {
- char string [length] ;
-
- cout << "Enter a string: " ;
- cin.get(string , length) ;
- cout << "Your string: "
- << DOUBLE_QUOTE
- << string
- << DOUBLE_QUOTE
- << '\n' ;
-
- return 0 ;
- }
-
- ==========================================================
-
- A slight variation on the function get() taking 3 arguments is the
- function getline(). The only difference is that getline() extracts the
- newline character ('\n') from the input buffer, whereas get() leaves it
- alone (and, presumably, must then be flushed by the programmer).
-
- // EXAMPLE INPUT-25
-
- // TEST THE getline() FUNCTION
-
- // IN TC++ 1.01 THE ENDING QUOTE
- // APPEARS ON THE NEXT LINE BE-
- // CAUSE THE NEWLINE CHARACTER IS
- // MADE PART OF THE USER'S BUFFER
- // AREA. IN BORLAND C++ THIS HAS
- // BEEN CORRECTED
-
- #include <header.h>
-
- const int length = 100 ;
-
- int main()
- {
- char string [length] ;
-
- cout << "Enter a string: " ;
- cin.getline(string , length) ;
- cout << "Your string: "
- << DOUBLE_QUOTE
- << string
- << DOUBLE_QUOTE
- << "\n" ;
-
- return 0 ;
- }
-
- ==========================================================
-
- It's also possible to find out exactly how many characters were
- extracted from the input buffer after a get() operation. The member
- function gcount() returns this value. In this program if you enter "ABC"
- gcount() will report that 4 characters were read.
-
- // EXAMPLE INPUT-26
-
- // DETERMINE HOW MANY CHARACTERS
- // WERE ENTERED
-
- #include <header.h>
-
- const int length = 100 ;
-
- int main()
- {
- char string[length] ;
-
- cout << "Enter a string: " ;
- cin.getline(string , length) ;
- cout << "Your string: "
- << DOUBLE_QUOTE
- << string
- << DOUBLE_QUOTE
- << "\n" ;
-
- cout << "You entered "
- << cin.gcount()
- << " characters\n" ;
-
- return 0 ;
- }
-
- ==========================================================
-
- Checking for end-of-file
-
- When reading data from the keyboard or a file, the programmer must
- always be on guard for the occurrence of an end-of-file mark (in DOS it's
- the character ^Z or decimal value 26 from text files. In Unix it's
- <CTRL>-D). This is comparable to detecting the return value EOF when
- doing a scanf() in C. In C++, the member function eof() taking no
- arguments in the class ios will return ╥true╙ if the end-of-file
- condition was found, ╥false╙ if not found.
-
- Normally data is obtained within a while loop, with the loop
- continuing to execute as long as end-of-file is not detected. This
- situation could be coded like this:
-
- // EXAMPLE INPUT-31
-
- // ENTER A NUMBER AND LOOP
- // UNTIL EOF IS DETECTED
-
- #include <header.h>
-
- int main()
- {
- cout << "Enter a number: " ;
- int num ;
- cin >> num ;
- while (!cin.eof())
- {
- cout << "You entered: "
- << num
- << '\n' ;
- cout << "\nNext number: " ;
- cin >> num ;
- }
- cout << "End-of-file\n" ;
-
- return 0 ;
- }
-
- ==========================================================
-
- While this program certainly works, there is a better way to code it.
- Since the statement:
-
- cin >> num ;
-
- returns a reference to the invoking object itself (namely cin), this
- object can be used as the invoking object for the eof() member function
- call. Thus, the revised code looks like:
-
- // EXAMPLE INPUT-32
-
- // A BETTER METHOD OF CODING
- // EXAMPLE INPUT-31
-
- #include <header.h>
-
- int main()
- {
- cout << "Enter a number: " ;
- int num ;
- while (!(cin >> num).eof())
- {
- cout << "You entered: "
- << num
- << '\n' ;
- cout << "\nNext number: " ;
- }
- cout << "End-of-file\n" ;
-
- return 0 ;
- }
-
- ==========================================================
-
- Notice how the read and check for end-of-file have been combined to
- form the Boolean condition of the while loop. This is analagous in C to
- writing:
-
- while(scanf("%d" , &num) != EOF)
- {
- /* Body of loop */
- }
-
- ==========================================================
-
- Checking for errors
-
- Unfortunately, we live in an imperfect world. People don't smile, cars
- crash, checks bounce, and data entry operators don't always do what
- they're supposed to do. This means that as a programmer you must be
- responsible for making your code as "operator-proof" as possible. In
- other words, no matter what the user may enter as "data", your program
- must capture it and successfully trap all error conditions to avoid such
- catastrophes as ╥garbage in, garbage out╙, aborts, hangs, endless loops,
- etc.
-
- When expecting character or string input, there's not too much that
- can go wrong, other than array overflow which has already been covered.
- But with numeric data, such as ints, floats and doubles, only certain
- keystrokes in a prescribed order and considered to be valid. For example,
- when you expect a decimal integer to be input, the user may enter a sign
- (+ or -) followed by the digits 0 through 9. An entry of A12 is obviously
- invalid. An entry such as 12A, however, is considered to be the number
- 12, with the "invalid" character 'A' serving to terminate the numeric
- portion of the input stream. In addition, any whitespace character
- terminates a numeric entry, and all leading whitespace characters are
- bypassed automatically.
-
- There are several ways to check for "garbage" input when expecting
- valid numeric data. Within the class ios the member function good() will
- return "true" if the preceding operation succeeded, "false" otherwise. In
- addition, the member function fail() will do just the opposite. The
- member function bad() will report some catastrophic condition, such as a
- corrupted stream.
-
- Another way to check for an input error is to use the overloaded
- function operator! (Boolean not) on the instance cin. This operator will
- return "true" if an error occurred, "false" otherwise. Similarly, testing
- the instance cin itself as a Boolean value will return "true" if the
- input was good, "false" otherwise.
-
- Note that in the header file header.h the function IOFLAGS() has
- been defined to accept an instance of an input stream as an argument, and
- will display the various input states. The default argument is the
- instance cin. To test this, trying entering some numeric and non-numeric
- data for this program, as well as end-of-file.
-
- // EXAMPLE INPUT-41
-
- // TEST THE INPUT STREAM STATES
-
- #include <header.h>
-
- int main()
- {
- cout << "Enter a number: " ;
- int number ;
- cin >> number ;
- IOFLAGS() ;
-
- return 0 ;
- }
-
- ==========================================================
-
- Now that you see what does and does not work, the next problem is how
- to eliminate the excess, or garbage, characters from the input stream.
- This can be accomplished by reading 1 character at a time until the
- <RETURN> ('\n') character or end-of-file has been read. (Of course this
- method assumes that if an error condition is encountered, then the
- integrity of the remaining characters in the buffer is in question.) In
- addition, when an error occurs, the status of the input stream is changed
- from ╥good╙ to ╥fail╙ and no more characters can be read until the status
- is reset to ╥good╙. To do this, you must call upon the ios member
- function clear() with no arguments (the default argument is 0, which
- means ╥set the status of the stream to 'good'╙).
-
- To save you the trouble of having to code a function to accomplish
- this "flushing" task, the file header.h has an input manipulator called
- FLUSH that will do this for you. (The subject of manipulators and how
- they work will be covered in the chapter MANIP.)
-
- Thus, an "operator-proof" program that loops while reading numbers and
- checking for end-of-file and garbage input might resemble this:
-
- // EXAMPLE INPUT-42
-
- // SHOW HOW TO HANDLE ANYTHING
- // THE OPERATOR CAN THROW AT
- // THE PROGRAM
-
- #include <header.h>
-
- int main()
- {
- cout << "\nEnter a number: " ;
- int number ;
- while(!(cin >> number).eof())
- {
- // Test for a bad number
- if(!cin)
- // Same as: if(!cin.good())
- cout << "Input error!\n" ;
-
- // Process a good number
- else
- cout << "YOU ENTERED: "
- << number
- << '\n' ;
-
- // Clear out the input buffer.
- cin >> FLUSH ;
-
- cout << "\nNext number: " ;
- }
- cout << "\nEND OF PROGRAM\n" ;
-
- return 0 ;
- }
-
- One note about the logic of this program. If 2 valid numbers are
- entered before the operator presses <RETURN>, then only the first number
- will be processed; the second will be flushed. You may or may not want
- this to happen, depending upon your design philosophy.
-
- ==========================================================
-
- Incore input
-
- Incore input refers to how you can read input from an in-memory buffer
- of type char* instead of from the keyboard.
-
- In C this is done by using the function sscanf(), where the first
- argument specifies the address of the buffer area from which the data is
- to be read. For example:
-
- // EXAMPLE INPUT-51
-
- // HOW TO DO INCORE INPUT USING C
-
- #include <header.h>
-
- int main(void)
- {
- char buffer[] = "ABC 1.234 5" ;
- char string[100] ;
- /* Initialized to link floating point formats */
- float f = sqrt(0.0) ;
- int n ;
-
- sscanf(buffer , "%s%f%d" , string , &f , &n) ;
- printf("string = %c%s%c\n" ,
- DOUBLE_QUOTE ,
- string ,
- DOUBLE_QUOTE) ;
- printf("float = %f\n" , f) ;
- printf("int = %d\n" , n) ;
-
- return 0 ;
- }
-
- =========================================================
-
- In order to accomplish the same result in C++ you must first include
- the header file strstream.h (this is already done in the file
- header.h). This file contains the declaration of the class istrstream,
- which you then use to create some instance. At the time of creation, you
- must provide two arguments: (1) the address of the buffer from which the
- data is to be read, and (2) the maximum size of this buffer (which
- normally is the sizeof the buffer). For example,
-
- char buffer[80] ;
- istrstream input(buffer , sizeof buffer) ;
-
- After this has been done, the instance input is used where you would
- normally use cin. All of the data is thus read from the buffer area. If
- you subsequently wish to read this buffer area starting back at character
- position 0, you must use the (inherited) member function seekg() with an
- argument of 0.
-
- // EXAMPLE INPUT-52
-
- // HOW TO DO INCORE INPUT USING C++
-
- #include <header.h>
-
- int main(void)
- {
- char buffer[] = "ABC 1.234 5" ;
- char string[100] ;
- float f ;
- int n ;
-
- istrstream input(buffer , sizeof buffer) ;
-
- input >> string >> f >> n ;
- cout << "string = "
- << DOUBLE_QUOTE
- << string
- << DOUBLE_QUOTE
- << endl ;
- cout << "float = " << f << endl ;
- cout << "int = " << n << endl ;
-
- // Let's do it again
- input.seekg(0) ;
- input >> string >> f >> n ;
- cout << "string = "
- << DOUBLE_QUOTE
- << string
- << DOUBLE_QUOTE
- << endl ;
- cout << "float = " << f << endl ;
- cout << "int = " << n << endl ;
-
- return 0 ;
- }
-
- =========================================================
-
- If you wish to do both input and output using a memory buffer, one way
- is to create an instance of the class strstream, but with no arguments,
- e.g.,
-
- strstream both ;
-
- When the instance both is used, data is stored in an internal buffer
- area. The extraction operator may then be used with the instance to read
- from this buufer area. In addition, the function rdbuf() returns the
- address of the buffer area.
-
- // EXAMPLE INPUT-53
-
- // HOW TO DO BOTH INPUT AND OUTPUT
- // WITH AN IN-CORE BUFFER
-
- #include <header.h>
-
- int main()
- {
- strstream both ;
-
- // Put data into the internal
- // buffer
- both << "ABC"
- << " " << 1.234
- << " " << 5
- << ends ;
-
- // The data so far:
- cout << both.rdbuf() << endl ;
-
- // Don't forget this
- both.seekg(0) ;
-
- // Extract data from 'buffer'
-
- char string[100] ;
- float f ;
- int n ;
- both >> string >> f >> n ;
-
- // Verify the data
- cout << "string = "
- << DOUBLE_QUOTE
- << string
- << DOUBLE_QUOTE
- << endl ;
- cout << "float = " << f << endl ;
- cout << "int = " << n << endl ;
- }
-
- =========================================================
-
- If you wish to create your own buffer area, then you may do so
- provided that you open the internal file in both input and output modes
- (see the chapter FILEIO for more information on file modes).
-
- // EXAMPLE INPUT-54
-
- // HOW TO DO BOTH INPUT AND OUTPUT
- // WITH A USER-DEFINED BUFFER
-
- #include <header.h>
-
- int main()
- {
- char buffer[100] ;
- strstream both(buffer , sizeof buffer , ios::in | ios::out) ;
-
- // Put data into 'buffer'
- both << "ABC"
- << " " << 1.234
- << " " << 5
- << ends ;
-
- // The data so far:
- cout << buffer << endl ;
-
- // Don't forget this
- both.seekg(0) ;
-
- // Extract data from 'buffer'
- char string[100] ;
- float f ;
- int n ;
- both >> string >> f >> n ;
-
- // Verify the data
- cout << "string = "
- << DOUBLE_QUOTE
- << string
- << DOUBLE_QUOTE
- << endl ;
- cout << "float = " << f << endl ;
- cout << "int = " << n << endl ;
- }
-
- ============================================================================
- ============================================================================
-
- CHAPTER 4 -- Manipulators
-
- Introduction
-
- As you have probably noticed by now, the elimination of conversion
- specifications (%d, %c, etc.) found in the scanf() and printf()
- functions in C causes you to do a lot of extra coding to accomplish the
- same end result. For example, to output a number in hex, the
- specification %x does the job in a printf() statement, but using the
- functions supplied in iostream.h you have to write:
-
- cout.setf(ios::hex , ios::basefield) ;
-
- even before you get around to outputting the number itself.
-
- Fortunately, the input and output stream classes provide the
- capability to eliminate much of this tedious coding, as well as a method
- to combine the setting of the stream state with the actual outputting (or
- inputting) of the data itself. This method uses what is called a
- manipulator. The term comes from the fact that a manipulator does just
- what it implies ╤ it manipulates, or changes, the state of the stream.
-
- In the same sense that a call of a function causes that function to do
- any number of individual tasks the programmer may specify, a manipulator
- is also a function that sets the stream state. The input and output
- classes come with some manipulators already built in, and a nice feature
- is that you can easily define your own.
-
- The one peculiar aspect about manipulators is that they are always
- called "indirectly" by a separate function in the class ios. This other
- function "knows" which individual manipulator to call because it takes
- the manipulator's address as its one formal argument. How does it get
- this address? When the name of a function is written without the open and
- close parentheses, the compiler generates the address of the function
- instead of actually calling the function. This address can, in turn, be
- passed as an argument to another function. Of course, this address must
- be stored into a variable declared as ╥pointer to function╙ which has the
- proper return type and argument list.
-
- One of the main advantages to using manipulators is that they can be
- chained together, just like any other type of argument you may want to
- input or output. Consider this problem: Write code to output the number
- 123, then set the field width to 5, then set the fill character to an
- '*', then output the number 456. Using the functions we've learned up to
- now, you might be tempted to write the code:
-
- cout << 123 << cout.width(5) << cout.fill('*') << 456 << '\n' ;
-
- If you do, you will see:
-
- ***1230 456
-
- instead of:
-
- 123**456
-
- Of course, one way to fix this problem is to segregate the calls to
- the member functions from the calls to the insertion operator. Thus:
-
- cout << 123 ;
- cout.width(5) ;
- cout.fill('*') ;
- cout << 456 << '\n' ;
-
- This produces the correct output, but it's "choppy" because one "thought"
- now has to be written in four separate output statements.
-
- But since the manipulators that handle input and output stream states
- can be chained with other arguments, then (assuming output) generically
- they have the form:
-
- cout << data-item1 << manipulator << data-item2 ;
-
- Thus, if the manipulator could be made to set the field width to 5 and
- the fill character to '*', our problem could be coded in a much more
- concise manner. In addition, manipulators must not return any value that
- might cause unwanted output to appear.
-
- In order to preserve the ability to chain successive calls to the
- insertion operator function, each manipulator function must adhere to the
- rule that the invoking instance is always passed in by reference and
- returned by reference. This means that all output manipulator functions
- have the format:
-
- ostream& manipulator_name(ostream& strm)
- {
- // your code here
- return strm ;
- }
-
- In a similar fashion, all input manipulators have the format:
-
- istream& manipulator_name(istream& strm)
- {
- // your code here
- return strm ;
- }
-
- To accommodate an argument of type "pointer to function", the class
- ostream has an overloaded insertion operator function similar to this
- code:
-
- ostream& operator<<(ostream& (*ptr)(ostream&))
- {
- return (*ptr)(*this) ;
- }
-
- The class istream has an overloaded extraction operator function
- similar to this code:
-
- istream& operator>>(istream& (*ptr)(istream&))
- {
- return (*ptr)(*this) ;
- }
-
- It's these two functions that call upon your manipulator function
- whose address has been stored into the pointer variable ptr. *this
- refers to the invoking instance itself, and will be discussed at length
- in the CLASSB chapter.
-
- As a test, here is a program that creates a manipulator function
- called manip that sets the field width to 5 and the fill character to
- '*'.
-
- ==========================================================
-
- // EXAMPLE MANIP-01
-
- // HOW TO CREATE YOUR OWN
- // MANIPULATOR
-
- #include <header.h>
-
- ostream& manip(ostream& strm)
- {
- strm.width(5) ;
- strm.fill('*') ;
- return strm ;
- }
-
- int main()
- {
- cout << manip
- << 23
- << '\n' ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- ***23
-
- ==========================================================
-
- Built-in manipulators taking no arguments
-
- Because some output (and input) stream manipulations are done so
- frequently, the header file iostream.h includes some pre-defined
- manipulators. These manipulators may take no arguments, or 1 argument.
- Let's first consider some built-in manipulators that take no arguments.
-
- As the first example, the manipulator endl is designed to output a new
- line character and flush the output buffer. Use may use this in place of
- '\n'. For example:
-
- // EXAMPLE MANIP-02
-
- // THE endl MANIPULATOR
-
- #include <header.h>
-
- int main()
- {
- cout << 1
- << endl
- << 2
- << endl ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- 1
- 2
-
- ==========================================================
-
- The manipulators dec, oct and hex work for both input and output
- operations, and set the stream state accordingly. NOTE: Using Borland C++
- by Atkinson and Atkinson, page 732, says that the hex portion of the
- following program will not work. In fact, it works just fine.
-
- // EXAMPLE MANIP-03
-
- // SETTING THE STREAM STATES USING
- // MANIPULATORS
-
- #include <header.h>
-
- int main()
- {
- cout << "Input a hex number: " ;
- int number ;
- cin >> hex
- >> number ;
- cout << "The number in hex is "
- << hex
- << number
- << endl ;
- cout << "The number in octal is "
- << oct
- << number
- << endl ;
-
- return 0 ;
- }
-
- If you enter the number ff (hex), you will then see the numbers ff (hex)
- and 377 (octal).
-
- ==========================================================
-
- Recall that when using the function get() with 3 arguments to read in
- a string, both leading and embedded whitespace are retained. If you want
- to bypass the leading whitespace, and still retain the embedded
- whitespace, then use the input manipulator ws. Note, however, that it is
- effective only for the next input operation, after which another get()
- would retain leading whitespace.
-
- In this program enter some leading whitespace, then some significant
- characters including embedded whitespace.
-
- // EXAMPLE MANIP-04
-
- // DEMONSTRATE HOW TO READ IN A
- // STRING AND RETAIN EMBEDDED
- // WHITE SPACE, BUT BYPASS
- // LEADING WHITE SPACE
-
- #include <header.h>
-
- const int length = 100 ;
-
- int main()
- {
- char string[length] ;
-
- cout << "Enter a string: " ;
- cin >> ws ;
- cin.get(string , length) ;
- cout << "Your string: "
- << DOUBLE_QUOTE
- << string
- << DOUBLE_QUOTE
- << endl ;
-
- return 0 ;
- }
-
- ==========================================================
-
- Another built-in manipulator that takes no argument is called ends.
- This causes a null character to be output, and is useful for objects of
- type strstream. Here is example OUTPUT-96 again, this time using ends.
-
- // EXAMPLE MANIP-05
-
- // HOW TO DO INCORE OUTPUT IN C++
- // (SAME AS OUTPUT-96 , BUT NOW THE
- // MANIPULATOR ends IS USED)
-
- #include <header.h>
-
- int main()
- {
- int number = 123 ;
- char buffer[80] ;
-
- ostrstream output(buffer , sizeof buffer);
-
- output << "number is "
- << number
- << ends ;
- cout << DOUBLE_QUOTE
- << buffer
- << DOUBLE_QUOTE
- << '\n' ;
-
- output.seekp(0) ;
- output << "A new buffer stream "
- << "of characters"
- << ends ;
- cout << DOUBLE_QUOTE
- << buffer
- << DOUBLE_QUOTE
- << '\n' ;
-
- return 0 ;
- }
-
- ==========================================================
-
- The last manipulator that takes no input argument is called flush.
- This causes the stream associated with the output instance to be
- completely emptied. In point of fact, you probably will never need to use
- this manipulator for several reasons. First, recall that the bit
- ios::unitbuf in the class ios is on by default. This causes all output
- streams to be flushed automatically whenever there is data in them.
- Second, the stream ostream is "tied" to the stream istream by the
- function call:
-
- cin.tie(&cout) ;
-
- so that whenever the operator needs to enter some data from the keyboard,
- any prompting information in the output stream is guaranteed to appear on
- the terminal screen. to "untie" these stream, you may call the tie()
- function with a value of zero.
-
- Therefore, if you really want to avoid flushing the output stream, you
- must (a) turn off the bit ios::unitbuf, and (b) untie the streams.
-
- In this example, the prompt does not appear before the operator must
- enter a number.
-
- ==========================================================
-
- // EXAMPLE MANIP-06
-
- // HOW TO AVOID FLUSHING THE OUTPUT
- // STREAM AUTOMATICALLY
-
- #include <header.h>
-
- int main()
- {
- cout.unsetf(ios::unitbuf) ;
- cin.tie(0) ;
-
- cout << "Enter a number: " ;
- int number ;
- cin >> number ;
- cout << "You entered: "
- << number
- << endl ;
-
- return 0 ;
- }
-
- ==========================================================
-
- But now let's add the flush manipulator.
-
- // EXAMPLE MANIP-07
-
- // ADD THE flush MANIPULATOR TO
- // FORCE THE PROMPT TO APPEAR
-
- #include <header.h>
-
- int main()
- {
- cout.unsetf(ios::unitbuf) ;
- cin.tie(0) ;
-
- cout << "Enter a number: "
- << flush ;
- int number ;
- cin >> number ;
- cout << "You entered: "
- << number
- << endl ;
-
- return 0 ;
- }
-
- ==========================================================
-
- Built-in manipulators taking 1 argument
-
- Because a manipulator that takes 1 argument requires special handling
- by the compiler, a separate header file must be included with each
- program. This file is called iomanip.h. Note that in the file header.h
- it is automatically included.
-
- Perhaps the most frequently used manipulator taking an argument is
- setw. Like its counterpart, the member function width(), it is used to
- set the field width for the next output item only. But since manipulators
- are designed to be coded "in line" with data to be output, they do not
- return a value (which would cause spurious data to appear in the output
- buffer). The one argument is, of course, the field width itself.
-
- Here is example OUTPUT-41 again, this time using a manipulator to set
- the field width.
-
- // EXAMPLE MANIP-11
-
- // HOW TO SET THE FIELD WIDTH
- // USING A MANIPULATOR
-
- #include <header.h>
-
- int main()
- {
- cout << setw(5)
- << 1
- << endl ;
- cout << setw(5)
- << 23
- << endl ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- 1
- 23
-
- ==========================================================
-
- Another frequently used manipulator is the one that sets the fill
- character. It is called setfill and, as you would expect, the 1 argument
- is the fill character itself.
-
- Here is example OUTPUT-52 again, this time using manipulators to set
- the field width and fill character.
-
- // EXAMPLE MANIP-12
-
- // A COMBINATION OF SETTING THE
- // FIELD WIDTH AND SPECIFYING
- // THE FILL CHARACTER USING
- // MANIPULATORS
-
- #include <header.h>
-
- int main()
- {
- cout << setw(5)
- << setfill('0')
- << 1
- << endl ;
- cout << setw(5)
- << setfill('*')
- << 23
- << endl ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- 0001
- ***23
-
- ==========================================================
-
- The other manipulators that take an argument are:
-
- resetiosflags(long flag) -- turns off the bits specified in flag
- (input and output)
- setbase(int base) -- sets the output base to decimal if base if 0 or
- 10; to octal if base is 8; to hexadecimal if base is 16
- (output)
- setiosflags(long flag) -- turns on the bits specified in flag (input
- and output)
- setprecision(int prec) -- sets the number of digits displayed after the
- decimal point to prec (output)
-
- ==========================================================
-
- Creating your own manipulators taking 1 argument
-
- The explanation of how manipulators taking 1 argument are handled by
- the compiler is too complex to be explained here, so let's just see how
- it is coded.
-
- First, the generic form (assuming output) of the manipulator is:
-
- ostream& manipulator-name(ostream& strm , type arg)
- {
- // your code here using arg
- return strm ;
- }
-
- where type is either int or long, and arg is the formal argument name.
-
- Next, you must include this code:
-
- OMANIP(type) manipulator-name(type arg)
- {
- return OMANIP(type) (manipulator-name , arg) ;
- }
-
- where OMANIP is a class defined in iomanip.h.
-
- For example, here is a manipulator called manip that sets the field
- width to whatever the argument happens to be, and also sets the fill
- character to '*'.
-
- // EXAMPLE MANIP-13
-
- // HOW TO CREATE A MANIPULATOR THAT
- // TAKES 1 ARGUMENT
-
- #include <header.h>
-
- ostream& manip(ostream& strm , int length)
- {
- strm << setw(length) << setfill('*') ;
- return strm ;
- }
-
- OMANIP(int) manip(int length)
- {
- return OMANIP(int) (manip , length) ;
- }
-
- int main()
- {
- cout << manip(7)
- << 123
- << endl ;
- cout << manip(5)
- << 45
- << endl ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- ****123
- ***45
-
- ==========================================================
-
- If you wish to use a type other than int or long, then you must
- include the following statement:
-
- IOMANIPdeclare(type) ;
-
- where type is either char, float, double, etc. If the type is "pointer
- to", then it must be typedef'ed before using.
-
- Here is the same example, but now it is the fill character that is
- variable.
-
- // EXAMPLE MANIP-14
-
- // HOW TO CREATE A MANIPULATOR THAT
- // TAKES 1 ARGUMENT, AND THAT ARGUMENT
- // IS NOT int OR long
-
- #include <header.h>
-
- // Don't forget this statement
- IOMANIPdeclare(char) ;
-
- ostream& manip(ostream& strm , char ch)
- {
- strm << setw(7) << setfill(ch) ;
- return strm ;
- }
-
- OMANIP(char) manip(char ch)
- {
- return OMANIP(char) (manip , ch) ;
- }
-
- int main()
- {
- cout << manip('*')
- << 123
- << endl ;
- cout << manip('$')
- << 45
- << endl ;
-
- return 0 ;
- }
-
- The output of this program is:
-
- ****123
- $$$$$45
-
- ==========================================================
-
- Finally, take a look at the header.h file for some useful manipulators
- that have already been defined for you.
-
- ============================================================================
- ============================================================================
-
- CHAPTER 5 -- File I/O
-
- Introduction
-
- File input/output using AT&T version 2.0 involves any of these 3
- operations:
-
- 1) Reading a file
- 2) Writing a file
- 3) Both reading and writing a file
-
- To handle these operations, special classes have already been defined.
- They are:
-
- Read -- class ifstream (derived from istream)
- Write -- class ofstream (derived from ostream)
- Both -- class fstream (derived from iostream)
-
- To use any of these classes, you must have the following statement:
-
- #include <fstream.h>
-
- which automatically includes the header file iostream.h. (The file
- fstream.h is already included in header.h.)
-
- There are no pre-defined instances of these classes comparable to cin
- and cout.Therefore, the first step in using file I/O is to create an
- instance of the appropriate class, e.g.,
-
- ifstream file_in ;
- ofstream file_out ;
- fstream file_both ;
-
- ========================================================
-
- Using the instances
-
- The first step in using the file instances is to open a disk file. In
- any computer language this means establishing a communication link
- between your code and the external file. Each of the 3 classes provides
- the member function called open to do this. The declarations for these
- open functions are as follows:
-
- void ifstream::open(const char* name ,
- int m = ios::in ,
- int prot = filebuf::openprot) ;
-
- void ofstream::open(const char* name ,
- int m = ios::out ,
- int prot = filebuf::openprot) ;
-
- void fstream::open(const char* name ,
- int m ,
- int prot = filebuf::openprot) ;
-
- The first argument is the external file name passed in as a constant
- string literal.
-
- The second argument is the file mode, and comes from a public
- enumerated type in the class ios. There are eight possible modes, as
- follows:
-
- ios::in Input mode. (Default for input file.)
- ios::out Output mode. (Default for output file.)
- ios::app Append to an output file rather than update
- an existing record.
- ios::ate Position file marker at end of file instead of beginning.
- ios::trunc Delete file if it exists and re-create it.
- ios::nocreate File must exist, otherwise open fails (output only)
- ios::noreplace File must not exist, otherwise open fails (output only)
- ios::binary Binary mode; default is text (Binary is a Borland
- enhancement)
-
- Note that for fstream instances, there is no default mode. Obviously,
- these various modes may be bitwise ORed together if more than one is
- desired.
-
- The third argument is the file access. Under Borland C++ the possible
- values are:
-
- 0 = Default
- 1 = Read-only file
- 2 = Hidden file
- 4 = System file
- 8 = Archive bit set
-
- If the open fails, the overloaded operator ! used on the instance will
- return "true". If the open succeeded, the instance itself will return
- "true".
-
- For example:
-
- file_in.open("INPUT") ;
- file_out.open("OUTPUT") ;
- file_both.open("BOTH" , ios::in | ios::out) ;
-
- An alternate method of calling the open function is to call the
- constructor with the same argument(s) that you would use for the open().
- Thus, instead of creating the instance and then explicitly calling the
- open() function, you can combine these two steps by writing:
-
- ifstream file_in("INPUT") ;
- ofstream file_out("OUTPUT") ;
- fstream file_both("BOTH", ios::in | ios::out) ;
-
- When you are done using the file, the member function close() taking
- no arguments will close it. This function is called automatically by the
- destructor for the class, but you may call it explicitly if you wish.
-
- Let's start with a simple program that accepts string input from the
- user and writes it to a disk file called OUTPUT.DAT.
-
- // EXAMPLE FILEIO-01
-
- // CREATE AN OUTPUT FILE AND WRITE
- // WHATEVER DATA THE OPERATOR MAY
- // ENTER
-
- #include <header.h>
-
- const int max = 100 ;
-
- int main()
- {
- char buffer[max] ;
- ofstream file_out("OUTPUT.DAT") ;
- if(!file_out)
- {
- cout << "OPEN FAILED\n" ;
- PAUSE() ;
- exit(1) ;
- }
-
- cout << "Enter a line of data: " ;
- while(!cin.get(buffer , max).eof())
- {
- file_out << buffer << endl ;
- cout << "Next line: " ;
- cin >> FLUSH ;
- }
-
- return 0 ;
- }
-
- ==========================================================
-
- Now give the user a chance to append more records to the file. Note
- that the mode of the file is ios::out | ios::app (although ios::app by
- itself would still have worked).
-
- // EXAMPLE FILEIO-02
-
- // GIVE THE USER A CHANCE TO APPEND
- // RECORDS TO THE FILE
-
- #include <header.h>
-
- const int max = 100 ;
-
- ////////////////////////////////////
-
- int main()
- {
- char buffer[max] ;
- ofstream file_out("OUTPUT.DAT" , ios::out | ios::app) ;
- if(!file_out)
- {
- cout << "OPEN FAILED\n" ;
- PAUSE() ;
- exit(1) ;
- }
-
- cout << "Enter a line of data: " ;
- while(!cin.get(buffer , max).eof())
- {
- file_out << buffer << endl ;
- cout << "Next line: " ;
- cin >> FLUSH ;
- }
-
- return 0 ;
- }
-
- ==========================================================
-
- Finally, this program numbers and prints the records that were just
- written.
-
- // EXAMPLE FILEIO-03
-
- // NOW READ THE FILE THAT WAS JUST
- // CREATED
-
- #include <header.h>
-
- const int max = 100 ;
-
- ////////////////////////////////////
-
- int main()
- {
- char buffer[max] ;
- ifstream file_in("OUTPUT.DAT") ;
- if(!file_in)
- {
- cout << "OPEN FAILED\n" ;
- PAUSE() ;
- exit(1) ;
- }
-
- int rec = 0 ;
- while(!file_in.get(buffer , max).eof())
- {
- cout << "Record #"
- << ++rec
- << ": "
- << buffer
- << endl ;
- file_in >> FLUSH ;
- }
-
- return 0 ;
- }
-
- ==========================================================
-
- The file position markers
-
- So that the file I/O classes can keep track of where in a file the
- data is to be written to and read from, they establish what is called a
- "file position marker" (fpm). On Turbo C++ this marker has been
- typedefed as a long integer representing an offset value from the
- beginning of the file. In point of fact, there are two such markers, one
- for reading, and one for writing. You may alter these markers by using
- two member functions: seekg() and seekp(). seekg() is associated with
- the file's "get or read" pointer, and seekp() with the file's "put or
- write" pointer. The declarations for these two functions are as follows:
-
- istream& istream::seekg(streampos offset) ;
- istream& istream::seekg(streamoff offset , seek_dir) ;
-
- ostream& ostream::seekp(streampos offset) ;
- ostream& ostream::seekp(streamoff offset , seek_dir) ;
-
- where streampos and streamoff represent long integers, and seek_dir is
- an enumerated type defined as follows:
-
- enum seek_dir {ios::beg , ios::cur , ios::end} ;
-
- If the 1-argument form of the function is used, then offset is the offset
- from the beginning of the file. If the 2-argument form is used, then
- offset is the offset number of bytes (positive or negative) from the
- absolute seek_dir position. Therefore, a call to seekg() with an
- argument of 0 causes the file to rewind and data to be read starting with
- the first record.
-
- To find out the positions of these markers, you may use the member
- functions tellg() and tellp(). They are declared as follows:
-
- streampos istream::tellg() ;
- streampos ostream::tellp() ;
-
- The first character of a record is deemed to be in position 0. Note that
- for text files a newline character is actually stored as 2 characters: a
- newline ('\n') and a carriage return ('\r').
-
- To illustrate this, here is the previous example of writing and
- reading a file, but now it uses an instance of the class fstream so that
- the output and input can be combined. After each record is read and
- printed, the file position marker (fpm) is displayed.
-
- // EXAMPLE FILEIO-04
-
- // HOW TO WRITE AND READ A FILE IN
- // THE SAME PROGRAM
-
- #include <header.h>
-
- const int max = 100 ;
-
- ////////////////////////////////////
-
- int main()
- {
- char buffer[max] ;
- fstream file_both("BOTH.DAT" ,
- ios::in |
- ios::out |
- ios::trunc ) ;
- if(!file_both)
- {
- cout << "OPEN FAILED\n" ;
- PAUSE() ;
- exit(1) ;
- }
-
- cout << "Enter a line of data: " ;
- while(!cin.get(buffer , max).eof())
- {
- file_both << buffer << endl ;
- cout << "Next line: " ;
- cin >> FLUSH ;
- }
-
- // Flush the output buffer
- file_both << flush ;
-
- // Return to the start of the file
- file_both.seekg(0) ;
-
- // Read and print the records, and
- // show the file position marker
- int rec = 0 ;
- while(!file_both.get(buffer , max).eof())
- {
- cout << "Record #"
- << ++rec
- << ": "
- << buffer
- << endl ;
- cout << "fpm is: "
- << file_both.tellg()
- << endl ;
- file_both >> FLUSH ;
- }
-
- return 0 ;
- }
-
- ==========================================================
-
- This program gives the user complete flexibility as to the name of the
- external file to be manipulated, and modes to be used. All such variables
- are entered from the DOS command line.
-
- // EXAMPLE FILEIO-05
-
- // GIVE THE USER COMPLETE FLEXIBILITY
- // AS TO THE MODE OF THE FILE AND THE
- // DATA WRITTEN AND READ
-
- // ARGUMENTS ARE ENTERED FROM THE DOS
- // COMMAND LINE. THE FIRST ARGUMENT
- // IS THE DISK FILE NAME, AND THE
- // REMAINING ARGUMENTS REPRESENT THE
- // VARIOUS OPEN MODES, EXACTLY AS
- // SPECIFIED BY THE ENUMERATED TYPES
-
- // NOTE THAT A CLASS IS NOW USED TO
- // CONTROL THE VARIOUS OPERATIONS ON
- // A FILE OBJECT
-
- #include <header.h>
-
- const int max = 100 ;
-
- class file
- {
- fstream file_object ;
- public:
-
- int open(int argc , char* argv[]) ;
- void read() ;
- void write() ;
- void beginning() ;
- void end() ;
- void print() ;
- void close() ;
- } ;
-
- // Open the file by setting up the
- // field 'mode' with the OR of what-
- // ever modes the user has chosen
-
- int file::open(int argc , char* argv[])
- {
- int mode = 0 ;
-
- for(int i = 2 ; i < argc ; ++i)
- {
- if(!strcmp (argv[i] , "out"))
- mode |= ios::out ;
- else if(!strcmp (argv[i] , "in"))
- mode |= ios::in ;
- else if(!strcmp (argv[i] , "app"))
- mode |= ios::app ;
- else if(!strcmp (argv[i] , "ate"))
- mode |= ios::ate ;
- else if(!strcmp (argv[i] , "trunc"))
- mode |= ios::trunc ;
- else if(!strcmp (argv[i] , "nocreate"))
- mode |= ios::nocreate ;
- else if(!strcmp (argv[i] , "noreplace"))
- mode |= ios::noreplace ;
- else if(!strcmp (argv[i] , "binary"))
- mode |= ios::binary ;
- else
- cout << "Invalid mode: "
- << argv[i]
- << endl ;
- }
-
- // Perform the actual open
- file_object.open(argv[1] , mode) ;
-
- // If an error occurred, return
- // "true"
- return (!file_object) ;
- }
-
- // Read the a record
-
- void file::read()
- {
- char buffer[max] ;
-
- cout << "Data line: " ;
- file_object.get(buffer , max) ;
- if(!(file_object.eof()))
- cout << buffer << endl ;
- else
- cout << "EOF\n" ;
-
- file_object >> FLUSH ;
- }
-
- // Write the file
-
- void file::write()
- {
- char buffer [max] ;
-
- cout << "Enter some data: " ;
- cin.get(buffer , max) ;
- cin >> FLUSH ;
- file_object << buffer << endl ;
- // Might be writing to a read-only
- // file
- file_object.clear() ;
- }
-
- // Return to the start of the file
-
- void file::beginning()
- {
- file_object.seekg(0) ;
- file_object.seekp(0) ;
- }
-
- // Seek to the end of the file
-
- void file::end()
- {
- file_object.seekg(0 , ios::end) ;
- file_object.seekp(0 , ios::end) ;
- }
-
- // Print the entire file
-
- void file::print()
- {
- long position = file_object.tellg() ;
- char buffer[max] ;
- int rec = 0 ;
- file_object.seekg(0) ;
- while(!file_object.get(buffer , max).eof())
- {
- cout << "Record #"
- << ++rec
- << ": "
- << buffer
- << endl ;
- file_object >> FLUSH ;
- }
- cout << endl ;
- file_object.seekg(position) ;
- file_object.clear() ;
- }
-
- // Close the file
-
- void file::close()
- {
- file_object.close() ;
- }
-
- ///////////////////////////////////
-
- char menu() ;
-
- int main(int argc , char* argv [])
- {
- file my_file ;
-
- if(argc < 2)
- {
- cout << "NO FILE NAME\n" ;
- exit(1) ;
- }
-
- if(my_file.open(argc , argv))
- {
- cout << "OPEN FAILED\n" ;
- exit(2) ;
- }
-
- char ch ;
- while((ch = menu()) != 'X')
- {
- switch(ch)
- {
- case 'R' : my_file.read() ;
- break ;
- case 'W' : my_file.write() ;
- break ;
- case 'B' : my_file.beginning() ;
- break ;
- case 'E' : my_file.end() ;
- break ;
- case 'P' : my_file.print() ;
- break ;
- default : cout << "INVALID\n" ;
- break ;
- }
- }
- my_file.close() ;
-
- return 0 ;
- }
-
- ///////////////////////////////////////
-
- char menu()
- {
- cout << "\t(R)ead a record\n" ;
- cout << "\t(W)rite a record\n" ;
- cout << "\t(B)eginning of file\n" ;
- cout << "\t(E)nd of the file\n" ;
- cout << "\t(P)rint the file\n" ;
- cout << "\te(X)it\n" ;
-
- cout << "\n\tYour choice: " ;
- char ch ;
- cin >> ch >> FLUSH ;
- return (toupper(ch)) ;
- }
-
- ==========================================================
-
- Using the line printer
-
- The line printer is just another output file insofar as DOS is
- concerned. To redirect output to a printer from within your program, use
- the predefined name prn.
-
- // EXAMPLE FILEIO-06
-
- // HOW TO PRINT FROM WITHIN A
- // PROGRAM
-
- #include <header.h>
-
- int main()
- {
- ofstream printer ;
- // "prn" is the DOS printer
- printer.open("prn") ;
- if (!printer)
- {
- cerr << "Can't open the printer\n" ;
- exit(1) ;
- }
- printer << "This line appears"
- << " on the printer\n" ;
-
- return 0 ;
- }
-
-