C++ by Dissection PDF
C++ by Dissection PDF
C++ by Dissection PDF
Ira Pohl
University of California
Santa Cruz
Access the latest information about Addison-Wesley titles from our World Wide Web site:
http://www.aw.com/cs
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as
tradmarks. Where those designations appear in this book, and Addison-Wesley was aware of a trademark
claim, the designations have been printed in initial caps or all caps.
The programs and applications presented in this book have been included for their instructional value.
They have been tested with care, but are not guaranteed for any particular purpose. The publisher does
not offer any warranties or representations, nor does it accept any liabilities with respect to the pro-
grams or applications.
Pohl, Ira
C++ by Dissection / Ira Pohl.
p. cm.
ISBN 0-201-74396-5 (pbk.)
1. C++ (Computer program language) I. Title.
QA76.73.C153 P58 2002
005.13’3--dc21 2001045829
CIP
Copyright © 2002 by Addison-Wesley
All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or trans-
mitted, in any form or by any means, electronic, mechanical, photocopying, recording, or otherwise,
without the prior written permission of the publisher. Printed in the United States of America.
ISBN 0-201-74396-5
About Ira Pohl
T oday, the ANSI C++ programming language is widely used throughout the world in
both academia and industry. In many educational institutions it is the language of
choice for a first programming course and for a language to be used for computer sci-
ence instruction. A key reason for this is that C++ has drifted down the curriculum
from more advanced courses to more introductory courses. Further, C++ comes with
many useful libraries, and is supported by sophisticated integrated environments. It is
a language that efficiently supports object-oriented programming (OOP) the dominant
contemporary programming methodology.
C++ by Dissection presents a thorough introduction to the programming process by
carefully developing working programs to illuminate key features of the C++ program-
ming language. Program code is explained in an easy-to-follow, careful manner through-
out. The code has been tested on several platforms and is found on the bundled CD-
rom accompanying this text. The code in C++ By Dissection can be used with most C++
systems, including those found in operating systems such as MacOS, MS-DOS, OS/2,
UNIX, and Windows.
C++, invented at Bell Labs by Bjarne Stroustrup in the mid-1980s, is a powerful, mod-
ern, successor language to C. C++ adds to C the concept of class, a mechanism for pro-
viding user-defined types, also called abstract data types. C++ supports object-oriented
programming by these means and by providing inheritance and runtime type binding.
Ira Pohl’s C++ by Dissection Dissections vii
Dissections
This book presents readers with a clear and thorough introduction to the programming
process by carefully developing working C++ programs, using the method of dissection.
Dissection is a unique pedagogical tool first developed by the author in 1984 to illumi-
nate key features of working code. A dissection is similar to a structured walk-through
of the code. Its intention is to explain to the reader newly encountered programming
elements and idioms as found in working code. Programs and functions are explained
in an easy-to-follow step-by-step manner. Key ideas are reinforced throughout by use in
different contexts.
No Background Assumed
This book assumes no programming background and can be used by students and first
time computer users. Experienced programmers not familiar with C++ will also benefit
from the carefully structured presentation of the C++ language. For student use, the
book is intended as a first course in computer science or programming.
It is suitable for a CS1 course or beginning programming course for other disciplines.
Each chapter presents a number of carefully explained programs, which lead the stu-
dent in a holistic manner to ever-improving programming skills. From the start, the stu-
dent is introduced to complete programs, and at an early point in the text is introduced
to writing functions as a major feature of structured programming. The function is to
the program as the paragraph is to the essay. Competence in writing functions is the
hallmark of the skilled programmer and hence is emphasized. Examples and exercises
are plentiful in content and level of difficulty. They allow instructors to pick assign-
ments appropriate to their audiences.
Ira Pohl’s C++ by Dissection Special Features viii
Special Features
Chapter Features
Programming Style and Software Engineering. Programming style and software method-
ology is stressed throughout. Important concepts such as structured branching state-
ments, nested flow of control, top-down design, and object-oriented programming are
presented early in the book. A consistent and proper coding style is adopted from the
beginning with careful explanation as to its importance and rationale. The coding style
used in the book is one commonly used by working programming professionals in the
C++ community.
Working Code. Right from the start the student is introduced to full working programs.
With the executable code, the student can better understand and appreciate the pro-
gramming ideas under discussion. Many programs and functions are explained through
dissections. Variations on programming ideas are often presented in the exercises.
Common Programming Errors. Many typical programming bugs, along with techniques
for avoiding them, are described. Much of the frustration of learning a programming
language is caused by encountering obscure errors. Many books discuss correct code
but leave the reader to a trial-and-error process for finding out about bugs. This book
explains how typical errors in C++ are made and what must be done to correct them.
Dr. P’s Prescriptions. A series of programming tips is based on wide experience. A con-
cise rationale is given for each tip.
Comparison to Java. An optional section describes the programming elements of Java
that are comparable to the C++ examples. Exercises supporting these sections are
included as well. For the most part, C++ and Java have equivalent elements. The text
aids the student already conversant in Java to migrate to C++. Also the C++ student who
later takes up Java will benefit from this section. Furthermore, as the book is a compan-
ion volume to Java by Dissection (with Charlie McDowell) the reader has access to com-
plete explanations of the Java concepts fully utilizing this book’s pedagogy.
Summary. A succinct list of points covered in the chapter serves as a review for the
reader, reinforcing the new ideas that were presented in the chapter.
Exercises. The exercises test the student’s knowledge of the language. Many exercises
are intended to be done interactively while reading the text. This encourages self-paced
instruction by the reader. In addition to exercising features of the language, some exer-
cises look at a topic in more detail, and others extend the reader’s knowledge to an
advanced area of use.
Classroom Usage
This book can be used as a text in a one-semester course that teaches students how to
program. Chapters 1 through 5 cover the C++ programming language through the use
of arrays, pointers, and basic object programming. A second-semester course can be
devoted to more advanced data types, OOP, generic programming and STL, file process-
ing, and software engineering as covered in Chapters 6 through 11. In a course designed
for students who already have some knowledge of programming, not necessarily in
C++, the instructor can cover all the topics in the text. This book can also be used as a
text in other computer science courses that require the student to use C++. In a compar-
ative language course, it can be used with companion volumes for C, Java, and C# that
Ira Pohl’s C++ by Dissection Interactive Environment x
follow the same dissection approach and share many of the same examples done
uniquely in each language.
Interactive Environment
This book is written explicitly for an interactive environment. Experimentation via key-
board and screen is encouraged throughout. For PCs, there are many vendors that sup-
ply interactive C++ systems, including Borland, IBM, Metroworks, Microsoft, and
Symantec.
Professional Use
While intended for the beginning programmer, C++ by Dissection: The Essentials of C++
Programming is a friendly introduction to the entire language for the experienced pro-
grammer as well. In conjunction with A Book on C, Fourth Edition by Al Kelley and Ira
Pohl (Addison Wesley Longman, Inc., Reading, MA, 1998, ISBN 0-201183994), the com-
puter professional will gain a comprehensive understanding of both languages. As a
package, the two books offer an integrated treatment of the C/C++ programming lan-
guage and its use that is unavailable elsewhere. Furthermore, in conjunction with Java
by Dissection by Ira Pohl and Charlie McDowell (Addison Wesley Longman, Inc., Reading,
MA, 1999, ISBN 0-201-61248-8), the student or professional is also given an integrated
treatment of the object-oriented language Java.
This book is the basis of many on-site professional training courses given by the author,
who has used its contents to train professionals and students in various forums since
1986. The text is the basis for Web-based training in C++ available from www.digi-
talthink.com.
Supplements
Support materials are available to instructors adopting this textbook for classroom use
and include the following:
■ Solutions to exercises
■ Code for example programs
■ Powerpoint slides of all the figures
Please check on-line information for this book at www.aw.com/cssupport for more
information on obtaining these supplements.
Ira Pohl’s C++ by Dissection Acknowledgments xi
Acknowledgments
Our special thanks go to Uwe F. Mayer, George Belotsky, and Bruce Montague, who were
careful readers of the technical content of this work and suggested numerous improve-
ments, without being responsible for my errors. Thanks to our reviewers, Charles
Anderson, Colorado state University; Parris Egbert, Brigham Young University; Chris
Eagle, Naval Postgraduate School; Nigel Gwee, Louisiana State University; Stephen P.
Leach, Florida State University; and Steven C. Shaffer, Penn State University. Thanks also
to John dePillis, Debra Dolsberry and Laura Pohl who developed and drew many of the
cartoons. Most importantly further thanks to Debra Dolsberry, who acted as the chief
technical editor for much of the material in this book and the CD-Rom. In addition, she
was largely responsible for using FrameMaker to create files suitable for typesetting
this book. Thanks also to Charlie McDowell and Al Kelley for writing companion vol-
umes in C and Java.
We would also like to thank Maite Suarez-Rivas, Acquisitions Editor, Katherine Harutu-
nian, Project Editor, and Patty Mahtani, Associate Managing Editor for their enthusiasm,
support, and encouragement; and we would like to thank Caroline Roop and Sally Boy-
lan at Argosy, for the careful attention to the production of this book.
Ira Pohl
University of California, Santa Cruz
Table of Contents
9 Input/Output 366
9.1 The Output Class ostream . . . . . . . .. . . . . . ............ 366
9.2 Formatted Output and iomanip . . . .. . . . . . ............ 367
9.3 User-Defined Types: Output. . . . . . .. . . . . . ............ 372
9.4 The Input Class istream. . . . . . . . . .. . . . . . ............ 374
9.5 Files . . . . . . . . . . . . . . . . . . . . . . . .. . . . . . ............ 375
9.6 Using Strings as Streams . . . . . . . . .. . . . . . ............ 379
9.7 The Functions and Macros in ctype .. . . . . . ............ 380
9.8 Using Stream States. . . . . . . . . . . . .. . . . . . ............ 380
9.9 Mixing I/O Libraries. . . . . . . . . . . . .. . . . . . ............ 383
9.10 Software Engineering: I/O . . . . . . . .. . . . . . ............ 384
9.11 Dr. P’s Prescriptions . . . . . . . . . . . .. . . . . . ............ 386
9.12 C++ Compared with Java . . . . . . . . .. . . . . . ............ 386
Summary . . . . . . . . . . . . . . . . . . . . .. . . . . . ............ 389
Review Questions . . . . . . . . . . . . . .. . . . . . ............ 390
Exercises . . . . . . . . . . . . . . . . . . . . .. . . . . . ............ 391
Ira Pohl’s C++ by Dissection xviii
Index 482
CHAPTER 1
T his chapter introduces the reader to the ANSI C++ programming world. Some gen-
eral ideas on programming are discussed, and a number of elementary programs are
thoroughly explained. The basic ideas presented here become the foundation for more
complete explanations that occur in later chapters. An emphasis is placed on the basic
input/output functions of C++. Getting information into and out of a machine is the
first task to be mastered in any programming language.
C++ uses the operators << and >> for output and input, respectively. The use of both of
these operators is explained. Other topics discussed in this chapter include the use of
variables to store values and the use of expressions and assignments to change the
value of a variable.
Throughout this chapter and throughout the text, many examples are given. Included
are many complete programs, which often are dissected. This allows the reader to see
in detail how each construct works. Topics that are introduced in this chapter are seen
again in later chapters, with more detailed explanation where appropriate. This spiral
approach to learning emphasizes ideas and techniques essential for the C++ program-
mer.
C++ is largely a superset of C. By learning C++, you are also learning the kernel language
C. A companion book, C by Dissection: Fourth Edition, by Al Kelley and Ira Pohl
(Addison-Wesley, 2000), teaches the rest of C that is not found here.
Most chapters also have a comparison between C++ and Java programs. Java is partly
based on C++. However, unlike C++, some C concepts do not work in Java or have a dif-
ferent meaning. Increasingly, people who begin to program in C++ have started from a
Java background. An introduction to the Java programming process can be found in the
companion volume Java by Dissection, by Ira Pohl and Charlie McDowell (Addison-
Wesley, 1999). The modern programmer needs to be comfortable in all three C-based
languages.
Ira Pohl’s C++ by Dissection 1.1 Getting Ready to Program 2
1.1
1.1 Getting Ready to Program
Programs are written to instruct machines to carry out specific tasks or to solve specific
problems. A step-by-step procedure that accomplishes a desired task is called an algo-
rithm. Thus, programming is the activity of communicating algorithms to computers.
We have all given instructions to someone in English and then had that person carry out
the instructions. The programming process is analogous, except that machines have no
tolerance for ambiguity and must have all steps specified in a precise language and in
tedious detail.
When we compile a simple program, three separate actions occur: First the preproces-
sor is invoked, then the compiler, and finally the linker. The preprocessor modifies a
copy of the source code by including other files and by making other changes. The com-
piler translates this into object code, which the linker then uses to produce the final exe-
cutable file. A file that contains object code is called an object file. Object files, unlike
source files, usually are not read by humans. When we speak of compiling a program,
we really mean invoking the preprocessor, the compiler, and the linker. For a simple
program, this is all done with a single command.
After the programmer writes a program, it has to be compiled and tested. If modifica-
tions are needed, the source code has to be edited again. Thus, part of the programming
process consists of this cycle:
When the programmer is satisfied with the program performance, the cycle ends.
1.1
1.1 A First Program
A first task for anyone learning to program is to print on the screen. Let us begin by
writing the traditional first C++ program which prints the phrase Hello, world! on the
screen. The complete program is
In file hello1.cpp
// Hello world in C++
// by Olivia Programmer
int main()
{
cout << "Hello, world!" << endl;
}
Using the text editor, the programmer types this code into a file ending in .cpp. The
choice of a file name should be mnemonic. Let us suppose hello.cpp is the name of the
file in which the program has been written. When this program is compiled and exe-
cuted, it prints the following message:
Hello, world!
Ira Pohl’s C++ by Dissection 1.1 A First Program 4
■ int main()
{
A C++ program is a collection of declarations and functions that
begins executing with the function main().
■ cout << "Hello, world!" << endl;
The identifier cout is defined in iostream as the standard output stream con-
nected by most C++ systems to the screen. The identifier endl is a standard
manipulator that flushes the output buffer, printing everything to that point
and going to a new line. The operator << is the put-to output operator, which
writes out what comes after it to cout.
Note that without the using std statement, we could have written
return 0;
just before the closing brace.
The expression cout << some string is used to print across the screen. It moves to a
new line when a newline character is read or it sees the endl. The screen is a two-
dimensional display that prints from left to right and top to bottom. To be readable,
output must appear properly spaced on the screen.
Let us rewrite our program to make use of two cout statements. Although the program
is different from our first one, its output is the same.
Ira Pohl’s C++ by Dissection 1.1 A First Program 6
In file hello2.cpp
// Hello world in C++
// by Olivia Programmer
// Version 2
In file hello3.cpp
// Hello universe in C++
// by Olivia Programmer
Hello, world!
Hello, universe!
Notice that the two cout statements in the body of main() could be replaced by the sin-
gle statement
1.2
1.2 Problem Solving: Recipes
Computer programs are detailed lists of instructions for performing a specific task or
solving a particular type of problem. Instruction lists, called algorithms, are commonly
found in everyday situations. Examples include instructions for cooking a meal, knitting
a sweater, and registering for classes. Examining one of these examples is instructive.
Consider this recipe for preparing a meat roast.
The recipe is typically imprecise—what does sprinkle mean, where exactly is the ther-
mometer to be inserted, and what is a sufficient amount of pan drippings? However, the
recipe can be formulated more precisely as a list of instructions by reading between the
lines.
Cooking a Roast
1. Sprinkle roast with 1/8 teaspoon salt and pepper.
2. Turn oven on to 150ºC.
3. Insert meat thermometer into center of roast.
4. Wait a few minutes.
5. If oven does not yet register 150ºC, go back to step 4.
6. Place roast in oven.
7. Wait a few minutes.
8. Check meat thermometer. If temperature is less than 80ºC, go back to step 7.
9. Remove roast from oven.
10. If there is at least 1/2 cup of pan drippings, go to step 12.
11. Prepare gravy from meat stock and go to step 13.
12. Prepare gravy from pan drippings.
13. Serve roast with gravy.
These steps comprise three categories of instructions and activities—those that involve
manipulating or changing the ingredients or equipment, those that just examine or test
the state of the system, and those that transfer to the next step. Steps 1 and 6 are exam-
ples of the first category; the temperature test in step 8 and the pan drippings test in
Ira Pohl’s C++ by Dissection 1.2 Problem Solving: Recipes 8
step 10 are instances of the second category; and transfers in steps 5 and 8 (go to step
x) are examples of the last category.
By using suitable graphical symbols for each of these categories, a simple two-dimen-
sional representation of our cooking algorithm can be obtained, as shown in the follow-
ing illustration:
Insert No
thermometer
Prepare gravy
Roast at 80º from stock
Wait a few No
minutes
Such a figure is called a flowchart. To perform the program (prepare the roast), just fol-
low the arrows and the instructions in each box. The manipulation activities are con-
tained in rectangles, the tests are shown in diamonds, and the transfer or flow of
control is determined by the arrows. Because of their visual appeal and clarity, flow-
charts are often used instead of lists of instructions for informally describing pro-
grams. Some cookbook authors even employ flowcharts extensively.
Change-Making Algorithm
1. Assume that the price is written in a box labeled price.
2. Subtract the value of price from 100 and place it in a box labeled change.
3. Divide the value in change by 10, discard the remainder, and place the result
in a box labeled dimes.
4. Take the integer remainder of change divided by 10 and place it in a box
labeled pennies.
5. Print out the values in the boxes dimes and pennies with appropriate labels.
6. Halt.
This algorithm has four boxes, namely, price, change, dimes, and pennies. Let’s exe-
cute this algorithm with the values given. Suppose that the price is 77 cents. Always
start with the first instruction. The contents of the four boxes at various stages of exe-
cution are shown in Table 1.1.
To execute step 1, place the first number, 77, in the box price. At the end of instruc-
tion 2, the result of subtracting 77 from 100 is 23, which is placed in the box change.
Each step of the algorithm performs a small part of the computation. By step 5, the cor-
rect values are in their respective boxes and are printed out. Study the example until
you’re convinced that this algorithm works correctly for any price under one dollar. A
good way to do so is to act the part of a computer following the recipe. Following a set
of instructions in this way, formulated as a computer program, is called hand simula-
tion or bench testing. It is a good way to find errors in an algorithm or program. In com-
puter parlance, these errors are called bugs, and finding and removing them is called
debugging.
We executed the change-making algorithm by acting as an agent, mechanically following
a list of instructions. The execution of a set of instructions by an agent is called a com-
putation. Usually, the agent is a computer; in that case, the set of instructions is a com-
puter program. In the remainder of this book, unless explicitly stated otherwise, we use
program to mean computer program.
The algorithm for making change has several important features that are characteristic
of all algorithms.
Ira Pohl’s C++ by Dissection 1.3 Implementing Our Algorithm in C++ 10
Algorithms
■ The sequence of instructions will terminate.
■ The instructions are precise. Each instruction is unambiguous and subject to
only one interpretation.
■ The instructions are simple to perform. Each instruction is within the capabil-
ities of the executing agent and can be carried out exactly in a finite amount
of time; such instructions are called effective.
■ There are inputs and outputs. An algorithm has one or more outputs
(answers) that depend on the particular input data.
Our description of the change-making algorithm, although relatively precise, is not writ-
ten in any formal programming language. Such informal notations for algorithms are
called pseudocode, whereas real code is something suitable for a computer. Where
appropriate, we use pseudocode to explain an algorithm or computation to you without
all the necessary detail needed by a computer.
The term algorithm has a long, involved history, originally stemming from the name of
a well-known Arabic mathematician of the ninth century, Abu Jafar Muhammed Musa
al-Khwarizim. It later became associated with arithmetic processes and then, more par-
ticularly, with Euclid’s algorithm for computing the greatest common divisor of two
integers. Since the development of computers, the word has taken on a more precise
meaning that defines a real or abstract computer as the ultimate executing agent—any
terminating computation by a computer is an algorithm, and any algorithm can be pro-
grammed for a computer.
1.3
1.3 Implementing Our Algorithm in C++
In file change.cpp
// Change in dimes and pennies
#include <iostream>
using namespace std;
int main ()
{
int price, change, dimes, pennies;
1.4
1.4 Software Engineering: Style
A good coding style is essential to the art of programming. It facilitates the reading,
writing, and maintenance of programs. A good style uses white space and comments so
that the code is easier to read and understand, and is visually attractive. Another impor-
tant stylistic point is to choose names for variables that convey their use in the program
to further aid understanding. A good style avoids error-prone coding habits.
Software needs to be maintained. Frequently, maintenance costs are higher than the
cost of initially writing the code. Good programming style is part of good documenta-
tion, and programs need to be readable. This includes commenting the code, choice of
identifiers, and associated documentation, such as a manual page or online help.
In this text, we are following the Bell Laboratories industrial programming style. We
place all #includes, int main()s, and braces { and } that begin and end the body of
main() in the leftmost position on the line:
#include <iostream>
int main()
{
·····
}
Ira Pohl’s C++ by Dissection 1.5 Common Programming Errors 13
The declarations and statements in the body of main() are indented three spaces. This
visually highlights the beginning and ending of the function body. There is one blank
line following the #includes, and one between the declarations and statements in the
body of main().
An indentation of two, three, four, five, or eight spaces is common. We use three spaces.
Whatever is chosen as an indentation should be used consistently. To heighten readabil-
ity, we put a blank space on each side of the binary operators. Some programmers do
not bother with this, but it is part of the Bell Labs style.
There is no single agreed-upon good style. As we proceed through this text, we often
point out alternate styles. Once you choose a style, use it consistently. Good habits rein-
force good programming. Caution: Beginning programmers sometimes think they
should dream up their own distinctive coding style. This should be avoided. The pre-
ferred strategy is to choose a style that is already in common use.
1.5
1.5 Common Programming Errors
When you first start programming, you make many frustrating, simple errors. One such
error is to leave off a closing double quote character to mark the end of a string. When
the compiler sees the first ", it starts collecting all the characters that follow as a string.
If the closing " is not present, the string continues to the next line, causing the compiler
to complain. Error messages vary from one compiler to another. Here is one possibility:
1.6
1.6 Writing and Running a C++ Program
The precise steps you have to follow to create a file containing C++ code and to compile
and execute it depend on three things: the operating system, the text editor, and the
compiler. However, in all cases, the general procedure is the same. We first describe in
some detail how it is done in a UNIX environment. Then we discuss how it is done in a
Windows environment.
In the discussion that follows, we use the CC command to invoke the C++ compiler. In
reality, however, the command depends on the compiler that is being used. For exam-
ple, if we were using the command line version of the Borland C++ compiler, we would
use the command bcc or bcc32.
This moves a.out to hello. Now the program can be executed with the command
hello
In UNIX, it is common practice to give the executable file the same name as the corre-
sponding source file, except to drop the .cpp suffix. If we wish, we can use the -o option
to direct the output of the CC command. For example, the command
CC –o hello hello.cpp
causes the executable output from CC to be written directly into hello, leaving intact
whatever is in a.out.
Different kinds of errors can occur in a program. Syntax errors are caught by the com-
piler, whereas runtime errors manifest themselves only during program execution. For
example, if an attempt to divide by zero is encoded into a program, a runtime error may
occur when the program is executed.
Let us now consider the Windows environment. Here, some other text editor would
most likely be used. Some C++ systems, such as Borland C++, have both a command line
environment and an integrated environment. The integrated environment includes both
the text editor and the compiler. In Windows, the executable output produced by a C++
compiler is usually written to a file that has the same name as the source file, but with
the extension .exe instead of .cpp. Suppose, for example, we are using the command line
environment in Borland C++. If we give the command
bcc hello.cpp
then the executable code is written to hello.exe. To execute the program, we give the
command
hello.exe
or, equivalently,
hello
To invoke the program, we do not need to type the .exe extension. If we wish to rename
this file, we can use the rename command.
1.7
1.7 Dr. P’s Prescriptions
1.8
1.8 C++ Compared with Java
Increasingly, beginning programmers start by studying Java. The roots of C++ and Java
both are found in C. Most serious programmers will end up learning all three languages.
This book is coordinated in its treatment with the book Java by Dissection, by Pohl and
McDowell, (Addison-Wesley, 1999) and with the book C by Dissection, Fourth Edition, by
Kelley and Pohl (Addison Wesley, 2000). These comparison sections are an enrichment
for those readers who already know or wish to know Java. If they are a distraction to
others, they can be skipped.
In this section, we implement our change-making algorithm from Section 1.2.1, Algo-
rithms—Being Precise, on page 9, in the Java programming language. This is taken from
Java by Dissection, pages 5–7.
In file MakeChange.java
// Change in dimes and pennies
import tio.*; // use the package tio
class MakeChange {
public static void main (String[] args) {
int price, change, dimes, pennies;
Throughout this book we use the tio package in order to simplify the input and output
required for Java. The source code is presented in Appendix D, The tio Library, and is
available for download on the Web at ftp://ftp.awl.com/cseng/authors/pohl-mcdowell/.
You can also view it at http://www.cse.ucsc.edu/~charlie/java/tio/.
Ira Pohl’s C++ by Dissection Summary 20
Summary
Review Questions
1. C++ uses the operators << and >> for and , respectively.
3. The operating system has two main purposes. First, the operating system oversees
and coordinates of the machine as a whole. Second, the operating system pro-
vides .
8. The text uses style. There is following the #includes, and between the decla-
rations and statements in the body of main(). An of two, three, four, five, or
eight spaces is common.
Exercises
(a) all on one line, (b) on seven lines, and (c) inside a box.
2. Here is part of a program that begins by having the user input three integers:
#include <iostream>
using namespace std;
int main()
{
int a, b, c, sum;
// Print L A U R A
#include <iostream.h>
int main()
{
cout << "L A U U RRRRR A" << endl;
cout << "L A A U U R R A A" << endl;
cout << "L A A U U R R A A" << endl;
cout << "LLL A A UUUUU R R A A "<<endl;
cout << endl;
Ira Pohl’s C++ by Dissection Exercises 23
// Print P O H L
4. The purpose of this exercise is to help you become familiar with some of the error
messages produced by your compiler. You can expect some error messages to be
helpful and others to be less so. Correct each syntax error.
#include <iostreem>
using namespace st;
int main()
{
int a = 1, b = 2, c = 3,
5. Here is part of an interactive program that computes the sum of the value of some
coins. The user is asked to input the number of half dollars, quarters, dimes, etc.
#include <iostream>
using namespace std;
int main()
{
int h, // number of half dollars
q, // number of quarters
d, // number of dimes
n, // number of nickels
p; // number of pennies
·····
cout << "Your change will be computed."<< endl;
cout << "Enter how many half dollars.";
cin >> h;
cout << "\nEnter how many quarters.";
cin >> q;
·····
Ira Pohl’s C++ by Dissection Exercises 24
Complete the program, causing it to print out relevant information. For example,
you may want to create output that looks like this:
Notice that pennies is plural, not singular as it should be. After you learn about the
if-else statement in Section 2.8.3, The if and if-else Statements, on page 52,
you can modify your program so that its output is grammatically correct.
6. Modify the program that you wrote in the previous exercise so that the last line of
the output looks like this:
7. The purpose of this exercise is to find out what happens on your system when a
runtime error occurs. Try the following code:
int a = 1, b = 0;
cout << "int division by zero:" << a/b << endl;
On a UNIX system, you might get a core dump. That is, the system might create a file
named core that contains information about the state of your program just before it
ended abnormally. This file is not meant to be read by humans. A debugger can use
the core dump to give you information about what your program was doing when it
aborted. (Do not leave core dumps lying around. Because they are rather large, they
eat up valuable disk space. Also, George Belotsky points out that core dump files can
also be a security problem; someone could search in the core dump for potentially
exploitable information.)
On some systems, dividing by a floating zero does not result in a runtime error. On
other systems, it does. What happens on your system with the following code? If Inf
or NaN gets printed, you can think of the value as infinity or not a number.
T his chapter, together with Chapter 3, Functions, Pointers, and Arrays, provides an
introduction to programming in C++ using its native types and its nonOOP (object-ori-
ented programming) features. C++ was designed to expand on the C language.
A native type is one provided by the language directly. In C++, this includes the simple
types such as character, integer, and floating-point types; the boolean type; and derived
types such as array, pointer, and structure types, which are aggregates of the simple
types. This chapter focuses on the native simple data types and statements.
The intent of this and the next chapter is to enable programmers to use the kernel or
core language, that subset of C++ that comes closest to forming a traditional imperative
language such as C, Pascal, or FORTRAN. With the improvements to C in the kernel lan-
guage of C++, it is now possible to use the C language without its more extensive addi-
tional object-oriented features. Three critical enhancements are type-safety, an
improved input/output library iostream, and the generic programming feature tem-
plate. For example, in type-safety, the compiler checks that a value of correct type is
used within a statement or expression. Type-safety enables the programmer to readily
discover subtle errors.
An important object-oriented feature is type extensibility. This is the ability within the
programming language to develop new types suitable to a problem domain. For this
extensibility to work properly, the new type should work like the native types of the
kernel language. Object-oriented design of user-defined types should mimic the look
and feel of the native types. This is one reason why it is important to understand the
design and use of the native types.
For the experienced C programmer, most of this chapter’s material should be skimmed
and read mainly with an eye for differences between C and C++. For a programmer com-
ing from another language, such as Java or Pascal, or for rusty C programmers, this and
the next chapter review the C++ core language.
C++
Farms
In C++, tokens can be interspersed with white space and with comment text that is
inserted for readability and documentation. There are five kinds of tokens: keywords,
identifiers, literals, operators, and punctuators.
C++ distinguishes between uppercase and lowercase. As we shall see, C++ uses lower-
case in its keyword list.
As a historical note, ALGOL60 was an ancestor language to C and C++, but it has not
been used in any substantial way since the 1970s. It was the core language of Simula67,
which was the first real object-oriented language.
2.1.1 Comments
C++ has a single-line comment, written as // rest of line.
2.1.2 Keywords
Keywords in C++ are explicitly reserved words that have a strict meaning and may not
be used in any other way. They include words used for type declarations, such as int,
char, and float; words used for statement syntax, such as do, for, and if; and words
used for access control, such as public, protected, and private. Table 2.2 shows the
keywords in current C++ systems.
2.1.3 Identifiers
An identifier in C++ is a sequence of letters, digits, and underscores. An identifier can-
not begin with a digit. Uppercase and lowercase letters are treated as distinct. It is good
practice to choose meaningful names as identifiers. One- or two-letter identifiers can be
used when it is obvious what the name is being used for. Avoid using identifiers that
are distinguished only by case differences. In principle, identifiers can be arbitrarily
long, but many systems distinguish only up to the first 31 characters. Table 2.3 shows
examples of identifiers.
Ira Pohl’s C++ by Dissection 2.1 Program Elements 28
Dee Ann
Coder Analyst XY1432
2.1.4 Literals
Literals are constant values, such as 1 or 3.14159. There are literals for each C++ data
type. String literals are also allowed, as illustrated in Table 2.5.
Character literals are written between single quotes. Special characters can be repre-
sented with the backslash character \. (See Appendix A, ASCII Character Codes, for the
full character set.) Table 2.6 has examples of character literals.
String literals are stored as a series of characters terminated with the null character,
whose value is 0. String literals are static char[ ] constants. The character '"' can
be represented inside strings by escaping it with the backslash character \. Table 2.7
shows literals that contain characters requiring a backslash.
When printed, these strings would produce effects required by the special characters.
Thus, the second string prints an a followed by a number of white-space characters as
determined by the tab setting, and then a b followed by a newline character.
String literals that are separated only by white space are implicitly concatenated into a
single string.
Floating-point literals can be specified either with or without signed integer exponents,
as illustrated by Table 2.9.
Operators are used in expressions and are meaningful when given appropriate argu-
ments. C++ has many operators. Certain symbols stand for different operators, depend-
ing on context; for instance, - can be either unary or binary minus. A unary operator is
an operator on one argument, and a binary operator is an operator on two arguments.
The unary minus expression -(expression) is equivalent in value to the binary minus
expression 0 - expression. C operators are all available in C++, but C++ has operators
that are not found in C, such as the scope resolution operator ::.
Punctuators include parentheses, braces, commas, and colons and are used to structure
elements of a program. For example, the following contain punctuators in C++:
2.2
2.2 Input/Output
C++ input/output is not directly part of the language but rather is added as a set of
types and routines found in a standard library. The C++ standard I/O library is iostream
or iostream.h. The file name without the .h extension is the official ANSI standard name.
Officially, the ANSI standard libraries that are taken from C libraries are c followed by
their names without the .h extension. The ANSI C standard library stdio.h can be used as
the ANSI C++ library cstdio. We use iostream because we are illustrating C++ practice.
This section is introductory, intended to give the bare minimum of detail to get the
reader up and running.
The iostream library overloads the two bit-shift operators.
In file io.cpp
#include <iostream>
using namespace std;
int main()
{
int i;
double x;
In this example, the user entered the double 1.2 and the integer 3 with the result being
outputted as the double value 3.6.
Ira Pohl’s C++ by Dissection 2.2 Input/Output 33
error i = -1
Enter a positive integer:
2.3
2.3 Program Structure
In file gcd.cpp
// GCD greatest common divisor program
#include <iostream>
using namespace std;
int main()
{
int x, y, howMany;
is the GNU C++ compile command g++, acting on three files: module1.cpp, module2.cpp,
and my_main.cpp. If compilation shows no errors, an executable a.out is produced. It is
important to rename the executable to something other than a.out, such as
program_name. Otherwise, further compilation overwrites the previous a.out. It is con-
venient to be able to directly compile to an executable, such as program_name. As men-
tioned in Section 1.6, Writing and Running a C++ Program, on page 15, this can be done
using -o program_name in the compile command. So,
g++ -o my_program module1.cpp module2.cpp my_main.cpp
compiles directly to an executable named my_program.
■ int main()
{
int x, y, howMany;
2.3.1 Redirection
On most systems, input can be redirected from a file. Assume that the gcd program has
been compiled into an executable file called gcd. The command
gcd < gcd.dat
takes its input from the file gcd.dat and writes the answers to the screen. Test this with
a file containing
4 4 6 6 21 8 20 15 20
On most systems, output can also be redirected to a file. The command
gcd > gcd.ans
places its output in the file gcd.ans, taking its input from the keyboard. Note that the
messages prompting the user for input also go to that file, and the user will have to
know what to do without being prompted on the screen.
Enter the same data as before and check the file gcd.ans to see that it has the four cor-
rect answers. The two redirections can be combined as follows:
Ira Pohl’s C++ by Dissection 2.4 Simple Types 37
2.4
2.4 Simple Types
The simple native types in C++ are bool, int, double, char, and wchar_t. These types
have a set of values and representation that is tied to the underlying machine architec-
ture on which the compiler is running. Both the bool and the wchar_t types are new to
C++ and are not in C and early C++ systems. The bool type provides a native boolean
type, and wchar_t provides a wide character type, used for representing character sets
requiring more than the standard 255 characters.
C++ integral simple types can often be modified by the keywords short, long, signed,
and unsigned, to yield further simple types. The floating-point types are float, dou-
ble, and long double. Table 2.11 lists these types, shortest to longest. Length here
refers to the number of bytes used to store the type.
This basic type list runs from the conceptually shortest type, bool, to the conceptually
longest type, double. Each longer type must be at least as long as its predecessor type.
On most machines, a bool or a char is stored in a single byte. The basic types may have
modifiers short or long that can change the range of values that they can represent.
For example, on many machines a short int is 2 bytes and represents the range (-
32,768, 32,767). The unsigned modifier also changes the range by making the values
represented greater than or equal to zero. For example, on many machines an unsigned
short is 2 bytes and represents the range (0, 65,535). On many current systems, int
and float are each stored in 4 bytes. The longer types such as long int and double
are often stored in 4 bytes also, but on some systems they might be stored in 8 bytes.
The wchar_t, or wide character type, can represent distinct codes for any element of
the largest extended character set in any language’s alphabet, such as Japanese. A
wchar_t type is often the same size as an int type.
C++ also has the sizeof operator, which is used to determine the number of bytes a
particular object or type requires for storage.
Ira Pohl’s C++ by Dissection 2.4 Simple Types 38
int size = 4
long size = 4
This is not a mistake but an implementation decision for Sun Microsystems. The long
type must be at least the size of the int type.
The range of integral values that can be represented on your system is defined in the
standard header file limits. Some examples from our system are shown in Table 2.12.
The range of floating-point values that can be represented on your system is defined in
the standard header file cfloat. Some examples from our system are illustrated in Table
2.13.
In Table 2.13, FLT_EPSILON is the smallest number that when added to 1 in that data
type yields a result different from 1. The C++ standard library file limits contains the
template numeric_limits, which allows the user to query the system about character-
istics of different types. For example:
Ira Pohl’s C++ by Dissection 2.4 Simple Types 39
As a rule, use the int type for most integer arithmetic and double for most floating-
point arithmetic. These types are the most efficient for their particular machines and
compilers. Shorter types can be used if memory space is a priority. Longer types should
be used when range of values or precision is needed.
2.4.1 Initialization
A variable declaration associates a type with the variable name. An important concep-
tual distinction is the following: A declaration of a variable constitutes a definition, if
storage is allocated for it. In effect, the definition creates the object.
A definition can also initialize the value of the variable. Initialization is expressed by
following the identifier name with an initializer. For simple variables, this is usually
type id = expression
Some examples of definitions are shown in the following code:
In file simple_variables.cpp
#include <iostream>
using namespace std;
int main()
{
int i = 5; // i is initialized to 5
char c1 = 'B', c2; // c2 is uninitialized
double x = 0.777, y = x + i;
Initialization can involve an arbitrary expression, provided that all of the variables and
functions used in the expression are defined. In the preceding example, y is initialized
in terms of the just-defined x. The uninitialized variable c2 cannot be relied on to have
any particular value associated with it. Using it in the computation before a well-defined
value is assigned to it is a mistake. As a rule of thumb, when there is a choice, it is bet-
ter to initialize a variable than to define it as uninitialized and later assign it a value. Ini-
tialization makes the code more readable, less error-prone, and more efficient.
Syntactically, C++ declarations are themselves statements and can occur intermixed
with executable statements. This differs from C, in which declarations are not syntacti-
cally statements and must either be in global scope or at the head of a block. In the pre-
vious code, we could have placed the char declarations after the first cout statement
without affecting the output.
·····
cout << "x = " << x // x = 0.777 << "\ty = "
<< y << endl; // y = 5.777
char c1 = 'B', c2; // c2 uninitialized
·····
2.5
2.5 The Traditional Conversions
The expression x + y has both a value and a type. For example, if x and y are both vari-
ables of type int, x + y is also an int. However, if x and y are of different types, x + y
is a mixed expression. Suppose that x is a short and y an int. The value of x is con-
verted, or coerced, to an int, and the expression x + y has type int. The value of x as
stored in memory is unchanged. It is only a temporary copy of x that is converted dur-
ing the computation of the value of the expression. Now suppose that both x and y are
of type short. Even though x + y is not a mixed expression, automatic conversion
again takes place; both x and y are promoted to int, and the expression is of type int.
The general rules are straightforward.
The operand of the lower type is promoted to that of the higher type, and the
value of the expression has that type.
To illustrate implicit conversion, we make some declarations and list a variety of mixed
expressions along with their corresponding types in Table 2.14.
Ira Pohl’s C++ by Dissection 2.5 The Traditional Conversions 41
An automatic conversion can occur with an assignment. For example, d = i causes the
value of i, which is an int, to be converted to a double and then assigned to d; double
is the type of the expression as a whole. A promotion, or widening, such as d = i, is
usually reliable, but a demotion, or narrowing, such as i = d, can lose information.
Here, the fractional part of d is discarded.
In addition to implicit conversions, which can occur across assignments and in mixed
expressions, there are explicit conversions, called casts. If i is an int,
static_cast<double>(i)
casts the value of i so that the expression has type double. The variable i itself
remains unchanged. The static_cast is available for a conversion that is well-defined,
portable, and essentially invertible. This makes it a safe cast, namely, one with predict-
able and portable behavior. Some more examples are
y = static_cast<char>('A' + 1)
x = static_cast<double>(static_cast<int>(y) + 1)
Casts that are representation- or system-dependent use reinterpret_cast. For exam-
ple:
i = reinterpret_cast<int>(&x) // system-dependent
System-dependent casts are undesirable and should be avoided. They are considered
unsafe.
Two other special casts exist in C++: const_cast and dynamic_cast. The const modi-
fier means that a variable’s value is nonmodifiable. Very occasionally, it is convenient to
remove this restriction, by casting away constness. This is accomplished with the
const_cast, as in
(type)expression or type(expression)
Some examples are
In file body_fat.cpp
// Pounds to Kilograms and Body Mass Index BMI
#include <iostream>
using namespace std;
// conversion constants
2.6
2.6 Enumeration Types
The keyword enum is used to declare a distinct integer type with a set of named integer
constants called enumerators. Consider the following declaration:
The tag name and the enumerators must be distinct identifiers within scope. The values
of enumerators need not be distinct. Enumerations can be implicitly converted to ordi-
nary integer types, but not vice versa.
a = off; // legal
i = a; // legal: i becomes 0
b = i; // illegal
b = static_cast<answer>(i); // legal: explicit cast
Enumerators can be declared anonymously, without a tag name. Some examples are
enum { LB = 0, UB = 99 };
enum { lazy, hazy, crazy } why;
The first declaration is a common means of declaring mnemonic integer constants. The
second declares a variable why of enumerator type, with lazy, hazy, and crazy as its
allowable values. Enumerators are useful to collect a small number of integral values
and turn them into a type. This is good for program documentation, and for program
safety as well. Type-checking allows the compiler to check that an appropriate type is
used in a given context.
2.7
2.7 Expressions
In C++, there are many special characters with particular meanings. Examples include
the arithmetic operators:
+ - * / %
which stand for the usual arithmetic operations of addition, subtraction, multiplication,
division, and modulus, respectively. In mathematics, the value of a modulus b is
obtained by taking the remainder after dividing a by b. Thus, for example, 5 % 3 has
Ira Pohl’s C++ by Dissection 2.7 Expressions 45
the value 2, and 7 % 2 has the value 1. In a program, operators can be used to separate
identifiers. Although not required, for style reasons we put white space around binary
operators to heighten readability.
a + b // a added to b
-a // -a is unary minus equal to 0 - a
Some special characters are used in different ways in different contexts, and the con-
text determines which way is intended. For example, parentheses are sometimes used
to indicate a function name; at other times, they are punctuators. Another example is
given by the expressions
1 + 2 * 3
The operator * has higher precedence than +, causing the multiplication to be per-
formed first, followed by the addition. Hence, the value of the expression is 7. An equiv-
alent expression is
1 + (2 * 3)
On the other hand, because expressions inside parentheses are evaluated first,
(1 + 2) * 3
is different; its value is 9. Now consider the equivalent expressions
1 + 2 - 3 + 4 - 5 and (((1 + 2) - 3) + 4) - 5
Because the binary operators + and - have the same precedence, the associativity rule
left to right is used to determine how an expression is evaluated. This means the opera-
tions are performed from left to right. Thus, they are equivalent expressions.
Table 2.15 gives the rules of precedence and associativity for the operators of C++ and
is an important reference. We break out pieces of this table when dealing with subcate-
gories of expressions, such as the logical expressions.
All operators in a given table entry, such as ++, new, and &, have equal precedence with
respect to one another but have higher precedence than all the operators in the entries
Ira Pohl’s C++ by Dissection 2.7 Expressions 46
below them. The associativity rule for all the operators in a given entry appears on the
right side of the table. These rules are essential information for every C++ programmer,
and this table is repeated in Appendix B, Operator Precedence and Associativity.
The operators include all the C operators but also have the following operators not
found in C: the scope resolution operator ::; the memory management operators new
and delete; the modern casting operators static_cast dynamic_cast
reinterpret_cast const_cast; the member selection operators .* ->*; the throw
expression for exception handling throw; and the runtime type identifier operator
typeid.
From Table 2.15, we see that the unary operators have higher precedence than binary
plus and minus. In the expression
- a * b - c
Ira Pohl’s C++ by Dissection 2.7 Expressions 47
the first minus sign is unary, and the second, binary. Using the rules of precedence, we
see that
((- a) * b) - c
is an equivalent expression.
C++ has many operators and expression forms. Arithmetic expressions in C++ are con-
sistent with C practice. For example, in both C++ and C, the results of an operator, such
as the division operator /, depend on its argument types.
One pitfall in C++ is that the equality operator and the assignment operator are easily
confused because they are visually similar. The expression a == b is a test for equality,
whereas a = b is an assignment expression. A common C++ programming mistake is to
code something like
if (i = 1)
// do something
Ira Pohl’s C++ by Dissection 2.7 Expressions 48
intending
if (i == 1)
// do something
The first if statement assigns 1 to i and evaluates to 1, so it is always true. This error
can be very difficult to find. It is correct C++, and in certain very rare situations may be
the code the programmer intends to write. Some compilers do provide a warning when
they see a simple assignment expression as the controlling expression of a selection
statement. To prevent this error, C++ programmers can adopt the style
expr1 , expr2
expr1 is evaluated first and then expr2. The comma expression as a whole has the value
and type of its right operand. For example, in
sum = 0, i = 1
if i has been declared an int, this comma expression has value 1 and type int.
The comma operator typically is used in the control expression part of an iterative
statement, when more than one action is required. The comma operator associates from
left to right.
The conditional operator ?: is unusual in that it is a ternary operator. It takes three
expressions as operands.
x = (y < z) ? y : z;
Because the conditional operator has precedence over the assignment operator, the
parentheses are not necessary, but they help make clear the nature of the test.
The type of the conditional expression
is determined by expr2 and expr3. If they are different types, the usual conversion rules
apply. The conditional expression’s type cannot depend on which of the two expres-
sions expr2 or expr3 is evaluated. The conditional operator ?: associates right to left.
C++ provides bit-manipulation operators, shown in Table 2.18, which operate on the
machine-dependent bit representation of integral operands. For example, the operand ~
changes an integral operand bit-representation into its one’s complement. These
operators can be ignored by programmers who don’t manipulate underlying bit repre-
sentations.
C++ considers function call ( ) and indexing or subscripting [ ] to be operators. C++
also has an address & operation and an indirection *, or dereferencing, operation. The
unary address operator yields the address, or location, where an object is stored. The
unary indirection operator is applied to a pointer that retrieves the value from the loca-
tion it is pointed at. This operation is also known as dereferencing.
2.8
2.8 Statements
C++ has a large variety of statement types, including an expression statement. For
example, the assignment statement in C++ is syntactically an assignment expression
followed by a semicolon. C++ and C both have assignment statements, procedure state-
Ira Pohl’s C++ by Dissection 2.8 Statements 51
a = b + 1;
This expression evaluates the right-hand side of the assignment and converts it to a
value compatible with the variable on the left-hand side. This value is assigned to the
left-hand side. The left-hand side must be an lvalue, a location in memory where a value
can be stored or retrieved. Simple variables are lvalues.
C++ allows multiple assignments in a single statement.
a = b + (c = 3);
C++ provides assignment operators that combine an assignment operator and some
other operator.
a += b; is equivalent to a = a + b;
a *= a + b; is equivalent to a = a * (a + b);
C++ also provides increment (++) and decrement (--) operators in both prefix and post-
fix form. In prefix form, the increment operator adds 1 to the value stored at the lvalue
it acts on and returns the result. Similarly, the prefix form decrement operator sub-
tracts 1 from the value stored at the lvalue it acts on and returns the result.
++i; is equivalent to i = i + 1;
--x; is equivalent to x = x - 1;
The postfix form behaves differently from the prefix form, changing the affected lvalue
after the value has been returned.
j = ++i; is equivalent to i = i + 1; j = i;
j = i++; is equivalent to j = i; i = i + 1;
i = ++i + i++; // awful practice, system-dependent
Note: These are not exact equivalencies. The compound assignment operators evaluate
their left-hand side expression once. Therefore, for complicated expressions with side
effects, results of the two forms can be different.
The null statement is written as a single semicolon and causes no action to take place. A
null statement is usually used where a statement is required syntactically but no action
is desired. This situation sometimes occurs in statements that affect the flow of control.
Ira Pohl’s C++ by Dissection 2.8 Statements 52
if (condition)
statement
If condition is true, then statement is executed; otherwise, statement is skipped. After
the if statement has been executed, control passes to the next statement. A condition is
an expression or a declaration with initialization that selects flow of control. Here is an
example of an if statement:
if (condition)
statement1
else
statement2
Ira Pohl’s C++ by Dissection 2.8 Statements 53
if (x < y)
min = x;
else
min = y;
cout << "min = " << min;
If x < y is true, then min is assigned the value of x; if x < y is false, min is assigned
the value of y. After the if-else statement is executed, min is printed.
In the next program, we show how these statements can be used in a complete program.
The program takes as input an integer grade and prints out a message and the equiva-
lent letter grade. If the entered grade was outside the normal range, a grade of Z is
printed. Notice how the if-else structure works to designate a series of logical cases.
The last if is the error case. This is a frequent coding idiom, and the good programmer
must master this form of decision making.
In file if_test.cpp
// For printing out grade meanings
#include <iostream>
using namespace std;
int main()
{
int grade; // from 0 to 100
char letter_grade = 'Z'; // A, B, C, D, F, or Z
if (grade == 100) {
cout << " First in Class!\n";
letter_grade = 'A';
}
else if (grade >= 90 && grade < 100) {
cout << " Congratulations!\n";
letter_grade = 'A';
}
else if (grade >= 80 && grade < 90) {
cout << " Very Good\n";
letter_grade = 'B';
}
Ira Pohl’s C++ by Dissection 2.8 Statements 54
■ cout << " Your grade was " << letter_grade <<
endl;
}
The if selection picks a grade of A, B, C, D or F and assigns it to the
variable letter_grade. If no grade is selected, the default value Z
prevails. The output statement prints the grade in an understandable
phrase.
while (condition)
statement
First, condition is evaluated. If it is true, statement is executed, and control passes back
to the beginning of the while loop. The result: The body of the while loop, namely,
statement, is executed repeatedly until condition is false. At that point, control passes
to the next statement. In this way, statement can be executed zero or more times.
An example of a while statement follows.
In file while_test.cpp
#include <iostream>
using namespace std;
int main()
{
int i = 1, sum = 0;
In file for_test.cpp
// Use of typical for statement
#include <iostream>
using namespace std;
int main(){
int sum = 0;
The for statement is one common case in which a local declaration is used to provide
the loop control variable, as in
{
int i; /*local to block*/
for (i = 0; i < N; ++i)
sum += a[i];
}
sum = i = 0;
do { // execute
sum += i;
cin >> i;
} while (i > 0); // then test
Ira Pohl’s C++ by Dissection 2.8 Statements 58
do
statement
while (condition);
First, statement is executed, and then condition is evaluated. If it is true, control passes
back to the beginning of the do statement, and the process repeats itself. When the
value of condition is false, control passes to the next statement. As an example, sup-
pose that we want to add 10 positive numbers, such as the last 10 readings of your
blood pressure. We need to read in each integer and require that it be positive. The fol-
lowing code accomplishes this:
In file do_test.cpp
// Use of typical do statement
#include <iostream>
using namespace std;
int main() {
int sum = 0, n;
The following example illustrates the use of a break statement. A test for a negative
value is made. If the test is true, the break statement causes the for loop to be exited.
Program control jumps to the statement immediately following the loop.
switch (condition)
statement
Ira Pohl’s C++ by Dissection 2.8 Statements 60
where statement is typically a compound statement containing case labels, and option-
ally a default label. Typically, a switch is composed of many cases, and the condition
in parentheses following the keyword switch determines which, if any, of the cases are
executed.
A case label is of the form
In file switch_test.cpp
// Program for printing out grade meanings
#include <iostream>
using namespace std;
int main()
{
int grade; // from 0 to 100
char letter_grade = 'Z'; // A, B, C, D, F, or Z
switch (grade/10) {
case 10: cout << " First in Class!\n";
letter_grade = 'A';
break;
case 9: cout << " Congratulations!\n";
letter_grade = 'A';
break;
case 8: cout << " Very Good\n";
letter_grade = 'B';
break;
case 7: cout << " Okay\n";
letter_grade = 'C';
break;
case 6: cout << " Work harder\n";
letter_grade = 'D';
break;
case 5: case 4: case 3: case 2: case 1: case 0:
cout << " Sorry you failed\n";
letter_grade = 'F';
break;
default: cout << " Not a recognizable grade"
<< endl;
}
cout << "Your grade was " << letter_grade
<< endl;
}
goto label;
control is unconditionally transferred to a labeled statement.
label:statement
Both the goto statement and its corresponding labeled statement must be in the body
of the same function. In general, goto should be avoided.
2.9
2.9 Software Engineering: Debugging
Getting your code to work correctly is a crucial skill. Much of software engineering is
about how to avoid or how to find errors. The general term for finding errors in pro-
gramming is debugging.
Correct choice and use of type is one of the programmer’s key techniques in avoiding
errors. Languages that are strongly typed are usually safer to program in than lan-
guages that are weakly typed. C is considered a weakly typed, and therefore error-
prone, language. C++ is a more strongly typed language than C but is less so than Java.
C++ allows many different types to be mixed together in expressions with various con-
versions happening silently.
A classic C or C++ error is an expression of the form
Ira Pohl’s C++ by Dissection 2.9 Software Engineering: Debugging 63
double x, y = 2.5;
int i = 5;
x = y + i / 3;
cout << "x = " << x << endl; // prints x = 3.5
when the programmer’s intention was to have i / 3.0. With the denominator being a
double the division would be double, and x would equal 4.13333. All C++ expressions
should be examined for suspicious conversions. Also, the programmer should test code
on some simple data for which the results are already known.
C++ has greatly improved on C’s primitive form of cast. In general, it is best to avoid
explicit casting, also known as coercion or conversion. Type logic is a safety check that
the compiler can perform statically to detect coding mistakes. However, if you must
cast, try to stay with the most benign form of conversion, static_cast<>. A true, por-
table conversion is performed. At the other end of the spectrum is
reinterpret_cast<>, with nonportable, system-dependent effects. This cast should
be avoided.
C++ has changed C’s rule on where declarations can occur. Use of local declarations is
allowed in the for loop, for example. Because these rules have changed in C++ since its
introduction in 1985, some legacy code is wrong and must be updated to conform to
ANSI rules. In earlier compilers, variables that were declared in the initializer-statement
part of the for statement had scope that extended beyond the for statement. This dec-
laration would be in conflict with the same variable name declared at the head of the
block.
It is perfectly acceptable to declare simple variables, including variables used for loop-
ing, at the head of a block, most likely the beginning of a function definition. Following
this advice yields code that works in both C and C++. For example, here is an iterative
version of the Fibonacci function:
In file fibonacci_1.c
// Fibonacci series compatible with C
unsigned fibonacci(unsigned n)
{
unsigned i, sum = 0, f0 = 0, f1 = 1;
In file fibonacci_2.cpp
// Idiomatically correct C++
// Fibonacci series incompatible with C
// Code follows the rule of smallest enclosing scope
unsigned fibonacci(unsigned n)
{
unsigned sum = 0;
In file fibonacci_3.cpp
// ERROR because of scopes
unsigned fibonacci(unsigned n)
{
unsigned sum;
2.10
2.10 Dr. P’s Prescriptions
■ The return from main() of the integer constant 0 is considered implicit. The prac-
tice of explicitly returning 0—or not—is discretionary.
■ To detect errors, include a default in the switch statement, even when all the
expected cases have been accounted for.
■ Avoid side-effect operators, such as ++, in complex expressions, unless they are
used in a known idiomatic style.
■ Use prefix increment and prefix decrement in preference to postfix when either can
be used.
■ Avoid casting expressions.
■ When possible, use the break or continue statement, rather than a goto.
These spacing and layout guidelines conform to standard industry practice and are
used to enhance readability. For example, a uniform indentation standard makes it eas-
ier to follow flow of control. One statement to a line gives adequate white space for easy
readability.
Parentheses in expressions can be used to aid clarity by making grouping and prece-
dence clear. For example, if the return expression statement is complicated, it should be
parenthesized for readability. Within expressions, spaces around operators make them
easier to read. Parentheses clarify associativity and precedence in expressions where
these can be difficult to follow. They can also aid readability.
It is customary in C, and usual in C++, to place an opening brace on the same line as the
starting keyword for a statement, such as an if or for. The closing brace lines up with
the first character of this keyword. In the ALGOL and Pascal community, the practice
was to put the equivalent to braces (begin - end tokens) on their own line, which is also
acceptable. Whichever brace policy is adopted should be adhered to by the entire pro-
grammer team at a project or company.
Starting global statements and preprocessing directives in column 1 is consistent with
historic practice, where in the earliest C systems, preprocessor directives had to be in
column 1. Also, because of indentation and rest-of-line comments, this gives the most
room to neatly lay out code.
The function main() is an integer function with the return value being passed to the
system. Zero indicates correct termination and is implicitly assumed. Historically, it was
required explicitly, so contemporary practice is to have a return 0 inside main(). The
ANSI committee endorses the new practice of not requiring it. Either practice is accept-
able, but be consistent.
Write short function definitions. Keeping all declarations at the head of such blocks
makes it easy to see what variables the function employs. These declarations should be
separated for visual clarity from executable statements that follow them.
As previously mentioned, using both the prefix and postfix increment operators can be
confusing. Stick to prefix most of the time. The same applies to the decrement
operators. Avoid multiple use of these operators on the same variable within the same
expression or statement, because the various increments, and when they occur, can be
easily misunderstood. One reason for staying with prefix is that they can sometimes be
more efficient than postfix on nonnative types.
Ira Pohl’s C++ by Dissection 2.11 C++ Compared with Java 67
In general, avoid casting. When using casts, try to use only the static_cast<>, as it is
the safest of the casts. While casts can be convenient and efficient shortcuts in code,
they are error-prone and often system-dependent.
The goto is unnecessary in C++. Other structured flow of control statements can better
be used to maintain clear flow of control. In many instances, break and continue state-
ments can be used. These also can be avoided by properly constructing if-else state-
ments.
2.11
2.11 C++ Compared with Java
The primitive types in a Java program can be boolean, char, byte, short, int, long,
float, and double. These types are always identically defined regardless of the
machine or system they run on. For example, the int type is always a signed 32-bit inte-
ger, unlike in C++, where int can vary from system to system. The boolean type is not
an arithmetic type and cannot be used in mixed arithmetical expressions. The char type
uses 16-bit Unicode values. The byte, short, int, and long are all signed integer
types, with length in bits of 8, 16, 32, and 64, respectively. Unlike in C++, unsigned
types are not provided. The floating types comply with IEEE754 standards and are
float, a 32-bit size, and double, a 64-bit size.
Java has the same basic set of operators as C++, with a few exceptions. For example,
Java does not have the comma operator, scope resolution operator, or delete operator.
Java added two operators: the instanceof and >>> operators.
The flow of control statements—if, if-else, while, for, and switch—available to
C++ are also available in Java. Although goto is a reserved word in Java, the goto state-
ment was not implemented. However, Java extended the break and continue state-
ments so that they can use labels.
We write a program, Moon, to convert to kilometers the distance in miles from Earth to
the Moon. In miles, this distance is, on average, 238,857 miles. This number is an inte-
ger. To convert miles to kilometers, we multiply by the conversion factor 1.609, a real
number. Our conversion program uses variables capable of storing integer values. The
variables in the following program are declared in main(). Java cannot have variables
declared as extern (in other words, as global or file scope variables).
In file Moon.java
// Distance to the moon converted to kilometers
public class Moon {
public static void main(String[] s) {
int moon = 238857;
int moon_kilo;
System.out.println("Earth to moon = " + moon + " mi.");
moon_kilo = (int)(moon * 1.609);
System.out.println("Kilometers = " + moon_kilo +" km.");
}
}
Ira Pohl’s C++ by Dissection 2.11 C++ Compared with Java 68
Note that narrowing conversions that are implicit in C++ are not done in Java. Java, in
this case, is more type-safe than C++. Also, in Java, all the primitive types are implemen-
tation-independent. So, numerically, a Java program gets the same answer regardless of
the system it is running on. C++ continues C’s tradition of having implementation-
dependent choices of primitive types, so as to optimize performance on a given
machine.
Ira Pohl’s C++ by Dissection Summary 69
Summary
First, the for-init-statement is evaluated and may be used to initialize variables used
in the loop. Then condition is evaluated. It is of type bool. If it is true, statement is
executed, expression is evaluated, and control passes back to the beginning of the
for loop again, except that evaluation of for-init-statement is skipped. This iteration
continues until condition is false, whereupon control passes to the next statement.
The for-init-statement can be an expression statement or a simple declaration. Where
it is a declaration, the declared variable has the scope of the for statement.
Review Questions
2. Three keywords in C++ that are not in C are , , and . Describe their use as
far as you currently understand it.
3. What token does the new comment style in C++ involve? Why should it be used?
4. What two literal values does the bool type have? Can they be assigned to int vari-
ables? With what result?
9. What happens when the condition part of the for statement is omitted?
10. It is customary in C++, to place an opening brace line as the starting keyword
for a statement, such as an if or for. The closing brace of this keyword.
Ira Pohl’s C++ by Dissection Exercises 71
Exercises
1. Rewrite the gcd() function from Section 2.3, Program Structure, on page 34, with a
for loop replacing the while loop.
2. Write a program that finds the maximum and minimum integer value of a sequence
of inputted integers. The program should first prompt the user for how many values
will be entered. The program should print this value out and ask the user to confirm
this value. If the user fails to confirm the value, she must enter a new value.
4. Use the complex library to provide the C++ complex number type, and rewrite the
preceding root-finding program to print out roots as complex numbers when appro-
priate. Compare this to a C implementation. In ANSI C++, use #include <complex>.
In the main program, declare such variables as
5. What does the following program print? The last expression will cause an error on
most machines.
Ira Pohl’s C++ by Dissection Exercises 72
// What is printed?
int main()
{
char c = 'A';
int i = 3, j = 1, m = 0;
bool p = false, q = true;
6. The C++ switch statement allows two or more cases to be executed for the same
value by allowing the code to fall through.
switch (i) {
case 0: case 1:
++hopeless; // fall through
case 2: case 3:
++weak;
case 4: case 5:
++fails; break;
case 6: case 7:
++c_grades; break;
case 8:
++b_grades; break;
case 9:
++a_grades; break;
default:
cout << "incorrect grade " << i << endl;
}
Hand simulate this statement for i equals 1. Write the equivalent if-else state-
ments.
7. (George Belotsky) Rewrite the if_test program to avoid printing the Z grade. First, it is
possible to print just the letter grade along with the descriptive message in every if
clause. This will prevent the Z grade from being printed. Another alternative is to
Ira Pohl’s C++ by Dissection Exercises 73
wrap the final output in an if statement that ensures the grade is not Z before
printing. A common variant of the last alternative is to use a boolean variable as a
flag to decide whether or not to print.
8. Use sizeof to determine the number of bytes each of the following requires on your
local system: bool, char, short, int, long, float, double, and long double. Also
do this for the enumerated types
9. Write a program to convert from Celsius to Fahrenheit. The program should use
integer values and print integer values that are rounded. Recall that zero Celsius is
32 degrees Fahrenheit and that each degree Celsius is 1.8 degrees Fahrenheit.
10. Write a program that prints whether water at a given Fahrenheit temperature would
be solid, liquid, or gas. In the computation, use an enumerated type:
11. Write a program that accepts either Celsius or Fahrenheit and produces the other
value as output. For example, input 0C, output 32F; input 212F, output 100C.
13. In the C world, more flexible file I/O is available by using the FILE declaration and
file operations found in stdio. The C++ community uses fstream, as discussed in Sec-
tion 9.5, Files, on page 375. Familiarize yourself with this library. Convert the gcd
program in Section 2.3, Program Structure, on page 34, to use fstreams. The pro-
gram should get its arguments from the command line, as in
gcd gcd.dat gcd.ans
Ira Pohl’s C++ by Dissection Exercises 74
int main()
{
int how_many = 100;
15. Alter the previous program to ask the user how many numbers should be generated.
Have this be an outer loop. Exit this program when the user answers with zero or a
negative number.
16. The constant RAND_MAX is the largest integer that rand() generates. Use RAND_MAX/
2 to decide whether a random number is to be heads or tails. Generate 1,000 ran-
domly generated heads and tails. Print out the ratio of heads to tails. Is this a rea-
sonable test to see whether rand() works correctly? Print out the size of the longest
series of heads thrown in a row.
17. The conditions in selection and iterative statements can be declaration statements,
such as if (bool d = test()) . . . . , where scope is restricted to the statement.
Write a program that tests whether your compiler conforms to this latest ANSI rule
change.
19. (Java) Rewrite the convert from Celsius to Fahrenheit program in exercise 9 on page
73, in Java.
20. (Java) Rewrite the C++ Fibonacci program in Section 2.9, Software Engineering:
Debugging, on page 64, in Java. Have it print out the first 40 Fibonacci numbers.
Investigate the for loop scope rules in Java.
CHAPTER 3
C++
Farms
3.1
3.1 Functions
A programmer can solve a simple problem in C++ with a single function. More difficult
problems can be decomposed into subproblems, each of which can be either coded
directly or further decomposed. Decomposing difficult problems until they are directly
codable as single C++ functions is the software engineering method of stepwise refine-
ment. The function construct in C++ is used to write code for these directly solvable
subproblems. These functions are combined into other functions and are ultimately
used in main() to solve the original problem.
Ira Pohl’s C++ by Dissection 3.2 Function Invocation 76
3.2
3.2 Function Invocation
A C++ program is made up of one or more functions, one of which is main(). Program
execution always begins with main(). When program control encounters a function
name, the function is called, or invoked. This means that program control passes to the
function. The place at which a function is called is known as its calling environment.
After the function does its work, program control is passed back to the calling environ-
ment, which continues execution from that point.
WAR ROOM
NORAD C
COMMAND OUTSIDE CNN
LINE
As a simple example, consider the following program, echo, which uses the string
library and echoes an input word.
In file echo1.cpp
#include <iostream>
using namespace std;
int main()
{
string word;
3.3
3.3 Function Definition
The C++ code that describes what a function does is called the function definition. Its
form is
function header
{
statements
}
Everything before the first brace makes up the header of the function definition, and
everything between the braces makes up the body of the function definition.
In its simplest form, the syntax of a function header is
type name(parameter-declaration-list)
The type specification that precedes the function name is the return type and deter-
mines the type of the value that the function returns, if any. We will see more involved
functions headers in Section 10.7, Exception Specification, on page 408.
In the function definition for echo() in the echo program, the parameter list has one
parameter. The body of the function consists of a block. Because the function does not
return a value, the return type of the function is void.
Parameters are syntactically identifiers, and they can be used within the body of the
function. The parameters in a function definition are called formal parameters to
emphasize their role as placeholders for the values that are passed to the function
when it is called. When the function is invoked, the value of the argument correspond-
ing to a formal parameter is used within the body of the executing function. These
parameters are call-by-value, meaning that only the values from the calling environment
are passed, and not the variables themselves. This implies that if the called function
changes the value of its formal parameters, the variables in the calling environment
remain unchanged.
C++ functions can have declarations at the head of the block or elsewhere, as long as
the variable is declared before its use. This differs from C, where variable declarations
must be at the head of a block. So in the echo program, main() could have been written
as
In file echo2.cpp
int main()
{
cout << "Enter your word: ";
string word; // place declaration near its use
cin >> word;
echo(word);
}
Ira Pohl’s C++ by Dissection 3.4 The return Statement 79
In ANSI C++, the empty parameter list is always equivalent to using void. Thus, main()
is equivalent to main(void). The function main() implicitly returns the integer value 0
if no explicit return expression statement is executed.
3.4
3.4 The return Statement
The return statement is a flow of control statement. When a return statement is exe-
cuted, the current function terminates, and program control is immediately passed
back to the place where the function was invoked. In addition, if an expression follows
the keyword return, the value of the expression is returned to the calling point as well.
This value must be assignment-convertible to the return type of the function definition
header.
A return statement has one of the following two forms:
return;
return expression;
Some examples are
return;
return (a + b);
Using parentheses in the return expression is optional, a stylistic device that some pro-
grammers use to enhance readability. Here is an example used to find the maximum of
two values:
Notice how there are two return expressions. Some programming experts prefer that
there be only one exit to a function. This is to simplify flow of control analysis of the
program. We can rewrite the preceding code and avoid using two returns:
3.5
3.5 Function Prototypes
The syntax of functions in C++ is type-safe, with the types of parameters listed inside
the header parentheses. By explicitly listing the type and number of arguments, strong
type-checking and assignment-compatible conversions are possible.
A function can be declared before it is defined. It can be defined later in the file or come
from a library or user-specified file. Such a declaration is called a function prototype and
has the general form
type name(argument-declaration-list);
The argument-declaration-list is typically a comma-separated list of types. If a function
has no parameters, the preferred style for such an empty parameter list is
function_name(). The function’s argument list can include the argument identifiers.
This information allows the compiler to enforce type compatibility. Arguments are con-
verted to these types as if they were following rules of assignment.
The use of the empty parameter list differs from that in traditional C, in which an
empty parameter list can indicate an unknown number of arguments. Frequently, C pro-
grammers indicate an empty parameter list by using function_name(void). In C++, the
Ira Pohl’s C++ by Dissection 3.6 Call-By-Value 81
empty parameter list is the same as the use of void. We used in the echo program the
function echo(). Its prototype in main() would be
void echo(string);
Both the function return type and the argument-list types are explicitly mentioned. The
definition of echo() that occurs in the file must match this declaration. The function
prototype can also include the identifier names of the arguments. In the case of echo(),
this is
3.6
3.6 Call-By-Value
Functions are invoked by writing their name and an appropriate list of arguments
within parentheses. These arguments match in number and type (or compatible type)
the parameters in the parameter list in the function definition. The compiler enforces
type compatibility. The basic argument-passing mechanism inherited from the C lan-
guage is call-by-value. That is, each argument is evaluated and its value is used locally in
place of the corresponding formal parameter. Thus, if a variable is passed to a function,
the stored value of that variable in the calling environment will not be changed. Here is
an example that clearly illustrates the concept of call-by-value:
In file compute_sum.cpp
#include <iostream>
using namespace std;
int main()
{
int n = 3, sum;
3.7
3.7 Recursion
A recursive function calls itself as part of its definition. A simple recursive function has
two main parts: the base-case part, where it computes a value and terminates, and the
recursive part, where it calls itself. Recursion is often used to define mathematical func-
tions, such as the factorial function. Having recursive functions in C++ allows the pro-
grammer to use a simple code body to define these functions.
In file factorial.cpp
// Recursive factorial function
long factorial(int n)
{
if (n <= 1)
return 1;
else
return n * factorial(n - 1);
}
Notice how the recursive call is with the expression n - 1. This guarantees that the
function factorial() terminates. Each recursion reduces the called expression by 1
until the termination condition n <= 1 is true. In running this computation, be aware
that for even relatively small values of n (such as 13), the computation fails because of
integer overflow.
Let us look at another simple example of recursion:
In file blast_off.cpp
// Recursive blast_off function
Count_down at time 5
Count_down at time 4
Count_down at time 3
Count_down at time 2
Count_down at time 1
BLAST OFF
// base-case part
if (base-case condition)
return base-case computed value;
else
return recursively computed expression;
The greatest common divisor of two integers is recursively defined in pseudocode as
follows:
GCD(m,n) is:
if m modulo n equals 0 then n;
else GCD(n, m mod n);
Recall that the modulo operator in C++ is %. To test your understanding, do exercise 4
on page 133.
3.8
3.8 Default Arguments
A formal parameter can be given a default argument, usually a constant that occurs fre-
quently when the function is called. Use of a default argument saves writing this default
value at each invocation. The ability to provide default values to arguments does not
exist in C or Java.
The default argument is placed in the function header by using initializing syntax
within the argument list for one or more of its right-most parameters. Here are some
examples:
Ira Pohl’s C++ by Dissection 3.8 Default Arguments 85
author_ship("1/1/2005", "1.3");
// Albie B. Coder is the programmer
author_ship("1/1/2003", "2.7", "L.M.P.");
// L.M.P. is the programmer
Another example is the following recursive function:
Ira Pohl’s C++ by Dissection 3.9 Functions as Arguments 86
In file powers.cpp
int sqr_or_power(int n, int k = 2) // k=2 is default
{
assert(k > 1); // if false program aborts
if (k == 2)
return (n * n);
else
return (sqr_or_power(n, k - 1) * n);
}
We assume that most of the time the function is used to return the value of n squared.
The assert is discussed in Section 3.24.2, Software Engineering: Program Correctness,
on page 124.
3.9
3.9 Functions as Arguments
Functions in C++ can be thought of as the addresses of the compiled code residing in
memory. Functions are therefore a form of pointer and can be passed as a pointer-value
argument into another function. Using this idea, we write code that prints n values of a
mathematical function f(x), starting at an initial value using a specific increment. This
plotting routine can be used to generate a function map that later is used to find prop-
erties of f(x), such as a root of the function f(x).
In file plot.cpp
double f(double x) { return (x * x + 1.0 / x); }
int main()
{
cout << "mapping function x * x + 1.0 / x " << endl;
plot(f, 0.01, 0.01, 100);
}
3.10
3.10 Overloading Functions
Function overloading is the ability to have multiple definitions for the same function
name within the same scope. The usual reason for picking a function name is to indi-
cate the function’s chief purpose. Readable programs generally have a diverse and liter-
ate choice of identifiers. Sometimes different functions are used for the same purpose.
For example, consider a function that averages a sequence of double values versus one
that averages a sequence of int values. Both are conveniently named average(), as in
the following code. An overloaded function can be written in which there are distinct
argument lists. The list of arguments must differ in either type or number or both.
In file average.cpp
double average(const int size, int& sum)
{
int data;
cout << "\nEnter " << size << " integers: " << endl;
int main()
{
int isum = 0;
double dsum = 0.0;
cout << average(10, isum) << " int average" << endl;
cout << average(10, dsum) << " double average" << endl;
}
The compiler chooses the function with matching types and arguments. The signature-
matching algorithm gives the rules for performing this. By signature, we mean the list
Ira Pohl’s C++ by Dissection 3.11 Inlining 89
of types that are used in the function declaration. The rules for this match are very
detailed. We discuss them in more detail in Section 5.9, Overloading and Signature
Matching, on page 208. Conceptually, the algorithm picks the best available match.
Therefore, if the arguments are exactly matched to the signature, that is the match
selected. In the preceding code, the arguments exactly matched the two versions of the
overloaded function average().
It is important not to abuse this feature. Multiple functions with the same name can
often be confusing. It is not readily apparent to the programmer which version is being
called. The use of function overloading is good design when each version of the over-
loaded function conceptually performs the same computation. In the previous example,
the same computation of summing a sequence is done as discussed in Chapter 6, Tem-
plates and Generic Programming.
3.11
3.11 Inlining
The keyword inline can be placed at the beginning of a function declaration. It tells
the compiler to attempt to replace the function call by the function body code. This
avoids function call invocation. On most computers, this leads to a substantial speed-
up when executing simple functions. This speed improvement can come at the expense
of an increase in the size of the executable code.
In file inline.cpp
inline double cube(double x)
{
return (x * x * x);
}
The compiler parses this function, providing semantics that are equivalent to a nonin-
line version. Compiler limitations prevent complicated functions, such as recursive
functions, from being inlined.
The preprocessor expands the macros and passes on the resulting text to the compiler.
So the preceding is equivalent to
#define SQR(X) X * X
·····
y = SQR(t + 8); // expands to t + 8 * t + 8
3.12
3.12 Scope and Storage Class
The core language has two principal forms of scope: local scope and file scope. Local
scope is scoped to a block. Compound statements that include declarations are blocks.
Function bodies are examples of blocks. They contain a set of declarations that include
their parameters. File scope has names that are external (global). There are also class
scope rules, which are discussed in Section 4.6, Class Scope, on page 150. Every variable
and function in C++ has two attributes: type and storage class. The four storage classes
are automatic, external, register, and static, with corresponding keywords
The basic rule of scoping is that identifiers are accessible only within the block in which
they are declared. Thus, they are unknown outside the boundaries of that block. A sim-
ple example follows.
In file scope_test.cpp
// Examples of scope rules
#include <iostream>
using namespace std;
int main()
{
int a = 2; // outer block a
cout << a << endl; // prints 2
{ // enter inner block
int a = b; // inner block a
cout << a << endl; // prints 15
} // exit inner block
cout << ++a << endl; // 3 is printed
}
Each block introduces its own nomenclature. An outer block name is valid unless an
inner block redefines it. If redefined, the outer block name is hidden, or masked, from
the inner block. Inner blocks may be nested to arbitrary depths that are determined by
system limitations. In the preceding example, there is a global variable b, which is avail-
able throughout the code. Similarly, the output stream identifier cout is available as it
is declared in the file iostream. The local variable a is declared in the outer block and
redeclared in the inner block. In the inner block, the inner declaration of a masks the
outer block variable a.
In C++, declarations can be almost anywhere in a block. An example shows this:
Even though C++ does not require that declarations be placed at the head of blocks, it is
frequently good practice to do so. Because blocks are often small, this practice provides
a good documentation style for commenting on their associated use.
Sometimes it is appropriate to declare variables later on in a block. One circumstance is
when placing declarations within blocks allows a computed or input value to initialize a
variable. A second circumstance is when large objects need to be allocated for a short
time toward the end of a block.
auto int a, b, c;
auto float f = 7.78;
Because the storage class is automatic by default, the keyword auto is seldom used.
there are exceptions. This tends to improve the modularity of the code and reduce the
possibility of undesirable side effects.
Here is a simple example of using external declarations for a program that sits in two
separate files.
In file circle.cpp
const double pi = 3.14159;
double circle(double radius)
{
return (pi * radius * radius);
}
In file circle_main.cpp
double circle(double); // function of scope extern
int main()
{
double x;
·····
cout << circle(x) << " is area of circle of radius "
<< x << endl;
}
With the GNU system, this is compiled as g++ circle.c circle_main.c.
The const modifier causes pi to have local file scope, so pi cannot be directly
imported into another file. When such a definition is required elsewhere, it must be
modified explicitly with the keyword extern.
{
for (register i = 0; i < LIMIT; ++i) {
·····
}
}
Ira Pohl’s C++ by Dissection 3.12 Scope and Storage Class 94
int f()
{
static int called = 0;
++called;
·····
return called;
}
The first time the function is invoked, the variable called is initialized to 0. On func-
tion exit, the value of called is preserved. When the function is invoked again, called
is not reinitialized; instead, it uses its retained value from the previous time the func-
tion was called.
In its second, more subtle use, static provides a privacy mechanism that is very
important for program modularity. By privacy, we mean visibility or scope—restrictions
on otherwise accessible variables or functions.
This use restricts the scope of the function. Static functions are visible only within the
file in which they are defined. Unlike ordinary functions, which can be accessed from
other files, a static function is available throughout its own file but in no other. Again,
this facility is useful in developing private modules of function definitions. Note that in
C++ systems with namespaces, this mechanism should be replaced by anonymous
namespaces (see Section 3.12.5, Namespaces, on page 98).
int foo(int a)
{
·····
b = goo(a);
// goo() is available here but not in other files
·····
}
In C++, the system initializes to 0 both external variables and static variables that are
not explicitly initialized by the programmer. Such variables include arrays, strings,
pointers, structures, and unions. For arrays and strings, this means that each element is
initialized to 0; for structures and unions, it means that each member is initialized to 0.
In contrast, automatic and register variables usually are not initialized by the system
and can start with garbage values.
#include "pgm.h"
When the preprocessor encounters this directive, it looks first in the current directory
for the file pgm.h. If there is such a file, it is included. If not, the preprocessor looks in
other system-dependent places for the file. If the file pgm.h cannot be found, the pre-
processor issues an error message and compilation stops.
Our header file pgm.h may contain #includes, #defines, declarations of enumeration
types, declarations of class types, other programming constructs, and a list of function
prototypes at the bottom. Thus pgm.h contains program elements that are appropriate
for our program as a whole. Because the header file pgm.h occurs at the top of each .c
file, it acts as the “glue” that binds our program together.
multi_main.cpp
pgm.h
#include "pgm.h"
·····
#includes
#defines
····· multi_fct.cp
multi_print.cpp
#include "pgm.h"
·····
Ira Pohl’s C++ by Dissection 3.12 Scope and Storage Class 96
We show a very simple example of how this works. We write our program in a separate
directory. It consists of a .h file and three .cpp files. Typically, the name of the directory
and the name of the program are the same. Here is our multi_main program:
In file pgm.h:
#include <iostream>
#include <cstdlib>
using namespace std;
#define N 3
In file multi_main.cpp:
#include "pgm.h"
int main()
{
char ans;
int k, n = N;
In file multi_fct.cpp:
#include "pgm.h"
void fct1(int n)
{
int i;
void fct2()
{
cout << "Hello from fct2()" << endl;
}
In file multi_prn.cpp:
#include "pgm.h"
3.13
3.13 Namespaces
C++ inherited C’s single global namespace. Programs written by two or more parties can
have inadvertent name clashes when combined. C++ encourages multivendor library
use. This motivates the addition of a namespace scope to ANSI C++.
namespace LMPinc {
class puzzles { ····· };
class toys { ····· };
·····
}
The namespace identifier can be used as part of a scope-resolved identifier, which has
the form
namespace_id::id
A using declaration lets a client have access to all names from that namespace.
namespace LMPinc{
int n;
namespace dolls { // inner namespace
int sq(){ return n * n; } // LMPinc::n
void pr_my_logo();
}
void LMPdolls::pr_my_logo()
{ cout << "Dolls by Laura" << endl; }
}
Ira Pohl’s C++ by Dissection 3.14 Pointer Types 99
As mentioned in Section 3.12.4, The Storage Class static, on page 94, namespaces can
be used to provide a unique scope that replaces static global declarations. This is done
by an anonymous namespace definition, as in
3.14
3.14 Pointer Types
C++ pointers, used to reference variables and machine addresses, are intimately tied to
array and string processing. C++ arrays can be considered a special form of pointer
associated with a contiguous piece of memory for storing a series of indexable values.
Pointers are used in programs to access memory and to manipulate addresses. If v is a
variable, &v is the address, or location in memory, of its stored value. The address oper-
ator & is unary and has the same precedence and right-to-left associativity as the other
unary operators. Pointer variables can be declared in programs and then used to take
addresses as values. The following declares p to be of type “pointer to int”:
int* p;
The legal range of values for any pointer always includes the special address 0, as well
as a set of positive integers that are interpreted as machine addresses on a particular
system. Some examples of assignment to the pointer p are
int i = 5, j;
int* p = &i; // pointer p is init to address of i
In file order1.cpp
// Pointer-based call-by-reference
int main()
{
int i = 7, j = 3;
The rules for using pointer arguments to achieve call-by-reference can be summarized
as follows:
3.15
3.15 Reference Declarations
int n;
int& nn = n; // nn is alternative name for n
double a[10];
double& last = a[9]; // last is an alias for a[9]
Declarations of references that are definitions must be initialized and are usually initial-
ized to simple variables. The initializer is an lvalue expression, which gives the vari-
able’s location in memory. In these examples, the names n and nn are aliases for each
other; that is, they refer to the same object. Modifying nn is equivalent to modifying n,
and vice versa. The name last is an alternative to the single array element a[9]. These
names, once initialized, cannot be changed.
When a variable i is declared, it has an address and memory associated with it. When a
pointer variable p is declared and initialized to &i, it has an identity separate from i.
The pointer p has memory associated with it that is initialized to the address of i. When
a reference variable r is declared and initialized to i, it is identical to i. It does not have
an identity separate from the other names for the same object. In effect, r is just
another name for i, that is, an alias.
The following definitions are used to demonstrate the use of dereferencing and alias-
ing. The definitions assume that memory at location 1004 is used for integer variable a
and that memory at 1008 is used for pointer variable p.
04
ss
10
re
s
d
es
ad
dr
ad
p
1004 a, ref_a
5 7 8
Pointer Declarations
Notice in the preceding figure of pointer declarations that any change to the value of a
is equivalent to changing ref_a. Such a change affects the dereferenced value of p. The
Ira Pohl’s C++ by Dissection 3.15 Reference Declarations 103
pointer p can be assigned another address and lose its association with a. However, a
and ref_a are aliases and within scope must refer to the same object. These declara-
tions can be used for call-by-reference arguments, which allows C++ to have call-by-ref-
erence arguments directly. The function order(), using this mechanism, is recoded as
follows:
In file order2.cpp
void order(int& p, int& q)
{
int temp;
if (p > q) {
temp = p;
p = q;
q = temp;
}
}
The function would be prototyped and invoked in main() as follows:
int main()
{
int i, j;
·····
order(i, j);
·····
}
If i and j are int variables, then order(i, j) uses the reference to i and the refer-
ence to j to exchange, if necessary, their two values. In traditional C, this operation
must be accomplished by using pointers and dereferencing.
When function arguments are to remain unmodified, it can be efficient and correct to
pass them const call-by-reference. This is the case for types that are structures. The
const is not strictly necessary, but it indicates the programmer’s intent not to modify
these values and allows the compiler to check this.
struct large_size {
int mem[N];
·····
};
Ira Pohl’s C++ by Dissection 3.16 The Uses of void 104
3.16
3.16 The Uses of void
The keyword void is used to declare the generic pointer type—pointer to void. The
keyword void is also used as the return type of a function not returning a value. In pro-
gramming, such a function is sometimes called a pure procedure. In addition, void can
be used in a cast to indicate that a value is unneeded.
Most interesting is the use of void* as a generic pointer type. A pointer declared as
type pointer to void, as in void* gp, may be assigned a pointer value of any type but
may not be dereferenced. Dereferencing is the operation * acting on a pointer value to
obtain what is pointed at. It would not make sense to dereference a pointer to a void
value.
3.17
3.17 Arrays
An array is a data type used to represent a large number of values of the same type. An
array might be used to represent all the salaries in a company or all the weights of par-
ticipants in a fitness program. Each element in an array has a position, with the initial
element having position zero. An array element’s position can be used as an index or
subscript to access that element. The elements of an array are randomly accessible
through the use of subscripts. Arrays of all types are possible, including arrays of
arrays. A typical array declaration allocates memory starting from a base address. An
array name is, in effect, a pointer constant to this base address. In C++, only one-dimen-
sional arrays are provided, with the first element always indexed as element zero.
To illustrate some of these ideas, let us write a small program that fills an array, prints
out values, and sums the elements of the array:
In file sum_array1.cpp
// Simple array processing
int main()
{
int a[SIZE]; // get space for a[0],·····,a[4]
int sum = 0;
The preceding array requires enough memory to store five integer values. Thus, if a[0]
is stored at location 1,000, the remaining array elements on a system needing 4 bytes
for an int are successively stored at locations 1004, 1008, 1012, and 1016. It is consid-
ered good programming practice to define the size of an array as a constant. Since
much of the code may depend on this value, it is convenient to be able to change a sin-
gle const int SIZE declaration to process various size arrays. Notice how the various
parts of the for statement are neatly tailored to provide a terse notation for dealing
with array computations.
Ira Pohl’s C++ by Dissection 3.18 Arrays and Pointers 106
3.17.1 Subscripting
Assume that a declaration has the form
int i, a[size];
We can write a[i] to access an element of the array. More generally, we may write
a[expr], where expr is an integral expression, to access an element of the array. We call
expr a subscript, or index, of a. The value of a C++ subscript should lie in the range 0 to
size – 1. An array subscript value outside this range often causes a runtime error. When
this happens, the condition is called “overrunning the bounds of the array,” or “sub-
script out of bounds.” It is a common programming error. The effect of the error in a
C++ program is system-dependent and can be quite confusing. One frequent result is
that the value of an unrelated variable is returned or modified. Thus, the programmer
must ensure that all subscripts stay within bounds.
3.17.2 Initialization
Arrays can be initialized by a comma-separated list of expressions enclosed in braces:
3.18
3.18 Arrays and Pointers
An array name by itself is an address, or pointer value, and pointers and arrays are
almost identical in terms of how they are used to access memory. However, there are
subtle and important differences. A pointer is a variable that takes addresses as values.
An array name is a particular fixed address that can be thought of as a constant pointer.
When an array is declared, the compiler must allocate a base address and a sufficient
amount of storage to contain all of the elements of the array. The base address of the
array is the initial location in memory where the array is stored; it is the address of the
first element (index 0) of the array. Suppose that we write the declaration
and the system causes memory bytes 300, 304, 308, . . . , 696 to be the addresses of
a[0], a[1], a[2], . . . , a[99], respectively, with location 300 being the base address
of a. We are assuming that each byte is addressable and that 4 bytes are used to store
an int. The two statements p = a; and p = &a[0]; are equivalent and would assign
300 to p. Note that [] has higher precedence than &, so &a[0] is equivalent to &(a[0]).
Pointer arithmetic provides an alternative to array indexing. The two statements p = a
+ 1; and p = &a[1]; are equivalent and would assign 304 to p. Assuming that the ele-
ments of a have been assigned values, we can use the following code to sum the array:
In file sum_array2.cpp
sum = 0;
for (p = a; p < &a[N]; ++p)
sum += *p;
is equivalent to
sum = 0;
for (i = 0; i < N; ++i)
sum += a[i];
In this loop, the pointer variable p is initialized to the base address of the array a. Then
the successive values of p are equivalent to &a[0], &a[1], . . . , &a[N-1]. In general, if i
is a variable of type int, p + i is the ith offset from the address p. In a similar man-
ner, a + i is the ith offset from the base address of the array a. Here is another way to
sum the array:
sum = 0;
for (i = 0; i < N; ++i)
sum += *(a + i);
Just as the expression *(a + i) is equivalent to a[i], the expression *(p + i) is
equivalent to p[i]. In many ways, arrays and pointers can be treated alike, but there is
one essential difference. Because the array a is a constant pointer and not a variable,
and we thus cannot change the address of a, expressions such as the following are ille-
gal:
a = p ++a a += 2
3.19
3.19 Passing Arrays to Functions
In file sum_array3.cpp
int sum(int a[], int n) // n is the size of a[]
{
int s = 0;
The first call sums all 100 elements of the array v[]. The second call sums the first 88
elements. The third function call again illustrates the use of pointer arithmetic. The
base address of v is offset by 7, and sum() initializes the local pointer variable a to this
address. This causes all address calculations inside the function call to be similarly off-
set. The number of elements summed is the value of the variable k. If the value of k is
10, then we sum elements from v[7] up to and including v[16].
In C++, a function with a formal array parameter can be called with an array argument
of any size, provided the array has the right base type.
3.20
3.20 Problem Solving: Random Numbers
Random numbers have many uses in computers. One use is to serve as data to test
code; another use is to simulate a real-world event that involves a probability. The
method of simulation is an important problem-solving technique. Programs that use
random number functions to generate probabilities are called Monte Carlo simulations.
The Monte Carlo technique can be applied to many problems that otherwise would have
no possibility of solution.
A random number generator is a function that returns integers that appear to be ran-
domly distributed in some interval 0 to n, where n is system-dependent. The function
rand() in the standard library cstdlib is provided to do this. This function generates a
pseudorandom sequence. It is called pseudorandom because the numbers are generated
Ira Pohl’s C++ by Dissection 3.20 Problem Solving: Random Numbers 109
In file random.cpp
#include <iostream>
#include <cstdlib>
using namespace std;
int main(void)
{
int n, seed;
void prn_random_numbers(int k)
{
int r, biggest, smallest;
Count: 15
Maximum: 32473
Minimum: 4449
3.21
3.21 Software Engineering: Structured Programming
Let us write a program that will find the maximum, minimum, and average value of an
array of doubles. This could be part of package of routines that does data analysis. The
key to simplifying this project is to use a single function for each part of the problem.
This is the heart of structured programming. A large problem is decomposed or refined
into a series of small problems.
Another key problem-solving technique is to reuse already tested code. We already pro-
grammed the routine for summing an array of int, as we saw in Section 3.19, Passing
Arrays to Functions, on page 108.
Ira Pohl’s C++ by Dissection 3.21 Software Engineering: Structured Programming 112
In file sum_array3.cpp
// n is the size of a[]
int sum(const int a[], const int n)
{
int s = 0;
In file average_array1.cpp
double average(const double a[], const int n)
{
double s = 0.0;
int main()
{
double a[size] = {0.5, 1.5, 6.0, 7.5, 2.3, 4.6 };
Now, to test this on a large amount of data, we can call on our random number genera-
tor to fill up a large array.
In file average_array2.cpp
void fill(double a[], const int n)
{
for (int i = 0; i < n; ++i)
a[i] = (2.0 * rand()) / RAND_MAX;
}
The constant RAND_MAX is the largest integer random number that rand() generates.
Therefore, the double expression used to fill the array will range between 0.0 and 2.0.
int main()
{
double a[size];
fill(a, size);
cout << "Test array data processing" << endl;
cout << "average = " << average(a, size) << endl;
cout << "minimum = " << minimum(a, size) << endl;
cout << "maximum = " << maximum(a, size) << endl;
}
Ira Pohl’s C++ by Dissection 3.22 Core Language ADT: char* String 114
Note that the minimum is very nearly 0 and the maximum approaches 2, and that the
output will vary from run to run. Random number generators are very useful for pro-
ducing large data sets that have predictable properties when used for testing.
3.22
3.22 Core Language ADT: char* String
The C++ community has agreed to treat the type char* as a form of string type. The
understanding is that such strings are terminated by the char value '\0', and that the
cstring (or string.h on older systems) package of functions is called using this represen-
tation. In ANSI C++, the library string provides as a template class a standardized string
type that is preferred to this use of char*. The language partly supports this abstrac-
tion by defining string literals as being null-terminated. A char* or char[] can be ini-
tialized with a literal string. Note that the terminating '\0' is part of the initializer list.
Also, a char[] cannot be assigned to and is a constant, while a char* can be assigned
to.
char* s = "c++"; // s[0] = 'c', s[1] = '+',
// s[2] = '+', s[3] = '\0';
In file string_func.cpp
// String function implementations
The following function implements a string-equality test. Note its use of pointer arith-
metic. The construct *s1++ means “dereference the pointer s1, and after using this
value in the expression, add sizeof(char) to its pointer value.” Because of operator
precedence, *s1++ is equivalent to *(s1++). This pointer calculation gets us the next
element address.
Ira Pohl’s C++ by Dissection 3.23 Multidimensional Arrays 117
3.23
3.23 Multidimensional Arrays
C++ allows arrays of any type, including arrays of arrays. With two bracket pairs, we
obtain a two-dimensional array. This idea can be iterated to obtain arrays of higher
dimension, as shown in Table 3.2. With each bracket pair, we add another array dimen-
sion.
A k-dimensional array has a size for each of its k dimensions. If we let si represent the
s i z e o f i t s i t h d i m e n s i o n , t h e d e c l a r a t i o n o f t h e a r r a y a l l o ca t e s s p a c e f o r
s1 × s2 × . . . × sk elements. In Table 3.2, b has 3 × 5 elements, and c has 7 × 9 × 2 ele-
ments. Starting at the base address of the array, all the array elements are stored con-
tiguously in memory, row by row.
Ira Pohl’s C++ by Dissection 3.23 Multidimensional Arrays 118
int a[3][5];
then we can think of the array elements arranged as in Table 3.3:
To illustrate these ideas, let us write a program that fills a two-dimensional array, prints
out values, and sums the elements of the array:
In file sum_2d_array1.cpp
#include <iostream>
using namespace std;
int main()
{
int a[M][N], i, j, sum = 0;
sum = 30
The parentheses are necessary because the brackets [ ] have higher precedence than
the indirection operator *. We can think of a[i] as the ith row of a (counting from 0),
and we can think of a[i][j] as the element in the ith row, jth column of the array
(counting from 0). The array name a by itself is equivalent to &a[0]; it is a pointer to an
array of five ints. The base address of the array is &a[0][0], not a. Starting at the base
address of the array, the compiler allocates contiguous space for 15 ints. For any array,
the mapping between pointer values and array indices is called the storage mapping
function. For the array a, the storage mapping function is specified by noting that
In file sum_2d_array2.cpp
int sum(int a[][5])
{
int i, j, sum = 0;
3.24
3.24 Operators new and delete
The unary operators new and delete are available to manipulate free store. They are
more convenient than, and replace, the C standard library functions malloc(), cal-
loc(), and free() in most applications. Free store is a memory pool for objects whose
lifetime is directly managed by the programmer. The programmer creates an object
using new and destroys the object using delete. This is important for dynamic data
structures, such as lists and trees.
Ira Pohl’s C++ by Dissection 3.24 Operators new and delete 121
new type-name
new type-name initializer
new type-name[expression]
In each case, there are at least two effects. First, an appropriate amount of store is allo-
cated from free store to contain an object of the named type. Second, the base address
of the object is returned as the value of the new expression.
The operator new can either throw a bad_alloc exception or return the value 0, when
memory is unavailable.
The following example uses new:
delete expression
delete [ ] expression
The first form is used when the corresponding new expression has not allocated an
array. The second form has empty brackets, indicating that the original allocation was
an array of objects. The operator delete does not return a value. Equivalently, one can
say that its return type is void. The following example uses these constructs to dynam-
ically allocate an array:
In file dynamic_array.cpp
// Use of new to dynamically allocate an array
// assumes older-style return of 0 for
// allocation error
int main()
{
int* data;
int size;
Ira Pohl’s C++ by Dissection 3.24 Operators new and delete 122
This introductory discussion of the free-store operators treats the basic cases. The free-
store operators are addressed in greater detail in Section 5.19, Overloading new and
delete, on page 224.
Ira Pohl’s C++ by Dissection 3.24 Operators new and delete 123
It is becoming a standard practice to use C++ libraries for accessing both char* arrays
and general arrays instead of coding the array functions directly. Here, we discuss two
such libraries: one for vectors and one for string processing.
In file sum_array4.cpp
int sum(vector<int> a, int n)
{
int s = 0;
In file sum_array5.cpp
// Sum written to use a.size() in place of N
int sum(vector<int> a)
{
int i, s = 0;
In file pr_statements.cpp
// Print a string with a line number
int main()
{
string s1, s2;
3.25
3.25 Software Engineering: Program Correctness
An assertion is a program check for correctness that, if violated, forces an error exit.
One point of view is that an assertion is a contractual guarantee among the provider of
a piece of code, the code’s manufacturer, and the code’s client or user. In this model,
the client needs to guarantee that the conditions for applying the code exist, and the
manufacturer needs to guarantee that the code works correctly under these provisions.
In this methodology, assertions provide various guarantees.
Program correctness can be viewed in part as a proof that the computation terminated
with correct output dependent on correct input. The user of the computation has the
responsibility of providing correct input. This is a precondition. The computation, if
successful, satisfies a postcondition. Such assertions can be monitored at runtime to
Ira Pohl’s C++ by Dissection 3.25 Software Engineering: Program Correctness 125
provide very useful diagnostics. Indeed, the discipline of thinking out appropriate
assertions frequently allows the programmer to avoid bugs and pitfalls.
In the C++ community, there is an increasing emphasis on the use of assertions. The
standard library assert provides the macro assert and is invoked as though its func-
tion signature were
void assert(expression);
If the expression evaluates as false, execution is aborted with diagnostic output. The
assertions are discarded if the macro NDEBUG is defined. The following program pro-
vides assertions to demonstrate this. The program examines a slice of an array for its
minimum element and places that element in the first examined array position.
In file order3.cpp
// Find minimum element in array slice
if (p > q) {
p = q;
q = temp;
}
}
int main()
{
int a[9] = { 6, -9, 99, 3, -14, 9, -33, 8, 11 };
Assertions and general exception handling are discussed at length in Chapter 10, Excep-
tions and Program Correctness.
Ira Pohl’s C++ by Dissection 3.26 Dr. P’s Prescriptions 127
3.26
3.26 Dr. P’s Prescriptions
3.27
3.27 C++ Compared with Java
Java does not have pointers but instead has nonprimitive variables that are references.
Java avoids much of the direct programmer management of memory that causes so
many bugs in C and C++. Java does have arrays, which are reference types. Java does
not have functions that are outside the scope of a class. Java’s term for functions is
methods, which indicates that all functions are members of a class. The closest con-
struct to an ordinary C or C++ function is a static method. Java can overload methods
but does not allow default arguments or inlining.
The following program initializes an array, prints its values, and computes its sum and
average value.
In file SumArray.java
class SumArray {
public static void main(String[] args)
{
int[] data = {1, 2, 3, 4, 5, 6, 7};
int sum = 0;
double average;
Note: In this example, main() is static. In Java, a static method more or less corre-
sponds to an ordinary C function. This example is nearly identical to one found on
pages 145 to 146 of Java by Dissection, Pohl and McDowell (Addison-Wesley 1999). A
complete explanation of Java arrays that is consistent with our treatment here can be
found in that text. It is also available as an eBook through MightyWords at http://
www.mightywords.com/.
Ira Pohl’s C++ by Dissection Summary 130
Summary
■ In ANSI C++, the empty parameter list is always equivalent to using void, so main()
is equivalent to main(void). The function main() implicitly returns the integer
value 0 if no explicit return expression statement is executed.
■ A formal parameter can be given a default argument, usually a constant that occurs
frequently when the function is called. Use of a default argument saves writing this
default value at each invocation. The following function header shows the syntax:
namespace StellarSoft {
class S_widget { ····· };
class update{ ····· };
·····
}
■ The namespace identifier can be used as part of a scope-resolved identifier. This has
the form
namespace_id::id
There is also a using declaration, which lets a client have access to all names from
that namespace.
Namespaces can be used to provide a unique scope that replaces static global decla-
rations.
■ The declaration void* is a generic pointer type. A pointer declared as type pointer
to void, as in void* gp, can be assigned a pointer value of any underlying base
type, but it may not be dereferenced. Unlike in C, a generic pointer may not be
assigned to a nonvoid pointer type without an explicit cast. In this regard, C++ is
again more type-safe than C.
■ The C and C++ communities have agreed to treat the type char* as a form of string
type. The understanding is that these strings are terminated by the char value '\0',
and that the cstring (or string.h on older systems) package of functions is called on
this abstraction. In ANSI C++, the library string provides as a template class a stan-
dardized string type that is preferred over the use of char*.
■ The unary operators new and delete are available to manipulate free store. Free
store is a memory pool for objects whose lifetime is directly managed by the pro-
grammer. The programmer creates an object by using new and destroys the object
by using delete. This is important for dynamic data structures, such as lists and
trees.
■ The standard library contains the template for the vector data structure. In almost
all cases, the vector is an improvement over the simple C++ array but can be used
essentially as an array. Many experts recommend that it be used in place of arrays
for most programming.
Ira Pohl’s C++ by Dissection Review Questions 132
Review Questions
3. Discuss the difference between using the macro ABS(f(y)) and the equivalent
inline call. Assume that f(y) calls a nontrivial function.
4. What is wrong with overloading int foo(); and void foo(); in the same scope?
Note that the only difference in their declarations is the return types.
5. The C++ STL vector can be used to replace in C and C++ programs.
9. The operator is used in place of the cstdlib function free() to return memory
to free store.
10. In C, call-by-reference requires the use of pointers, but in C++, may be used as
well.
Ira Pohl’s C++ by Dissection Exercises 133
Exercises
1. Pointers to char strings are by convention terminated with the value 0. The follow-
ing function implements a string-equality test. Note its use of pointer arithmetic.
The construct *s1++ means “dereference the pointer s1, and after using this value
in the expression, add sizeof(char) to its pointer value.” Here is streq() from
Section 3.22, Core Language ADT: char* String, on page 117:
2. Reimplement the preceding functions using array notation, both in the header and
the body of the code. So the header for streq() is
GCD(m,n) is:
if m mod n equals 0 then n;
else GCD(n, m mod n);
Ira Pohl’s C++ by Dissection Exercises 134
Recall that the modulo operator in C++ is %. Code this routine using recursion in
C++. We have already done this iteratively.
5. We wish to count the number of recursive function calls by gcd(). It is generally bad
practice to use globals inside functions. In C++, we can use a local static variable
instead of a global. Complete and test the following C++ gcd() function:
fcn_calls++;
·····
}
int main()
{
int i;
double x, cube();
printf("\n\nINTEGERS\n");
for (i = 1; i <= N; ++i)
printf("cube(%d) = %f\n", i, cube(i));
printf("\n\nREALS\n");
for (x = 1; x <= MAX; x += 0.3)
printf("cube(%f) = %f\n", x, cube(x));
return 0;
}
double cube(x)
double x;
{ return (x * x * x); }
The program gives the wrong answers for the integer arguments because integer
arguments are passed as if their bit representation were double. It is unacceptable
as C++ code. Recode, as a proper function prototype, and run, using a C++ compiler.
C++ compilers enforce type compatibility on function argument values. Therefore,
the integer values are properly promoted to double values.
int foo(int n)
{
static int count = 0;
++count;
if ( n <= 1) {
cout << " count = " << count << endl;
return n;
}
else
foo(n / 3);
}
int main()
{
foo(21);
foo(27);
foo(243);
}
8. The static storage class is useful in multifile compilation. Predict what the follow-
ing program prints:
// file A.c
int goo(int i)
{
return (i * foo(i));
}
// file B.c
int foo(int i)
{
return (i * 5);
}
int main()
{
cout << "foo(5) = " << foo(5) << endl;
cout << "goo(5) = " << goo(5) << endl;
}
Ira Pohl’s C++ by Dissection Exercises 136
The program is compiled as follows: g++ A.c B.c. File-scope functions are by default
extern. The foo() in file A.c is private to that file, but goo() is not. Thus, redefin-
ing foo() in file B.c does not cause an error. Try this again, this time dropping
static, to see what error message your compiler gives. Then try a third time, mak-
ing goo() inline in A.c, to see what error message your compiler gives. Recode
these files, using anonymous namespaces to replace the static declarations.
9. C++ provides a method to pass command line arguments into the function main().
The following code prints its command line arguments:
10. Modify the previous program to print the command line arguments from left to
right and to number each of them.
11. Write the function double maximum(double a[], int n). This is a function that
finds the maximum of the elements of an array of double of size n. (See Section
3.21, Software Engineering: Structured Programming, on page 112.)
12. The problem with using void* is that it cannot be dereferenced. Thus, to perform
useful work on a generic pointer, one must cast it to a standard working type, such
as a char*. Write and test
13. Write a program that performs string reversal. Assume that s1 ends up with the
reverse of the string s2 and that s1 points at enough store that is adequate for
reversal. (See Section 3.22, Core Language ADT: char* String, on page 115, for some
examples of string-handling functions.)
14. Write a program that performs string reversal, using storage allocated with new.
Assume that s1 ends up with the reverse of the string s2, and use new to allocate s1
of length strlen(s2) + 1, which is adequate store for s1.
if (p > q) {
p = q;
q = temp;
}
}
to make it more efficient. This can be done by reordering some of the operations.
This can be important in an application that calls this simple routine repeatedly.
19. Write a function findzero() that finds xzero, the value of x for which the function
fcn(x) is closest to zero in a specified interval. The function findzero() should
Ira Pohl’s C++ by Dissection Exercises 138
have the same arguments as findmin(). Again write it to have standard default val-
ues for its parameters.
20. Modify the dynamic array program in Section 3.24, Operators new and delete, on
page 121, so that it is initialized by pseudorandom numbers in the range (0,
RAND_MAX). For 5,000 such random numbers, find their average value. See whether,
while using the operator new, you can do this problem for 50,000, 500,000,
5,000,000, . . ., until you find a value on your system that causes new to fail. If you
rewrote this code to use ordinary stack-allocated arrays, at what size on your system
did it fail to allocate the array? Also try the same problem using vector<int>, and
see how large a problem can be run.
21. Write a program that simulates a roulette wheel with the numbers 0 to 35. This is
where the wheel has the numbers 0–35. You should use a random number generator
that gets you one of these values with equal probability. Test your simulation by
spinning the wheel 36, 360, 3,600, and 36,000 times. Store the results in an array of
36 integer values, one for each wheel location. Print the results. Were your results in
agreement with what you expected? In order to start the pseudorandom sequence at
different points, you should use the cstdlib function srand(n) to start the sequence
with the number seed n.
22. Using the functions written in the previous exercise, simulate a gambler making
1,000 bets of one dollar at odds of 35 to 1. Notice that the real odds should be 36 to
1. This favors the casino running the roulette wheel and is why casinos are so profit-
able. The gambler starts with 1,000 dollars. Print out how much the gambler has at
the end of her 1,000 bets. Consider this one trial. Now do this 1,000 times and see
what the average bankroll is after each 1,000 bets. Does this conform with what you
expected?
23. When a gambler persists at a game that favors the casino, it is likely that the gam-
bler will lose his shirt—this is called gambler’s ruin. Give your gambler 100 dollars.
Let him keep betting until he runs out of money. Count how many bets this took.
Notice that if you are very unlucky, this might take only 100 bets. Store this number
in an array, call it ruinLength[]. Do this for 1,000 trials and see what the mini-
mum, maximum, and average length to ruin was. Notice that by using a structured
programming approach, you should be able to easily design your program and com-
plete this exercise.
25. If the BMI is over 25, you are considered overweight; if it is over 40, you are consid-
ered obese. Test the program on data taken from at least five individuals, printing
out for each name a weight, height, BMI, and BMI category of normal, overweight, or
obese. Store the data in arrays or in vectors.
26. (Java) Recode the BMI program in Java. Use Java arrays to store values for each indi-
vidual.
CHAPTER 4
T his chapter introduces the reader to classes. The original name given by Bjarne
Stroustrup to his language was “C with classes.” The name C++ was coined by Rick Mas-
citti in 1983, being a pun on the ++ increment operator. Stroustrup had extensive expe-
rience with Simula67, the first object-oriented language. It was developed in 1967 to be
a simulation language and added the construct class to its base language, ALGOL60.
A class in C++ is an extension of the idea of struct found in C. A class packages a data
type with its associated functions and operators. This in turn can be used to implement
abstract data types (ADTs). An abstract data type, such as a stack, is a set of values and
operations that define the type. The type is abstract in that it is described without its
implementation. It is the job of the C++ programmer to provide a concrete representa-
tion of the ADT. This is usually done with the class.
C++ classes bundle data declarations with function declarations, thereby coupling data
with behavior. The class description also has access modifiers public and private
that allow data hiding. Allowing private and public visibility for members gives the pro-
grammer control over what parts of the data structure are modifiable. The private parts
are hidden from client code, and the public parts are available. It is possible to change
the hidden representation, but not to change the public access or functionality. If this is
done properly, client code need not change when the hidden representation is modi-
fied. A large part of the object-oriented programming (OOP) design process involves
thinking up the appropriate ADTs for a problem. Good ADTs not only model key fea-
tures of the problem but also are frequently reusable in other code.
Ira Pohl’s C++ by Dissection 4.1 The Aggregate Type class and struct 140
4.1
4.1 The Aggregate Type class and struct
The class or struct type allows the programmer to aggregate components into a sin-
gle named variable. A class has components, called members, that are individually
named. Since the members of a structure can be of various types, the programmer can
create aggregates that are suitable for describing complicated data.
As a simple example, let us define a structure that describes a point. We can declare the
structure type as follows:
struct point {
double x, y;
};
In C++, the structure name, or tag name, is a type. In the preceding declaration, struct
is a keyword, point is the structure tag name, and the variables x and y are members of
the structure. The declaration point can be thought of as a blueprint; it creates the type
point, but no instances are allocated. The declaration
point pt;
allocates storage for the variable pt. To access the members of pt, we use the member
access operator, represented by a period, or dot. It is a construct of the form
structure_variable.member_name
The construct is used as a variable in the same way that a simple variable or an element
of an array is used. Suppose that we want to assign to pt the value (–1, +0.5). To do this,
we can write
pt.x = -1;
pt.y = 0.5;
The member name must be unique within the specified structure. Since the member
must always be prefaced or accessed through a unique structure variable identifier,
Ira Pohl’s C++ by Dissection 4.2 Member Selection Operator 141
there is no confusion between two members that have the same name in different struc-
tures, as in
struct fruit {
char name[15];
int calories;
};
struct vegetable {
char name[15];
int calories;
};
struct {
int a, b, c;
} triples [2] = { {3, 3, 6}, {4, 5, 5} };
Note: Omitting the semicolon at the end of a declaration is a typical syntax error.
We use two-dimensional point example in much of this chapter. You should see at dif-
ferent places in the text whether you can extend these ideas to a three-dimensional
point. To test your understanding, do exercise 2 on page 179.
4.2
4.2 Member Selection Operator
Now we introduce the member selection operator ->, which provides access to the
members of a structure via a pointer. This operator is typed on the keyboard as a minus
sign followed by a greater-than sign. If a pointer variable is assigned the address of a
structure, a member of the structure can be accessed by a construct of the form
(*pointer_to_structure).member_name
Ira Pohl’s C++ by Dissection 4.2 Member Selection Operator 142
The operators -> and ., along with () and [ ], have the highest precedence, and they
associate left to right. In complicated situations, the two accessing modes can be com-
bined. Here is the point structure:
struct point {
double x, y;
};
Table 4.1 illustrates its use:
The member w.x was assigned 1. Therefore, the equivalent expression “pointer p
accessing member x” is 1. The assignment v[0] = w assigns values member by mem-
ber. Therefore, v[0].x is 1. A more complete example using point is the following:
In file struct_point1.cpp
// Compute an average point
int main()
{
point data[5] = { {1.0, 2.0}, {1.0, 3.3},
{5.1, 0.5}, {2.0, 2.0}, {0, 0} };
point average_point;
4.3
4.3 Member Functions
C++ allows functions to be members. C allows only data members. The function decla-
ration is included in the structure declaration. The idea is that the functionality
required by the structure or class should often be directly included in the class or
struct declaration. Such functions are called class methods. This term method, mean-
ing member function, comes from object-oriented programming methodology. This con-
struct improves the encapsulation of the ADT point operations by packaging it
directly with its data representation. An informal idea for designing an object is to
think of the object as a noun, such as point, and to think of methods as verbs that
apply to the noun, such as print(). Let us add a printing operation and an initializing
operation to the ADT point:
In file point1.cpp
struct point {
double x, y;
void print() const { cout << "(" << x << ","
<< y << ")"; }
void set(double u, double v) { x = u; y = v; }
};
The member functions, or methods, are written in much the same way that other func-
tions are. One difference is that they can use the data member names directly. Thus, the
member functions in point use x and y in an unqualified manner. When invoked on a
particular object of type point, they act on the specified member in that object.
Let us use these member functions in an example:
Ira Pohl’s C++ by Dissection 4.3 Member Functions 144
int main()
{
point w1, w2;
w1.set(0, 0.5);
w2.set(-0.5, 1.5);
cout << "\npoint w1 = ";
w1.print();
cout << "\npoint w2 = ";
w2.print();
cout << endl;
}
This prints
point w1 = (0,0.5)
point w2 = (-0.5,1.5)
■ int main()
{
point w1, w2;
The newly defined type looks like any of the native types. Here, two
points are declared in main().
■ w1.set(0, 0.5);
w2.set(-0.5, 1.5);
cout << "\npoint w1 = ";
w1.print();
Notationally, to call methods of type point requires a point variable
dotted to the method name. In the first line, the point w1 is set to
the coordinates (0, 0.5). In the last line, these coordinates would be
printed out by the method print().
Member functions that are defined within the struct are implicitly inline. As a rule,
only short, heavily used member functions should be defined within the struct, as in
the example just given. To define a member function outside the struct, the scope res-
olution operator is used (see Section 4.6, Class Scope, on page 150). Let us illustrate this
by adding a member function, point::plus(). We write it out fully, using the scope
resolution operator. In this case, the function is not implicitly inline.
In file point1.cpp
struct point {
·····
void plus(point c); // function prototype
·····
};
In file point1.cpp
struct point {
·····
void print(const string& name) const;
·····
};
4.4
4.4 Access: Private and Public
In C++, structures have public and private members. The keyword private followed
by a colon is used to declare subsequent members to have private access. The private
members can be used by only a few categories of functions. Class member functions
can use private members, and friend functions of the class can use private members.
Friend functions are discussed in Section 5.10, Friend Functions, on page 211.
The keyword public followed by a colon is used to declare subsequent members to
have public access. The public members can be used by any code.
We modify our example of point to hide its data representation, as follows:
In file point2.cpp
struct point {
public:
void print() const { cout << "(" << x << ","
<< y << ")"; }
void print(const string& name) const;
void set(double u, double v) { x = u; y = v; }
void plus(point c);
private:
double x, y;
};
Ira Pohl’s C++ by Dissection 4.5 Classes 147
In the following code, an attempt by a nonmember function, foo(), to access the now
private members x and y results in a syntax error:
void foo(point w)
{
·····
cout << " x coordinate = " << w.x; // syntax error
·····
}
The keyword protected followed by a colon is used to declare subsequent members to
have protected access. The protected members can be thought of as private members
but with special rules when they are used by a derived class. This is not used here but is
explained in Section 8.1, A Derived Class, on page 330.
Hiding data is an important component of OOP. It allows for more easily debugged and
maintained code because errors and modifications are localized. Client programs need
be aware only of the type’s interface specification. This is also known as the black box
principle. A good design hides unnecessary implementation detail and presents the sim-
plest possible useful user interface to the client.
4.5
4.5 Classes
Classes in C++ are introduced by the keyword class. A form of struct, classes have a
default privacy specification of private, in contrast to structures defined with struct,
which have a default privacy specification of public. Thus, struct and class can be
used interchangeably with the appropriate access specifications. In the following exam-
ple, we modify point to use class:
Ira Pohl’s C++ by Dissection 4.5 Classes 148
In file point3.cpp
class point {
double x, y; // implicitly private
public:
void print() const { cout << "(" << x << "," << y << ")"; }
void print(const string& name) const;
void set(double u, double v) { x = u; y = v; }
void plus(point c);
};
Contemporary C++ style is to use access specifiers explicitly rather than rely on
defaults. The use of implicit features is labor-saving but error-prone. Therefore, it is
better style to declare point as follows:
In file point4.cpp
class point {
public: // place public members first
void print() const { cout << "(" << x << "," << y << ")"; }
void print(const string& name) const;
void set(double u, double v) { x = u; y = v; }
void plus(point c);
private:
double x, y;
};
When access keywords are used, struct and class are interchangeable. Stylistically,
professional C++ programmers use class in preference to struct unless the struct
has only public data members. This text uses access keywords explicitly and places
public members first and private members last. In this need-to-know style, everyone
needs to know the public interface, but only the class provider needs to know the pri-
vate implementation details.
At this point, you should at this point be able to write a class pair. The class pair,
like point, has two fundamental values; for example, the first value might be a name
and the second value a telephone number. Such a pair could provide you an online way
of keeping your personal phone list.
I’m sorry, sir, but I really cannot let you have accessunless you are a member.
Ira Pohl’s C++ by Dissection 4.5 Classes 149
As a second example, let us write an ADT for customer, which many business applica-
tions require. As part of this representation, we will use the Standard Template Library
(STL) string type. This type overloads the binary operator + to produce string concate-
nation.
In file customer.cpp
enum c_kind { general, wholesale, retail };
class customer {
public:
void set_name(const string& l, const string& f)
{ last_name = l; first_name = f; }
c_kind get_kind() const { return t; }
void set_kind(c_kind k) { t = k; }
void print() const { cout << (first_name + " "
+ last_name) << endl; }
double price_discount() const;
private:
string last_name, first_name;
int id_number;
c_kind t;
};
int main()
{
customer c, d;
c.set_name("Pohl", "Ira");
c.set_kind(wholesale);
c.print();
cout << "\nYour PC costs "
<< 900 * (1 - c.price_discount())
<< " dollars." << endl;
}
Ira Pohl’s C++ by Dissection 4.6 Class Scope 150
Ira Pohl
Your PC costs 720 dollars.
4.6
4.6 Class Scope
Classes add new scope rules to those of the kernel language. Classes provide an encap-
sulation technique. Conceptually, it makes sense that all names declared within a class
be treated within their own scope, as distinct from external names, namespace names,
function names, and other class names, creating a need for a scope resolution operator.
In file how_many1.cpp
int count = 0; // global count
In file how_many2.cpp
int count = 0; // global count
widgets w;
gizmos g;
g.f();
w.f();
g.gizmos::f(); // legal but redundant
g.widgets::f(); // illegal-widgets can’t act on gizmo
Ira Pohl’s C++ by Dissection 4.6 Class Scope 152
In file nested.cpp
char c; // external scope ::c
void foo()
{
class local { ····· } x;
}
4.7
4.7 An Example: Flushing
We want to estimate the probability of being dealt a flush in poker. A flush occurs when
at least five cards are of the same suit. We simulate shuffling cards by using a random
number generator. This is a form of Monte Carlo calculation, named after the famous
gambling resort. As was already mentioned in Section 3.20, Problem Solving: Random
Numbers, on page 108, a Monte Carlo calculation is a computer simulation program
requiring a probability calculation. The program uses classes to represent the necessary
data types and functionality. The key data type is card, which consists of a suit value
and a pips value. A pips value is between 1 and 13. On an actual card, these 13 pips
values are ace, 2, 3, ·····, 10, jack, queen, and king.
In file poker.cpp
enum suit { clubs, diamonds, hearts, spades };
class pips {
public:
void set_pips(int n) { p = n % 13 + 1; }
int get_pips() const { return p; }
void pr_pips() const { cout << p; }
private:
int p; // meant to hold values [1,13]
};
class card {
public:
void set_card(int n)
{ s = static_cast<suit>(n/13); p.set_pips(n); }
void pr_card() const;
suit get_suit() const { return s; }
pips get_pips() const { return p; }
private:
suit s;
pips p;
};
Ira Pohl’s C++ by Dissection 4.7 An Example: Flushing 154
class deck {
public:
void set_deck();
void shuffle();
void deal(int, int, card*);
void pr_deck() const;
private:
card d[52];
};
void deck::set_deck()
{
for (int i = 0; i < 52; ++i)
d[i].set_card(i);
}
void deck::shuffle()
{
for (int i = 0; i < 52; ++i) {
int k = i + (rand() % (52 - i));
card t = d[i]; // swap cards
d[i] = d[k];
d[k] = t;
}
}
class pips {
public:
void set_pips(int n) { p = n % 13 + 1; }
·····
private:
int p; // meant to hold values [1,13]
};
The class pips and the enum suit are used to build the card type.
The set_pips() method uses integers 0 to 51 to set an appropriate
pips value for a card. The clustering of member functions and the
data members they act on improves modularity. Behavior and
description are logically grouped together.
Ira Pohl’s C++ by Dissection 4.7 An Example: Flushing 155
■ class card {
public:
void set_card(int n)
{s=static_cast<suit>(n/13); p.set_pips(n);}
·····
private:
suit s;
pips p;
};
Each level of declaration hides the complexity of the previous level.
The class card uses suit and pips in its representation. The
set_card() method uses integer division to generate an enumerator
value. To recode suit as a class type, you could have a set_suit()
method do the same computation.
■ class deck {
public:
void set_deck();
void shuffle();
void deal(int, int, card*);
void pr_deck() const;
private:
card d[52];
};
The class deck declares only the class member functions; defini-
tions come later.
■ void deck::set_deck()
{
for (int i = 0; i < 52; ++i)
d[i].set_card(i);
}
The set_deck() function calls card::set_card() to map the inte-
gers into card values. Again we notice how each part of the design
enables us to segregate function and description into appropriate
object types.
Ira Pohl’s C++ by Dissection 4.7 An Example: Flushing 156
void deck::shuffle()
{
for (int i = 0; i < 52; ++i) {
int k = i + (rand() % (52 - i));
card t = d[i]; // swap cards
d[i] = d[k];
d[k] = t;
}
}
The shuffle() function uses the library-supplied pseudo-random
number generator rand() in stdlib to exchange two cards for every
deck position.
We now write main() to test these classes by computing the odds of getting a dealt-out
flush in a poker game. We allow the user to decide how many cards to play, as there are
many poker variants that require between five and nine cards per hand.
#include <iostream>
#include <ctime> // needed for time()
#include <cstdlib> // needed for rand() and srand()
using namespace std;
int main()
{
card one_hand[9]; // max hand is 9 cards
deck dk;
int i, j, k, flush_count = 0, sval[4];
int ndeal, nc, nhand;
do {
cout << "\nEnter no. cards in a hand (5-9): ";
cin >> nc;
} while (nc < 5 || nc > 9);
nhand = 52 / nc;
cout << "Enter no. of hands to deal: ";
cin >> ndeal;
srand(time(NULL)); // seed rand() from time()
dk.set_deck();
for (k = 0; k < ndeal; k += nhand) {
if ((nhand + k) > ndeal)
nhand = ndeal - k;
dk.shuffle();
for (i = 0; i < nc * nhand; i += nc) {
for (j = 0; j < 4; ++j) // zero suit counts
sval[j] = 0;
dk.deal(nc, i, one_hand); // deal next hand
for (j = 0; j < nc; ++j)
sval[one_hand[j].get_suit()]++; // +1 to suit
Ira Pohl’s C++ by Dissection 4.7 An Example: Flushing 157
You can test your understanding of the poker program by modifying it to compute the
probability of other poker hands. It is straightforward to compute whether a hand has a
straight. A straight is a hand that has five cards whose pips value are in sequence, such
as having a (3, 4, 5, 6, 7).
A Bold Bluff
One of a series of
Dogs Playing Poker
by C. M. Coolidge
4.8
4.8 The this Pointer
The keyword this denotes an implicitly declared self-referential pointer that can be
used in a nonstatic member function. Later, we discuss static member functions, where
the this pointer is not available. A simple illustration of the pointer’s use follows.
In file point5.cpp
// Class illustrating the use of the this pointer
class point {
public: // place public members first
void print() const { cout << "(" << x << ","
<< y << ")"; }
void print(const string& name) const;
void set(double u, double v) { x = u; y = v; }
void plus(point c);
point inverse() {x = -x; y = -y; return (*this);}
point* where_am_I() { return this; }
private:
double x, y;
};
void point::plus(point c)
{
x += c.x;
y += c.y;
}
Ira Pohl’s C++ by Dissection 4.9 static Members 159
int main()
{
point a, b;
a.set(1.5, -2.5);
a.print();
cout << "\na is at " << a.where_am_I() << endl;
b = a.inverse();
b.print();
cout << "\nb is at " << b.where_am_I() << endl;
}
The output on our system is
(1.5,-2.5)
a is at 0x0064fdd4
(-1.5,2.5)
b is at 0x0064fdc4
Note that machine addresses are displayed in hexadecimal and are system-dependent.
In this case, the two addresses differ by hexadecimal 0x10, or decimal 16 bytes, the size
of the two doubles required to represent a point.
4.9
4.9 static Members
C++ allows static members. Using the modifier static when declaring a data member
means that the data member is independent of any given class variable. The data mem-
ber is part of the class but separate from any single class object. Nonstatic data mem-
bers are created for each instance of the class. A static data member is commonly
accessible by all instances of its class; it is a global member within class scope. Another
Ira Pohl’s C++ by Dissection 4.9 static Members 160
difference between static and nonstatic members is that static members cannot use
the this pointer.
Since a static member is independent of a particular instance, it can be accessed in the
form
class-name :: identifier
Note the use of the scope resolution operator. A static member of a global class must be
explicitly declared and defined in file scope. For example, if we want a counter to keep
track of how many points are declared at any time, we can add to class point as fol-
lows:
class point {
public:
static int how_many; // declaration
·····
};
class point {
·····
static print_how_many(); // static goes first
·····
};
4.10
4.10 const Members
A data member declared with the const modifier cannot be modified after initializa-
tion. A nonstatic member function can also have the const modifier. Syntactically, a
const member function has the modifier const follow the argument list inside the
class declaration, and its definition outside the class must also have this modifier. A
const member function is not allowed to modify any of its implicit arguments.
class person {
·····
int print_age() const;
private:
int age;
};
x.method(i, j, k);
has an explicit argument list i, j, k and an implicit argument list that includes the mem-
bers of x. The implicit arguments can be thought of as a list of arguments accessible
through the this pointer. A const member function cannot modify its implicit argu-
ments. Writing out const member functions and parameter declarations is called const-
correctness and is an important aid in writing code. In effect, it is an assertion that the
compiler should check that an object does not have its values modified. Const-correct-
ness can also allow the compiler to apply some special optimizations, such as placing a
const object in read-only memory. The following example illustrates these ideas:
In file salary.cpp
// Calculate salary using static members
class salary {
public:
void set(int b) { b_sal = b; your_bonus = 0; }
void calc_bonus(double perc)
{ your_bonus = b_sal * perc; }
static void set_all_bonus(int p)
{ all_bonus = p; }
int comp_tot() const
{ return (b_sal + your_bonus + all_bonus); }
private:
int b_sal;
int your_bonus;
static int all_bonus; // declaration
};
Ira Pohl’s C++ by Dissection 4.10 const Members 162
int main()
{
salary w1, w2;
w1.set(1000);
w2.set(2000);
w1.calc_bonus(0.2);
w2.calc_bonus(0.15);
salary::set_all_bonus(400);
cout << " w1 " << w1.comp_tot() << " w2 "
<< w2.comp_tot() << endl;
}
class salary {
·····
private:
const static int all_bonus = 1000; // initializer
·····
};
In file person.cpp
class person { // Class with mutable members
public:
person(const string namep, int agep, unsigned long ssn)
: name(namep), age(agep), soc_sec(ssn) { }
void bday() const { ++age; }
void print() const { cout << name << " is " << age
<< " years old. SSN = " << soc_sec << endl; }
private:
mutable int age; // always modifiable
const unsigned long soc_sec;
const string name;
};
int main()
{
const person ira("ira pohl", 38, 1110111);
ira.print();
ira.bday(); // okay, ira.age is mutable
ira.print();
}
The key point is that without the mutable modifier on the declaration of age, bday()
could not modify ira’s age normally because ira was declared const. To fully under-
stand this example, you will need to read about constructors and initializer syntax in
Chapter 5, Ctors, Dtors, Conversions, and Operator Overloading.
4.11
4.11 A Container Class Example: ch_stack
A container is a data structure whose main purpose is to store and retrieve a large num-
ber of objects. In the kernel language, an array acts as such a structure. In this section,
we develop code that is used to store character values in a stack, which is a last-in-first-
out (LIFO) container. We code the stack class ch_stack that stores characters.
In file ch_stack1.h
class ch_stack {
public:
void reset() { top = EMPTY; }
void push(char c) { +s[++top] = c; }
char pop() { return s[top--]; }
char top_of() const { return s[top]; }
bool empty() const { return (top == EMPTY); }
bool full() const { return (top == FULL); }
Ira Pohl’s C++ by Dissection 4.11 A Container Class Example: ch_stack 165
private:
enum { max_len = 100, EMPTY = -1, FULL = max_len - 1 };
char s[max_len];
int top;
};
The basic operations on a stack are push and pop. The push operation places a value on
the top of the stack, and the pop operation removes the value at the top of the stack.
We use a fixed-length char array to implement the stack. Later, we talk about other,
more flexible implementations.
We now write main() to test the same operations.
In file ch_stack1.cpp
// Reverse a string with a ch_stack
int main()
{
ch_stack s;
char str[40] = { "My name is Don Knuth!" };
int i = 0;
4.12
4.12 Software Engineering: Class Design
The access order for classes has traditionally been private first:
class ch_stack {
// private by default
int top;
enum { max_len = 100, EMPTY = -1,
FULL = max_len - 1 };
char s[max_len];
public:
void reset() { top = EMPTY; }
void push(char c) { s[++top] = c; }
char pop() { return s[top--]; }
char top_of() const { return s[top]; }
bool empty() const { return (top == EMPTY); }
bool full() const { return (top == FULL); }
};
The reason is that in the original form of C++, only the access keyword public was
present. The access keywords private and protected did not exist. By default, mem-
ber access for class was private; therefore, the private members had to come first.
The style of public first is becoming the norm. It follows the rule that the widest audi-
ence needs to see the public members. More specialized information is placed later in
the class declaration. This can be thought of as a need-to-know principle or newspaper
Ira Pohl’s C++ by Dissection 4.12 Software Engineering: Class Design 167
principle. In a newspaper, the first sentence gives the most important and most widely
disseminated information. Details are left for later.
Data members should in general be private. This is an important coding heuristic. Gen-
erally, data are part of an implementation choice and should be accessed through pub-
lic member functions. Such member functions are called accessor functions when they
do not change, or mutate, the data. This is not necessarily inefficient because simple
accessor member functions can be inline. In the class ch_stack, the member functions
top_of(), empty(), and full() are all inline accessor functions. Accessor functions
should be declared const. The member function reset() is a mutator. It allows a con-
strained action on the hidden variable top. Notice how much safer such a design is. If
top were directly accessible, it would be easy for it to be inappropriately changed.
In OOP design, the public members are usually functions and are thought of as the
type’s interface. These are the actions, or behaviors, publicly expected of an object. If
we think of the object type as a noun, the behaviors are verbs. In the implementation,
data members are generally placed in private access. This is a key data-hiding principle;
namely, that implementation is kept inside a black box that cannot be directly exploited
by the object’s user.
There is a way through indirection to provide additional data hiding. This is through the
use of a separate class for the underlying data representation. This technique is called
the Cheshire Cat technique, in honor of Lewis Carroll’s cat that disappeared leaving
only a smile. Let us recode class ch_stack to use this technique:
class ch_stack {
public:
void reset() { ptr -> reset(); }
void push(char c)
{ ptr->push(c); }
char pop() { return ptr->pop(); }
char top_of() const { return ptr->top_of(); }
bool empty() const
{ return ptr -> empty(); }
bool full() const
{ return ptr -> full(); }
private:
ch_stk_rep* ptr; // opaque pointer
};
All the data and underlying operations are handled through the ch_stk pointer. The
class ch_stack is therefore known as a wrapper class. The relationship between the
wrapper class and the underlying representation class is called the handle design pat-
tern. We will illustrate this relationship when we introduce Unified Modeling Language
(UML) diagrams in Section 4.12.2, Unified Modeling Language (UML) and Design, on page
169.
Ira Pohl’s C++ by Dissection 4.12 Software Engineering: Class Design 168
class ch_stk_rep {
public:
void reset() { top = EMPTY; }
void push(char c)
{ s[top++] = c; }
char pop() { return s[top--]; }
char top_of() const { return s[top]; }
bool empty() const
{ return (top == EMPTY); }
bool full() const
{ return (top == FULL); }
private:
enum { max_len = 100, EMPTY = -1,
FULL = max_len - 1 };
int top;
char s[max_len];
}
class suit {
public:
void set(int n) { s = n / 13; }
int get_suit() const { return s; }
void print() const;
private:
enum suit_val
{ clubs, diamonds, hearts, spades } s;
};
Ira Pohl’s C++ by Dissection 4.12 Software Engineering: Class Design 169
We add the member function get_suit() to access the hidden integer value of a suit
variable. The advantage is that suit and pips are now treated symmetrically, with both
being given class definitions. The disadvantage is that we have added more code and a
layer of methods to access what is basically a simple type having four unique values.
There is no clear answer as to which choice for suit is better. In one sense, the curse of
C++ is that there are too many opportunities, but this is also its great benefit over sim-
pler languages such as Java.
person
name
age
soc_sec
bday()
A class diagram describes the types and relationships in the system. It is very useful
documentation, and a number of systems, such as Rational Rose, now provide auto-
mated tools to develop such documentation along with coding. A relationship that can
be depicted by UML includes the part-whole, or aggregation, relationship (HASA). For
example, a handle type such as ch_stack has a representation class class
ch_stk_rep pointer.
ch_stack ch_stk_rep
ptr top
s[]
push() push()
The representation class is used to concretely implement the handle class. This rela-
tionship recurs in many object-oriented coding schemes. It is called the bridge or han-
dle design pattern. A design pattern is a recurring software solution to a problem,
usually involving several classes collaborating to solve the problem. The book Design
Patterns: Elements of Reusable Object-Oriented Software, by Gamma, et al. (Addison-
Wesley, 1995) popularized this approach by listing over 20 such patterns with catchy
names, such as the bridge pattern.
4.13
4.13 Dr. P’s Prescriptions
■ Indentation is as follows: class, access keywords, and closing brace all line up and
are placed on separate lines. Member declarations are indented and line up verti-
cally.
■ Access privileges are in order: public, protected, and private.
■ Data members should be private.
■ const your member functions where possible.
■ Provide a uniform set of methods, such as set(), get(), and print().
■ Use inlining only when vital to performance.
The indentation rules are consistent with industry practice. The idea behind placing
more visible members first is based on the same logic used in newspaper articles—
namely, what everyone needs to know comes first. What everyone needs to know are the
public members. This is the interface available to all users of the class.
In most designs, it is appropriate to make data members private. As we explained, this
is the black box principle. The builder (read: programmer) hides the details of the
implementation. The client benefits by having to see and understand fewer details and
being protected from obvious misapplications.
Many member functions, such as print methods, are accessor but not mutator func-
tions. This means they do not change values of the object they are using. In these cases,
the methods should be declared const. This is good programming methodology. It
allows the compiler to check for key correctness attributes of the code. It also allows
the compiler to perform certain optimizations that come from knowing the object’s
value is not changed by the method. Note: const does not have an effect on what the
function computes, so many lazy programmers choose to not use it.
A client expects to print information about a data type, so almost all data types need
print methods in their interface. A client expects to retrieve and change key values of
the object. A proper choice of set and get methods allows the class programmer to pro-
vide these services. Providing these functions with like names and uses makes it easier
to code new types and have clients easily use them.
Inlining comes at a cost. The inline function is expanded to a body of code rather than
just a function call. This can cause code bloat. More important, inlining forces develop-
ers to recompile their entire code base when these functions are rewritten. Changing a
Ira Pohl’s C++ by Dissection 4.14 C++ Compared with Java 171
non-inline function requires only a relink as long as the interface remains the same.
(George Belotsky mentioned this in a private note.)
4.14
4.14 C++ Compared with Java
Java classes are based on the C++ aggregate type class. A class provides the means
for implementing a user-defined data type and associated functions. Therefore, a class
can be used to implement an ADT. Unlike in C++, however, functions, or methods, as
they are called in Java, cannot exist outside a class construct. Also Java class types are
always reference types. The Java primitive types, such as int or char are value types.
Let us write a class called Person that is used to store information about people:
In file Person.java
// An elementary Java implementation of type Person
class Person {
public void setName(String nm) { name = nm; }
public void setAge(int a) { age = a; }
public void setGender(char b) { gender = b; }
public String toString()
{ return (name + " age is " + age
+ " gender is " + gender); }
private String name;
private int age;
private char gender; // male 'M', female 'F'
};
As with C++ classes, Java has two important additions to the structure concept of tradi-
tional C. First, Java has members called class methods that are functions, such as set-
Age(). Second, Java has both public and private members. The keyword public
indicates the visibility of the members that follow it. Without this keyword, the mem-
bers are private to the class. Private members are available for use only by other mem-
ber functions of the class. Public members are available anywhere the class is available.
Privacy allows part of the implementation of a class type to be hidden and prevents
unanticipated modifications to the data structure. Restricted access, or data hiding, is a
feature of object-oriented programming.
The declaration of methods inside a class allows the ADT to have actions, or behaviors,
that can act on its private representation. For example, the member function
toString() has access to private members and gives Person a string representation
used in output. This method is common to many class types.
We can now use this data type Person as if it were a basic type of the language. Other
code that uses this type is a client. The client can use only the public members to act on
variables of type Person.
Ira Pohl’s C++ by Dissection 4.15 Advanced Topics 172
Person test:
Alan Turing age is 20 sex is M
Notice the use of new Person() to create an instance of Person. The new operator goes
off to the heap, as it does in C++, and obtains memory for creating an instance of object
Person. The value of p1 is a reference to this object. In effect, this is the address of the
object. For a more detailed look of a similar example, and explanation of the nuances of
Java classes, read Java by Dissection (Addison-Wesley) by Ira Pohl and Charlie McDowell,
pages 234 to 242.
4.15
4.15 Advanced Topics
This section can be omitted on a first reading, as it is about less used and arcane facili-
ties.
In file show_hide.cpp
//Pointer to class member
class X {
public:
int visible;
void print() const
{ cout << "\nhide = " << hide
<< " visible = " << visible; }
void reset() { visible = hide; }
void set(int i) { hide = i; }
private:
int hide;
};
int main()
{
X a, b, *pb = &b;
int X::*pXint = &X::visible;
pfcn pF = &X::print;
a.set(8); a.reset();
b.set(4); b.reset();
a.print();
a.*pXint += 1;
a.print();
cout << "\nb.visible = " << pb ->*pXint;
(b.*pF)();
pF = &X::reset;
(a.*pF)();
a.print();
cout << endl;
}
The output is as follows:
hide = 8 visible = 8
hide = 8 visible = 9
b.visible = 4
hide = 4 visible = 4
hide = 8 visible = 8
The typedef void (X::*pfcn)(); statement says that pfcn is a pointer to class X
member whose base type is a function with no arguments that returns void. Member
functions X::print and X::reset match this type.
Ira Pohl’s C++ by Dissection 4.15 Advanced Topics 174
The declaration
4.15.2 Unions
This section is about a dangerous type-construction facility, the union. A union is a
derived type whose syntax is the same as for structures except that the keyword union
replaces struct. The member declarations share storage, and their values are overlaid.
Therefore, a union allows its value to be interpreted as a set of types that correspond to
the member declarations.
A union initializer is a brace-enclosed value for its first member. Consider the following
declaration:
In file union.cpp
union int_dbl {
int i;
double x;
} n = { 0 }; // i member is set to zero
Now we write main() to show how the variable n can be used as either an integer type
or a double type.
int main()
{
n.i = 7; // int value 7 is stored in n
cout << n.i << " is integer. ";
cout << n.x << " is double-machine dependent.\n";
n.x = 7.0; // double value 7.0 in n
cout << n.i << " is integer - machine dependent.";
cout << n.x << " is double." << endl;
}
Ira Pohl’s C++ by Dissection 4.15 Advanced Topics 175
This example also illustrates why unions can be dangerous and are often system-depen-
dent. On some systems, it is possible that not all bit patterns are legal values for the
overlaid types. In that case, a legal value with one type might, when accessed as the
other type, lead to an exception.
A union can be anonymous, as in the following code:
In file weekend.cpp
enum week { sun, mon, tues, weds, thurs, fri, sat };
static union {
int i;
week w;
};
int main()
{
i = 5;
if (w == sat || w == sun)
cout << " It's the weekend! ";
}
The anonymous union allows the individual member identifiers to be used as variables.
The member names must be unique within scope, and no variables of the anonymous
type can be declared. Note that an anonymous union declared in file scope must be
static.
In file set.cpp
struct word {
unsigned w0:1, w1:1, w2:1, w3:1, w4:1, w5:1, w6:1,
w7:1, w8:1, w9:1, w10:1, w11:1, w12:1, w13:1,
w14:1, w15:1, w16:1, w17:1, w18:1, w19:1, w20:1,
w21:1, w22:1, w23:1, w24:1, w25:1, w26:1, w27:1,
w28:1, w29:1, w30:1, w31:1;
};
We can overlay word and unsigned within a union to create a data structure for manip-
ulating bits.
union set {
word m;
unsigned u;
};
int main()
{
set x, y;
x.u = 0x0f100f10;
y.u = 0x01a1a0a1;
x.u = x.u | y.u; // set union
cout << "element 9 = "
<< ((x.m.w9)? "true" : "false") << endl;
}
The set operation union is performed as a word-parallel operation on most systems. A
word-parallel operation means an operation that executes simultaneously on all the dis-
tinct bits contained in the machine word. This is far more efficient than processing each
bit sequentially.
C++ provides bit-manipulation operators (see Table 4.2). They operate on the machine-
dependent bit representation of integral operands. It is customary that the shift
operators be overloaded to perform I/O.
Summary
■ The original name Stroustrup gave to his language was “C with classes.” A class is an
extension of the idea of structure in traditional C. A class is a way of implementing a
data type and associated functions and operators. It is the mechanism in C++ for
implementing ADTs, such as complex numbers and stacks.
■ The structure type allows the programmer to aggregate components into a single
named variable. A structure has components, called members, that are individually
named. Critical to processing structures is the accessing of their members. This is
done with either the member access operator . or the member selection operator
-> . These operators, along with () and [], have the second-highest precedence.
Highest precedence belongs to scope resolution, ::.
■ The concepts of structure and class are augmented in C++ to allow functions to be
members. The function declaration is included in the structure declaration and is
invoked using access methods for structure members. The functionality required by
the struct data type should often be directly included in the struct declaration.
■ Member functions defined within the structure or class are implicitly inline. As a
rule, only short, heavily used member functions should be defined within the struc-
ture. When defined outside the structure, the scope resolution operator is used.
■ The scope resolution operator allows member functions of various structure types
to have the same names; which member function is invoked depends on the type of
object it acts on. Member functions within the same struct can be overloaded.
■ Structures have public and private members that provide data hiding. Inside a struc-
ture or class, the keyword private followed by a colon restricts the access of the
members that follow it. The private members are used by only a few categories of
functions, whose privileges include access to these members. These functions
include the member functions of the class.
■ Classes in C++ are a form of struct, whose default access specification is private.
Thus, struct and class can be used interchangeably with the appropriate access
specification.
■ Data members can be declared with the storage class modifier static. A data mem-
ber that is declared static is shared by all variables of that class and is stored in
one place only. Therefore, the data member can be accessed using the form
class-name :: identifier
■ Classes can be nested. The inner class is inside the scope of the outer class. This is
not in accordance with C semantics.
Ira Pohl’s C++ by Dissection Review Questions 178
Review Questions
5. The static modifier used in declaring a data member means that the data member
is .
8. LIFO means .
Ira Pohl’s C++ by Dissection Exercises 179
Exercises
1. Design a C++ structure to store a dairy product name, portion weight, calories, pro-
tein, fat, and carbohydrates. Twenty-five grams of American cheese have 375 calo-
ries, 5 grams of protein, 8 grams of fat, and 0 carbohydrates. Show how to assign
these values to the member variables of your structure. Write a function that, given a
variable of type struct dairy and a weight in grams (portion size), returns the
number of calories for that weight.
2. Write a structure point that has three coordinates x, y, and z. How can you access
the individual members?
3. Use the structure card defined in the poker program in Section 4.7, An Example:
Flushing, on page 153, to write a hand-sorting routine. In card games, most players
keep their cards sorted by pip value. The routine places aces first, kings next, and so
forth, down to twos. A hand is five cards.
struct brother {
char name[20];
int age;
struct sister sib;
} a;
struct sister {
char name[20];
int age;
struct brother sib;
} a;
5. In this exercise, use the class ch_stack, defined in Section 4.11, A Container Class
Example: ch_stack, on page 164. Write the function
6. Rewrite the functions push() and pop() discussed in Section 4.11, A Container
Class Example: ch_stack, on page 164, to test that push() is not acting on a full
ch_stack and that pop() is not acting on an empty ch_stack. If either condition is
detected, print an error message, using cerr, and use exit(1) (in cstdlib) to abort
the program. Contrast this to an approach using asserts.
8. For the ch_stack type in Section 4.4, Access: Private and Public, on page 146, write
as member functions
struct a {
int i, j, k;
};
and the class
class a {
int i, j, k;
};
Explain why the class declaration is not useful. How can you use the keyword pub-
lic to change the class declaration into a declaration equivalent to struct a?
10. Recode as a class the data type deque, which is a double-ended queue that allows
pushing and popping at both ends.
class deque {
public:
void reset() {top=bottom = max_len / 2; top--; }
·····
private:
char s[max_len];
int bottom, top;
};
Declare and implement push_t(), pop_t(), push_b(), pop_b(), print_stack(),
top_of(), bottom_of(), empty(), and full(). The function push_t() stands for
push on top and pop_t() for pop on top; push_b() stands for push on bottom and
pop_b() for pop on bottom. The print_stack() function should output the stack
from bottom to top. An empty stack is denoted by having the top fall below the bot-
tom. Test each function. Draw the UML diagram for this class.
11. Extend the data type deque by adding a member function relocate(). If the deque
is full, relocate() is called, and the contents of the deque are moved to balance
Ira Pohl’s C++ by Dissection Exercises 181
empty storage around the center max_len/2 of array s. Its function declaration
header is
12. Recode deque to hide the representation in a class deque_rep. Draw an appropri-
ate UML diagram for this handle class design.
13. Write a function that swaps the contents of two strings. If you pushed a string of
characters onto a ch_stack and popped them into a second string, they would come
out reversed. In a swap of two strings, we want the original ordering. Use a deque to
do the swap. The strings are stored in character arrays of the same length, but the
strings themselves may be of differing lengths. The function prototype is
14. Write a function pr_hand() that prints out card hands. Add it to the poker program
and use it to print out each flush.
15. In Section 4.7, An Example: Flushing, on page 153, main() detects flushes. Write a
function
17. Use the previous exercises to determine the probability that a poker hand results in
a straight flush. This is the rarest poker hand and has the highest value. Note that, in
a hand of more than five cards, it is not sufficient to merely check for the presence
of both a straight and a flush to determine that the hand is a straight flush.
Ira Pohl’s C++ by Dissection Exercises 182
class suit {
public:
void assign(int n) { s = n / 13; }
int getsuit() const { return s; }
void print() const;
private:
suit_val s;
};
We add the member function getsuit() to access the hidden integer value of a
suit variable. Now recode all references to suit throughout the program.
19. Change class ch_stack to int_stack by substituting type int for type char in the
class definition as appropriate. In Section 6.1, Template Class stack, on page 246,
we see how to use templates to automate this process.
20. Redesign the roulette simulation problem of Chapter 3, Functions, Pointers, and
Arrays 3, exercise 21 on page 138, through exercise 23 on page 138, to be a class-
based program. You might have a roulette class and a gambler class. Recompute an
estimate of the gambler’s ruin length for a gambler whose initial capital is 2,000 dol-
lars. Draw the UML for this program.
21. (Java) Recode point in Section 4.5, Classes, on page 148, as a Java class.
22. (Java) Recode and test ch_stack in Section 4.11, A Container Class Example:
ch_stack, on page 164, as a Java class. Add a method reverse() that does the
same basic operation as the code in main() in Section 4.11, A Container Class Exam-
ple: ch_stack, on page 165 and test it.
23. (Java to C++) Recode the Java program PersonTest.java in Section 4.14, C++ Com-
pared with Java, on page 171, to run as C++.
CHAPTER 5
O bjects are class instances. An object requires memory and an initial value, which
C++ provides through declarations that are definitions. Variables of any type require
memory and an initial value. For example, in
void foo()
{
int n = 5;
double z[10] = { 0.0 };
struct gizmo { int i, j; } w = { 3, 4 };
·····
}
all of the variables are created at block entry when foo() is invoked. A typical imple-
mentation uses a runtime system stack. Thus, the int variable n on a system with 4-
byte integers gets its space allocated off the stack and initialized to the value 5. The
gizmo variable w requires 8 bytes to represent its two-integer members. The array of
double variable z requires 10 times sizeof(double) to store its elements. In each
case, the system provides for the construction and initialization of these variables. On
exit from foo(), deallocation occurs automatically.
In creating complicated aggregates, the user expects similar management of a class-
defined object. The class needs a mechanism to specify object creation and destruction
so that a client can use objects like native types.
A constructor (ctor) is a member function whose name is the same as the class name; it
creates objects of the class type. This process involves initializing data members and,
frequently, allocating storage from the heap by using new. A destructor (dtor) is a mem-
ber function whose name is the class name preceded by the tilde character, ~. A
destructor’s usual purpose is finalizing or destroying objects of the class type. Finaliz-
ing objects involves retrieving resources allocated to the object. Frequently this
requires using delete to deallocate store assigned to the object.
Whereas constructors can be overloaded and take arguments, destructors can do nei-
ther. A constructor is invoked when its associated type is used in a definition, when
call-by-value is used to pass a value to a function, or when the return value of a function
Ira Pohl’s C++ by Dissection 5.1 Classes with Constructors 184
must create a value of associated type. Destructors are invoked implicitly when an
object goes out of scope. Constructors and destructors do not have return types and
cannot use return expression statements.
5.1
5.1 Classes with Constructors
The simplest use of a constructor is for initialization. In this and later sections, we
develop some examples that use constructors to initialize the values of the data mem-
bers of the class. Our first example is an implementation of a data type counter to
store numbers that are computed with a modulus of 100. A car’s trip odometer is a
counter.
In file counter.cpp
// Counter and constructor initialization
class counter {
public:
counter(int i); // ctor declaration
void reset() { value = 0; }
int get() const { return value; }
void print() const { cout << value << '\t'; }
void click() { value = (value + 1) % 100; }
private:
int value; // 0 to 99
};
// Constructor definition
but not
counter() { value = 0; }
is added as a member function of counter, the following declarations are possible:
This initializes counter variable’s value to 0 by default, unless the user provides in an
argument list an explicit initial value.
inline counter::counter(int i = 0) :
value(i % 100) { }
The member variable value is initialized by the expression i % 100. The constructor
definition has a compound statement that is empty. Notice that initialization replaces
assignment. The individual members must be initializable as
In file printable.cpp
// ASCII printable characters
class pr_char {
public:
pr_char(int i = 0) : c(i % 128) { }
void print() const { cout << rep[c]; }
private:
int c;
static const char* rep[128];
};
Ira Pohl’s C++ by Dissection 5.1 Classes with Constructors 188
int main()
{
pr_char c;
for (int i = 0; i < 128; ++i) {
c = i; // or: c = static_cast<pr_char>(i);
c.print();
cout << endl;
}
}
%$#\n\b !!!
In file parabola.cpp
class point {
public:
point() : x(0), y(0) { } // default
point(double u) : x(u), y(0) { } // double to point
point(double u, double v) : x(u), y(v) { }
void print() const { cout << "(" << x << ","
<< y << ")"; }
void set(double u, double v) { x = u; y = v; }
void plus(point c);
private:
double x, y;
};
void point::plus(point c)
{
x += c.x;
y += c.y;
}
This class has three individually coded constructors. They could be combined using
default arguments as follows:
Ira Pohl’s C++ by Dissection 5.1 Classes with Constructors 190
In file parabola.cpp
void graph(double a, double b, double incr,
double f(double, double), double p, point gr[])
{
double x = a;
for (int i = 0; x <= b; ++i, x += incr)
gr[i].set(x, f(x, p));
}
int main()
{
point g[no_of_pts]; // uses default ctor
do not change the stack object, such as top_of() and empty(). It is usual to make
these const member functions. Some of these functions are mutator functions that do
change the ch_stack object, such as push() and pop(). The constructor member func-
tions have the job of creating and initializing ch_stack objects.
In file ch_stack2.h
class ch_stack {
public:
// public interface for ch_stack
explicit ch_stack(int size) :
max_len(size), top(EMPTY)
{ assert(size > 0); s = new char[size];
assert(s != 0); }
void reset() { top = EMPTY; }
void push(char c) { s[++top]= c; }
char pop() { return s[top--]; }
char top_of() const { return s[top]; }
bool empty() const { return (top == EMPTY); }
bool full() const { return (top == max_len-1); }
private:
enum { EMPTY = -1 };
char* s; // changed from s[max_len]
int max_len;
int top;
};
In the preceding code and in the rest of this chapter, we use asser-
tions to test whether a pointer value is 0. This is done after calling
new and indicates that new has failed. The assert technique requires
that a debug option be turned on for the compiler. Also, we are
assuming that memory allocation exception handling is turned off.
An alternative scheme is to have the bad_alloc exception thrown.
This is discussed in detail in Section 10.9, Standard Exceptions and
Their Uses, on page 409. This code has no destructor and leads to
memory leaks. We show an appropriate destructor in Section 5.1.6,
Classes with Destructors, on page 195. It should also have a copy con-
structor and assignment operator overloaded.
■ enum { EMPTY = -1 };
char* s; // changed from s[max_len]
int max_len;
int top;
Here is the ch_stack implementation for a dynamically sized array.
We use a base pointer s rather than a fixed-length array.
Constructors are important because they create possibilities for conveniently initializ-
ing the abstract data type. For example, we can code two additional constructors for
ch_stack. One would be a default constructor to allocate a specific-length ch_stack,
and a second would be a two-parameter constructor whose second parameter would be
a char* to initialize the ch_stack. The two constructors are as follows:
ch_stack::
ch_stack(int size, const char str[]) : max_len(size)
{
int i;
assert(size > 0);
s = new char[size];
assert(s != 0);
for (i = 0; i < max_len && str[i] != 0; ++i)
s[i] = str[i];
top = --i;
}
The corresponding function prototypes would be included as members of the class
ch_stack. We show the use of these constructors:
Ira Pohl’s C++ by Dissection 5.1 Classes with Constructors 193
int cube_plus(int i)
{
i = i + 1;
return i * i * i;
}
when called as in
int j = 2;
cout << cube_plus(j + 2) << endl;
is equivalent to placing a block of code
class_name::class_name(const class_name&);
The compiler copies by memberwise initialization. This is usually correct for simple
classes that have nonpointer data members, such as class point. This is incorrect in
other circumstances, such as for classes with members that are pointers. In many cases,
the pointer is the address of an object. The act of duplicating the pointer value but not
the object pointed at can lead to buggy code. This form of copying is called shallow
copying. Shallow copying is wrong for classes such as ch_stack. In these cases, deleting
the original object may cause the copied object to incorrectly disappear.
The class ch_stack explicitly defines its own copy constructor, as is appropriate.
Ira Pohl’s C++ by Dissection 5.1 Classes with Constructors 194
In file ch_stack2.h
// Copy ctor for ch_stack of characters
In file ch_stack2.cpp
// Count the number of c’s found in s
5.2
5.2 Classes with Destructors
A destructor is a member function whose name is the class name preceded by a tilde, ~.
Destructors are almost always called implicitly, usually at the exit of the block in which
the object was declared. They are also invoked when a delete operator is called on a
pointer to an object having a destructor or where needed to destroy a subobject of an
object being deleted.
Let us augment our ch_stack example with a destructor:
In file ch_stack2.h
// Implementation with ctors and dtor
class ch_stack {
public:
ch_stack(); // default ctor
explicit ch_stack(int size) :
max_len(size), top(EMPTY)
{ assert(size > 0); s = new char[size];
assert(s != 0); }
ch_stack(const stack& stk); // copy ctor
ch_stack(int size, const char str[]);
~ch_stack() { delete []s; } // dtor
// rest of the methods ·····
private:
enum { EMPTY = -1 };
char* s;
int max_len;
int top;
};
The addition of the destructor allows the class to return unneeded heap-allocated mem-
ory during program execution. All of the public member functions perform in exactly
the same manner as before. However, the destructor is implicitly invoked on block and
function exit to clean up storage no longer accessible.
5.3
5.3 Members That Are Class Types
In file address.cpp
class address {
public:
address(string street, string city)
:street_name(street),city_name(city) { }
void print() const;
string get_street() const { return street_name; }
string get_city() const { return city_name; }
private:
string city_name;
string street_name;
};
class person {
public:
person(string n, address h);
void print() const;
void set_address();
private:
address home;
const string name;
};
person::person(string n, address h) :
name(n), home(h) { }
Notice that the person constructor is a series of initializers. The initializers of the
address member invoke the address copy constructor. Also, the methods
get_street() and get_city() could be written to return a const reference as fol-
lows:
5.4
5.4 Example: A Singly Linked List
The singly linked list data type is the prototype of many useful dynamic abstract data
types (ADTs) called self-referential structures. These data types have pointer members
that refer to objects of their own type and are the basis of many useful container
classes. The following declaration implements such a type:
Ira Pohl’s C++ by Dissection 5.4 Example: A Singly Linked List 197
In file slist.cpp
struct slistelem {
char data;
slistelem* next;
};
slist ...
void slist::prepend(char c)
{
slistelem* temp = new slistelem;// create element
assert(temp != 0);
temp -> next = h; // link to slist
temp -> data = c;
h = temp; // update head of slist
}
A list element is allocated from the heap, and its data member is initialized from the
single argument c. Its link member next is set to the old list head. The head pointer h is
updated to point at this element as the new first element of the list.
The member function del() has the inverse role.
void slist::del()
{
slistelem* temp = h;
In file slist.cpp
void slist::print() const // object is unchanged
{
slistelem* temp = h;
void slist::release()
{
while (h != 0)
del();
}
Ira Pohl’s C++ by Dissection 5.4 Example: A Singly Linked List 199
slist::~slist()
{
delete h;
}
The following code demonstrates the use of this type. The destructor has been modified
to print a message.
Ira Pohl’s C++ by Dissection 5.4 Example: A Singly Linked List 200
In file slist.cpp
slist::~slist()
{
cout << "destructor invoked" << endl;
release();
}
int main()
{
slist* p;
{
slist w;
w.prepend('A');
w.prepend('B');
w.print();
w.del();
w.print();
p = &w;
p -> print();
cout << "exiting inner block" << endl;
}
// p -> print(); gives system-dependent behavior
cout << "exiting outer block" << endl;
}
Notice that main() contains an inner block, which is included to test that the destruc-
tor is invoked on block exit, returning storage associated with w to the heap. The output
of this program is
B -> A ->
###
A ->
###
A ->
###
exiting inner block
destructor invoked
exiting outer block
The first print() call prints the two-element slist, which stores A and B. After a del
operation is performed, the list contains one element, which stores A. The outer block
pointer to slist p is assigned the address of the slist variable w. When the list is
accessed through p in the inner block, it prints A. This output shows that the destructor
works at block exit on the variable w.
The behavior of the commented-out invocation of slist::print() is system-depen-
dent. It is a runtime error to dereference p here because the address it refers to may
have been overwritten at block exit by the deletion routine.
Ira Pohl’s C++ by Dissection 5.5 Strings Using Reference Semantics 201
5.5
5.5 Strings Using Reference Semantics
Allocation at runtime of large aggregates can readily exhaust memory resources. The
list example in Section 5.4, Example: A Singly Linked List, on page 198, shows one
scheme for handling this: The system reclaims memory by traversing each list and dis-
posing of each element. This model of reclamation is a form of garbage collection. In
Java, LISP, and SmallTalk, the system itself is responsible for this reclamation. Such sys-
tems periodically invoke a garbage collector to identify all memory locations currently
accessible by the executing program and reclaim those that are inaccessible. Most such
schemes require traversal and marking of memory locations accessible from pointers
with a computationally expensive procedure.
A disposal scheme that avoids this is reference counting, in which each dynamically
allocated object tracks its active references. When an object is created, its reference
count is set to 1. Every time the object is newly referenced, the reference count is incre-
mented; every time it loses a reference, the count is decremented. When the reference
count becomes 0, the object’s memory is disposed of.
The following example creates a my_string class that has reference semantics for
copying. The class uses both the cstring and the assert libraries. This class has shallow
copy semantics because pointer assignment replaces copying. The techniques illus-
trated are common for this type of aggregate. We use the class str_obj to create object
values. The type str_obj is a required implementation detail for my_string. It could
not be directly placed in my_string without destroying the potential many-to-one rela-
tionship between objects of type my_string and referenced values of type str_obj.
The values of my_string are in the class str_obj, which is an auxiliary class for
my_string’s use only. The publicly used class my_string handles the str_obj
instances and is called a handler class.
In file my_string.cpp
// Reference counted my_strings
class str_obj {
public:
int ref_cnt;
char* s;
str_obj() : ref_cnt(1), len(0)
{ s = new char[1];
assert(s != 0); s[0] = 0; }
str_obj(const char* p) : ref_cnt(1)
{ len = strlen(p); s = new char[len + 1];
assert(s != 0); strcpy(s, p); }
~str_obj() { delete []s; }
private:
int len;
};
The str_obj declares objects that are used by my_string. For now, we leave the data
members ref_cnt and s public. They are needed in some of the methods of class
Ira Pohl’s C++ by Dissection 5.5 Strings Using Reference Semantics 202
my_string. We explain later how data members can be made private and accessed
using the friend mechanism. Notice how the str_obj class is used for construction
and destruction of objects using the heap. On construction of a str_obj, the ref_cnt
variable is initialized to 1.
class my_string {
public:
my_string() { st = new str_obj; assert(st != 0); }
my_string(const char* p)
{ st = new str_obj(p); assert(st != 0); }
my_string(const my_string& str)
{ st = str.st; st -> ref_cnt++; }
~my_string();
void assign(const my_string& str);
void print() const { cout << st -> s; }
private:
str_obj* st;
};
my_string::~my_string()
{
if (--st -> ref_cnt == 0)
delete st;
}
5.6
5.6 Constructor Issues and Mysteries
Object creation for native types is usually the task of the compiler. The writer of a class
wishes to achieve the same ease of use for the class. Let us reexamine some issues in
simple terms. Does every class need an explicitly defined constructor? Of course not. If
no constructor is written by the programmer, the compiler provides a default construc-
tor, if needed.
In file tracking.cpp
// Personal data tracking
struct pers_data {
int age; // in years
int weight; // in kilograms
int height; // in centimeters
char name[20]; // last name
};
void print(pers_data d)
{
cout << d.name << " is " << d.age
<< " years old\n";
cout << "weight : " << d.weight
<< "kg, height : " << d.height << "cm."
<< endl;
}
Ira Pohl’s C++ by Dissection 5.6 Constructor Issues and Mysteries 205
int main()
{
pers_data laura = { 3, 14, 88, "POHL" };
// construction off the stack
ch_stack::ch_stack(int size)
{ s = new char[size]; assert(s != 0);
max_len = size; top = EMPTY; }
is better written as
5.7
5.7 Polymorphism Using Function Overloading
the native type int. Mixed-type expressions are also made possible by defining conver-
sion functions.
One principle of OOP is that user-defined types must enjoy the same privileges as
native types. Where the C++ standard library adds the complex number type, the pro-
grammer expects the convenience of using it without regard to a native/nonnative dis-
tinction. Operator overloading and user-defined conversions let us use complex
numbers in much the same way as we can use int or double.
Later, we will discuss two other powerful forms of polymorphism, namely, parametric
polymorphism using templates and pure polymorphism using virtual functions.
5.8
5.8 ADT Conversions
Explicit type conversion of an expression is necessary when either the implicit conver-
sions are not desired or the expression is not otherwise legal. One aim of OOP using
C++ is the integration of user-defined ADTs and built-in types. To achieve this, C++
makes a constructor of one argument a type conversion from the argument’s type to
the constructor’s class type. For example:
point::point(double u);
is automatically a type conversion from double to point, unless it is disabled by
declaring such a conversion constructor with the modifier explicit. The conversion is
available both explicitly and implicitly. Explicitly, it is used as a conversion operation in
either cast or functional form. Thus,
point s;
double d = 3.5;
s = static_cast<point>(d);
and
5.9
5.9 Overloading and Signature Matching
In file rational.cpp
// Overloading functions
class rational {
public:
rational(int n = 0) : a(n), q(1) { }
rational(int i, int j) : a(i), q(j) { }
rational(double r) : a(static_cast<long> (r * BIG)),
q(BIG) { }
void print() const { cout << a << " / " << q; }
operator double()
{ return static_cast<double>(a) / q; }
private:
long a, q;
enum { BIG = 100 };
};
int main()
{
int i = 10, j = 5;
float x = 7.0;
double y = 14.5;
rational w(10), z(3.5), zmax;
cout << "\ngreater(" << i << ", " << j << ") = "
<< greater(i, j);
cout << "\ngreater(" << x << ", " << y << ") = "
<< greater(x, y);
cout << "\ngreater(" << i << ", ";
z.print();
cout << ") = "
<< greater(static_cast<rational>(i), z);
zmax = greater(w, z);
cout << "\ngreater(";
w.print();
cout << ", ";
z.print();
cout << ") = ";
zmax.print();
cout << endl;
}
The output from this program is
Ira Pohl’s C++ by Dissection 5.9 Overloading and Signature Matching 210
greater(10, 5) = 10
greater(7, 14.5) = 14.5
greater(10, 350 / 100) = 10
greater(10 / 1, 350 / 100) = 10 / 1
A variety of conversion rules, both implicit and explicit, are being applied.
5.10
5.10 Friend Functions
The keyword friend is a function specifier that gives a nonmember function access to
the hidden members of the class and provides a method of escaping the data-hiding
restrictions of C++. However, we must have a good reason for escaping these restric-
tions, as they are important to reliable programming.
One reason for using friend functions is that some functions need privileged access to
more than one class. A second reason is that friend functions pass all of their argu-
ments through the argument list, and each argument value is subject to assignment-
compatible conversions. Conversions apply to a class variable passed explicitly and are
especially useful in cases of operator overloading, as seen in the next section.
A friend function must be declared inside the class declaration to which it is a friend.
The function is prefaced by the keyword friend and can appear in any part of the class
without affecting its meaning. The preferred style is to place the friend declaration in
the public part of the class. Since access has no effect on friend declarations, they are
conceptually public. A friend function to one class could be a private member of
another class, and hence not be public. Member functions of one class can be friend
functions of another class. In this case, they are written in the friend’s class, using the
scope resolution operator to qualify its function name. In order to specify that all mem-
ber functions of one class are friend functions of a second class, write
friend class class-name.
The following declarations illustrate the syntax.
void alice()
{
// use some private stuff from tweedledee
·····
cout << "Have some more tea.\n";
}
Ira Pohl’s C++ by Dissection 5.10 Friend Functions 212
class tweedledee {
·····
friend void alice(); // friend function
int cheshire(); // member function
·····
};
class tweedledum {
·····
// friend member function
friend int tweedledee::cheshire();
·····
};
class tweedledumber {
·····
// all member functions of tweedledee have access
friend class tweedledee;
·····
};
The global function alice() has access to all members of tweedledee. The member
function tweedledee::cheshire() is given access to all the members of tweedledum.
All member functions of tweedledee are given access to all the members of tweedle-
dumber.
Tweedledee
and
Tweedledum
by
Sir John
Tenniel
(1820-1914)
Let us revisit our implementation of my_string and make the data variables private.
This is appropriate, as objects should hide their implementation.
Ira Pohl’s C++ by Dissection 5.11 Overloading Operators 213
In file my_string.cpp
class str_obj {
public:
friend class my_string;// my_string access members
str_obj() : len(0), ref_cnt(1)
{ s = new char[1]; assert(s != 0); s[0] = 0; }
str_obj(const char* p) : ref_cnt(1)
{ len = strlen(p); s = new char[len + 1];
assert(s != 0); strcpy(s, p); }
~str_obj() { delete []s; }
private:
int len, ref_cnt;
char* s;
};
The friend declaration gives my_string privileged access to the private members of
str_obj. Its member functions would not otherwise be able to use the variables
ref_cnt and s.
The OOP paradigm is that objects (in C++, class variables) should be accessed through
their public members. Only member functions should have access to the hidden imple-
mentation of the ADT. This is a neat, orderly design principle. The friend function, how-
ever, straddles this boundary. The friend function has access to private members but is
not itself a member function. The friend function can be used to provide quick fixes to
code that needs access to the implementation details of a class. But the mechanism is
easily abused.
5.11
5.11 Overloading Operators
In file rational.cpp
// Overloading operators
class rational {
public:
friend bool operator>(rational w, rational z);
};
5.12
5.12 Unary Operator Overloading
In file my_clock.cpp
class my_clock {
public:
my_clock(unsigned long i = 0);// ctor & conversion
my_clock set(unsigned long i = 0);
void print() const; // formatted printout
void tick(); // add one second
my_clock operator++() { tick(); return *this; }
private:
unsigned long tot_secs, secs, mins, hours, days;
};
Ira Pohl’s C++ by Dissection 5.12 Unary Operator Overloading 215
void my_clock::tick()
{
*this = static_cast<my_clock>(++tot_secs);
}
In file my_clock.cpp
// my_clock and overloaded operators
int main()
{
my_clock t1(59), t2(172799); // t2=2 days-1 sec
The output is
5.13
5.13 Binary Operator Overloading
We continue with our my_clock example and show how to overload binary operators.
The same principles hold: When a binary operator is overloaded using a member func-
tion, it has as its first argument the implicitly passed class variable and as its second
argument the lone argument-list parameter. Friend functions and ordinary functions
have both arguments specified in the parameter list. Of course, ordinary functions can-
not access private members.
Let us create an addition operation for type my_clock that adds two values:
In file my_clock.cpp
class my_clock {
·····
friend my_clock operator+(my_clock c1,
my_clock c2);
};
Ira Pohl’s C++ by Dissection 5.13 Binary Operator Overloading 218
class my_clock {
·····
my_clock operator-(my_clock c);
};
my_clock my_clock::operator-(my_clock c)
{
return (tot_secs - c.tot_secs);
}
Remember that there is an implicit first argument. This takes some getting used to. It is
better to use a friend function for binary minus because of the symmetric treatment of
the arguments.
We define a multiplication operation as a binary operation, with one argument an
unsigned long and the second a my_clock variable. The operation requires the use of
a friend function. It cannot be done with a member function because, as was already
stated, member functions have as their implicit first argument the this pointer.
5.14
5.14 Overloading the Assignment Operator
The assignment operator for a class type is by default generated by the compiler to
have member-by-member assignment. This is fine for many user-defined types such as
rational or point. For types such as my_string and slist, which need deep copying,
this is incorrect. As a rule of thumb, anytime a class needs an explicit copy constructor
defined, it also needs an assignment operator defined. As we have seen with copy con-
structors, this is usually the case when the object allocates its own memory.
We augment my_string with an assignment operator. This is in accord with the OOP
design principle that user-defined types should have the look and feel of native objects.
The class programmer can specify the behavior of assignment by overloading it. It is
good style to be consistent with standard usage. The following member function over-
loads assignment for class my_string:
In file my_string.cpp
my_string& my_string::operator=(const my_string& str)
{
if (str.st != st) {
if (--st -> ref_cnt == 0)
delete st;
st = str.st;
st -> ref_cnt++;
}
return *this;
}
■ st = str.st;
st -> ref_cnt++;
The right-hand side of the assignment str.st is the new left-hand
side string representation.
■ return *this;
The self-referential pointer is dereferenced and passed back as the
value of the expression. This allows multiple assignment with right-
to-left associativity to be defined.
5.15
5.15 Overloading the Subscript Operator
The subscripting operator is usually overloaded where a class type represents an aggre-
gate for which indexing is appropriate. The index operation is expected to return a ref-
erence to an element contained within the aggregate. Overloading assignment and
subscripting share several characteristics. Both must be done as nonstatic member
functions, and both usually involve a reference return type.
An overloaded subscript operator can have any return type and any argument list type.
However, it is good style to maintain the consistency between a user-defined meaning
and standard usage. Thus, the most common function prototype is
Such functions can be used on either side of an assignment. Let us continue with our
my_string example. We overload operator[] to return the reference to the ith charac-
ter in the my_string. If there is no such character, the reference to the null string char-
acter is returned.
In file my_string.cpp
char& my_string::operator[](int position)
{
char* s = st -> s;
for (int i = 0; i != position; ++i) {
if (*s == 0)
break;
s++;
}
return *s;
}
5.16
5.16 Overloading Operator () for Indexing
The function call operator () can be overloaded as a nonstatic member function with
respect to various signatures. It is frequently used to provide an operation requiring
multiple indices. For example, we can code a substring operation for my_string by
overloading as a member function my_string::operator(). It has two arguments so
that my_string(from, to) returns a substring, with from being the beginning of the
substring and to the end.
5.17
5.17 Overloading << and >>
In keeping with the spirit of OOP, it is important to overload << to output user-defined
types. The operator << has two arguments—an ostream& and the ADT—and must pro-
duce an ostream&. Whenever overloading << or >>, you want to use a reference to a
stream and return a reference to a stream, because you do not want to copy a stream
object. Let us write these functions for the type rational:
In file rational.cpp
class rational {
public:
friend ostream&
operator<<(ostream& out, const rational& x);
friend istream& operator>>(istream& in,rational& x);
·····
private:
long a, q;
};
5.18
5.18 Overloading ->
The structure pointer operator -> is overloaded as a nonstatic class member function.
The overloaded structure pointer operator is a unary operator on its left operand. The
argument must be either a class object or a reference of this type. The function should
return a pointer to a class object, an object of a class for which operator -> is defined,
or a reference to a class for which operator -> is defined. The idea is to provide addi-
tional functionality to a pointer type. This type of object is a smart pointer. This tech-
nique is used for implementing the proxy design pattern.
We overload the structure pointer operator inside class t_ptr in the following example.
Objects of type t_ptr act as controlled-access pointers to objects of type triple. The
template class auto_ptr is an example of a smart pointer that is defined in the stan-
dard library.
In file triple.cpp
// Overloading the structure pointer operator
class triple {
public:
triple(int a, int b, int c) : i(a), j(b), k(c) { }
void print() const { cout << "i = " << i << ", j = " << j
<< ", k = "<< k << endl; }
private:
int i, j, k;
};
Ira Pohl’s C++ by Dissection 5.19 Overloading new and delete 224
class t_ptr {
public:
t_ptr(bool f, triple* p) : access(f), ptr(p) { }
triple* operator ->();
private:
bool access;
triple* ptr;
};
triple* t_ptr::operator->()
{
if (access)
return ptr;
else {
cout << "unauthorized access" << endl;
return &unauthor;
}
}
The overloaded operator -> tests the variable t_ptr::access. If it is true, access is
granted. The following code illustrates this:
int main()
{
triple a(1, 2, 3), b(4, 5, 6);
t_ptr ta(false, &a), tb(true, &b);
unauthorized access
i = 0, j = 0, k = 0
i = 4, j = 5, k = 6
5.19
5.19 Overloading new and delete
Most classes involve free-store memory allocation and deallocation. Sometimes, more
sophisticated use of memory than is provided by simple calls to operators new and
delete is needed for efficiency or robustness.
Operator new has the general form
Ira Pohl’s C++ by Dissection 5.19 Overloading new and delete 225
class X {
public:
void* operator new(size_t size)
{ return (malloc(size)); }
void operator delete(void* ptr) { free(ptr); }
X(size_t size);
~X() { delete(p); }
·····
private:
char* p;
};
X::X(size_t size)
{
p = reinterpret_cast<char*>(operator new(size));
assert(p!= 0);
}
In this example, the class X has provided overloaded forms of new() and delete().
When a class overloads operator new(), the global operator is still accessible using
the scope resolution operator ::.
One reason to overload these operators is to give them additional semantics, such as
providing diagnostic information or being more fault-tolerant. Also, the class can have a
more efficient memory-allocation scheme than that provided by the system.
The placement syntax provides a comma-separated argument list used to select an over-
loaded operator new() with a matching signature. These additional arguments are
often used to place the constructed object at a particular address. This form of
operator new uses the new library.
Ira Pohl’s C++ by Dissection 5.19 Overloading new and delete 226
class object {
public:
·····
private:
·····
};
int main()
{
object *p = new(buf1) object; // allocate at buf1
object *q = new(buf2) object; // allocate at buf2
·····
}
This placement syntax allows an arbitrary signature for the overloaded new operator.
This signature—which is distinct from the initializer argument—calls new to select an
appropriate constructor.
The delete operator comes in two flavors. There are two possible signatures:
5.20
5.20 More Signature Matching
Rules for signature matching are given in simplified form in Section 5.9, Overloading
and Signature Matching, on page 208. A further clarification of these rules with exam-
ples is given here.
For a given argument, a best match is always an exact match. An exact match also
includes trivial conversions. These are shown in Table 5.1 for type T.
The use of volatile is specialized. It means that a variable can be modified external to
the program code. So a variable representation of an address that gets data from an
external device, such as a real-time clock, would be volatile. Also, volatile is used
to suppress compiler optimizations that involve such variables.
It is important to remember that user-defined conversions include constructors of a
single argument that can be implicitly called to perform conversions from the argument
type to their class type. This can happen for assignment conversions, as in the argu-
ment-matching algorithm. The following example is modified from the one in Section
5.12, Unary Operator Overloading, on page 214:
In file my_clock.cpp
// Modify my_clock program
class my_clock {
public:
my_clock(unsigned long i);// ctor & conversion
void print() const; // formatted printout
void tick(); // add one second
my_clock operator++() { tick(); return *this; }
void reset(const my_clock& c);
private:
unsigned long tot_secs, secs, mins, hours, days;
};
int main()
{
my_clock c1(900), c2(400);
·····
c1.reset(c2);
c2.reset(100);
·····
}
The call to reset(100) involves an argument match between int and my_clock that is
a user-defined conversion invoking the constructor my_clock(unsigned). Where these
conversions are unintended, explicit can be used in declaring the constructor to dis-
able its use as an implicit conversion.
5.21
5.21 Software Engineering: When to Use Overloading
Explicitly casting arguments can be both an aid to documentation and a useful way to
avoid poorly understood conversion sequences. It is not an admission of ignorance to
cast or to parenthesize arguments or expressions that otherwise could be converted or
evaluated properly.
Operator overloading is easily misused. Do not overload operators when doing so can
lead to misinterpretation. Typically, operator overloading is appropriate when there is a
widely used notation that conforms to your overloading, such as complex arithmetic.
Also, overload related operators in a manner consistent with C++ community expecta-
tions. For example, the relational operators <, >, <=, and >= should all be meaningful
and provide expected inverse behaviors.
Generally speaking, overload symmetric binary operators, such as +, *, ==, !=, and &&,
with friend functions. Both arguments are then passed as ordinary parameters, which
subjects them to the same rules of parameter passing. Recall that using a member func-
tion to provide overloading for symmetric binary operators causes the first argument to
be passed via the this pointer.
Anytime a class uses new to construct objects, it should provide an explicitly over-
loaded operator=(). This advice is analogous to our rule that such a class provide an
explicit copy constructor. The compiler-provided default assignment operator seman-
tics in most cases result in spurious behavior. This leads to a suggested normal form
for classes with heap-managed memory. Normal form means that the class provides
explicit constructors, including the default and copy constructor and the overloaded
assignment operator, as well as an appropriate destructor. Class behaviors should be
consistent with other C++ types.
Ira Pohl’s C++ by Dissection 5.22 Dr. P’s Prescriptions 229
In file my_string.cpp
// Normal form for heap-managed classes
class my_string {
public:
my_string() { st = new str_obj; assert(st != 0); }
my_string(const char* p)
{ st = new str_obj(p); assert(st != 0); }
my_string(const my_string& str)
{ st = str.st; st -> ref_cnt++; }
~my_string();
my_string& operator=(const my_string& str);
// other methods ·····
private:
str_obj* st;
};
5.22
5.22 Dr. P’s Prescriptions
5.23
5.23 C++ Compared with Java
In file Change.java
class Change {
private int dollars, quarters, dimes, pennies;
private double total;
Change(int dlrs, int qtr, int dm, int pen) {
dollars = dlrs;
quarters = qtr;
dimes = dm;
pennies = pen;
total = dlrs + 0.25 * qtr + 0.1 * dm + 0.01 * pen;
}
static Change makeChange(double paid, double owed)
{
double diff = paid - owed;
int dollars, quarters, dimes, pennies;
dollars = (int)diff;
pennies = (int)((diff - dollars) * 100);
quarters = pennies / 25;
pennies -= 25 * quarters;
dimes = pennies / 10;
pennies -= 10 * dimes;
return new Change(dollars, quarters, dimes, pennies);
}
The following simple program, taken from Java by Dissection, by Ira Pohl and Charlie
McDowell (Addison-Wesley, 1999) pages 210 to 212, uses the class Change.
In file ChangeTest.java
public class ChangeTest {
public static void main(String[] args) {
double owed = 12.37;
double paid = 15.0;
System.out.println("You owe $" + owed);
System.out.println("You gave me $" + paid);
System.out.println("Your change is " +
Change.makeChange(paid, owed));
}
}
The output of this program is
p1 = new Person();
// make Unknown 0 M
p1 = new Person("Laura Pohl");
// make Laura Pohl 0 M
p1 = new Person("Laura Pohl", 12, 'F');
// make Laura Pohl 12 F
The overloaded constructor is selected by the set of arguments that match the con-
structor’s parameter list.
Destruction is done automatically by the system, using automatic garbage collection.
This differs from C++, in which the programmer must provide the destructor. When the
object can no longer be referenced—for example, when the existing reference is given a
new object—the now inaccessible object is called garbage. Periodically, the system
sweeps through memory and retrieves these dead objects. The programmer need not be
concerned with such apparent memory leaks.
Unlike C++, Java does not have operator overloading. Java’s use of new is similar to that
in C++ but does not allow for overloading of the new operator. In general, this simplifies
and restricts what the Java programmer can do and needs to worry about. Java allows
ordinary casts but does not allow nonportable casts.
Java performs an automatic conversion only if the conversion does not result in any
information loss. The exception is that some numeric conversions from integer types to
floating-point types can result in loss of precision, but the most significant digits of the
result are unchanged. For example, the following results in an automatic conversion
when n is assigned to f:
int n = 2;
float f;
f = n;
Trying to assign f to n requires a cast.
n = (int)f;
In this case, the floating-point value stored in f is rounded toward zero and the result-
ing value is stored in n. String conversion is used in println():
in which x is a numeric primitive type variable. String conversion occurs when exactly
one operand of the operator + is a string. In this case, the nonstring operand is con-
verted to a String. For the primitive types, the result of string conversion is a value of
type String that represents the primitive value. For example, the result of doing a
string conversion on the int value 123 is the String "123".
Summary
■ A constructor, a member function whose name is the class name, constructs objects
of its class type. This can involve initializing data members and allocating the heap,
using the operator new. A constructor is invoked when its associated type is used in
a definition.
type::type(const type& x)
is used to copy one type value into another when a variable is initialized by a value, a
value is passed as an argument in a function or a value is returned from a function.
If the copy constructor is not present, the compiler provides one that does member-
by-member initialization of values.
■ A class having members whose type requires a constructor uses initializers, a
comma-separated list of constructor calls following a colon. The constructor is
invoked by using the member name followed by an argument list in parentheses.
The initialization is in the order of the declaration of the members.
■ Constructors of a single parameter are automatically conversion functions. They
convert from the parameter type to the class type. my_type::my_type(int); is a
conversion from int to my_type. This property can be disallowed by declaring the
constructor explicit.
■ Overloading operators gives them new meanings. For example, the meaning of the
expression a + b depends on the types of the variables a and b. The expression
could mean string concatenation, complex number addition, or integer addition,
Ira Pohl’s C++ by Dissection Review Questions 236
depending on whether the variables were the ADT my_string, the ADT complex, or
the built-in type int, respectively.
■ The keyword friend is a function specifier that allows a nonmember function
access to the nonpublic members of the class of which it is a friend.
■ It is common to overload >> and << to provide input and output for class types.
■ The structure pointer operator -> , or smart pointer, is overloaded as a nonstatic
class member function. The argument must be a class object or a reference of this
type. The function should return a pointer to a class object, an object of a class or
reference to a class for which operator -> is defined.
■ Overloading functions are selected using the signature matching algorithm.
Review Questions
1. What is the signature in the following declaration: void f(int x, double y);?
5. Explain how cout << x uses operator overloading and why this is important.
10. Some operators can be overloaded only as nonstatic member functions. Name three
such operators.
Ira Pohl’s C++ by Dissection Exercises 237
Exercises
1. Table 5.1 contains a variety of mixed-type expressions. Fill in both the type the
expression is converted to and its value when well defined.
2. To test your understanding, use the slist type to code the following member func-
tions.
3. For the type rational in Section 5.9, Overloading and Signature Matching, on page
209, explain why the conversions of integer 7 and double 7.0 lead to different inter-
nal representations.
4. The following line of code is from the rational.cpp program in Section 5.9, Overload-
ing and Signature Matching, on page 209.
To test your understanding, write a rational constructor that, given two integers
as dividend and quotient, uses a greatest common divisor algorithm to reduce the
internal representation to its smallest a and q value.
5. Overload the equality and comparison operators for rational. Notice that two
rationals are equal in the form given by the previous exercise if and only if their
dividends and quotients are equal. (See Section 5.9, Overloading and Signature
Matching, on page 209.)
class complex {
public:
complex(double r) : real(r), imag(0) { }
void assign(double r, double i)
{ real = r; imag = i; }
void print()
{ cout << real << " + " << imag << "i "; }
operator double()
{ return (sqrt(real * real + imag * imag));}
private:
double real, imag;
};
We wish to augment the class by overloading a variety of operators. For example, the
member function print() could be replaced by creating the friend function opera-
tor<<():
7. For the type complex, write the binary operator functions add, multiply, and sub-
tract. Each should return complex. Write each as a friend function. Why not write
them as member functions?
10. Program a class vec_complex that is a safe array type whose element values are
complex. Overload operators + and * to mean, respectively, element-by-element
complex addition and dot-product of two complex vectors. For added efficiency,
you can make the class vec_complex a friend of class complex.
11. Redo the my_string ADT by using operator overloading. (See Section 5.5, Strings
Using Reference Semantics, on page 201.) The member function assign() should be
changed to become operator=. Also, overload operator[] to return the ith charac-
ter in the my_string. If there is no such character, the value -1 is to be returned.
13. Explain why friendship to str_obj was required when overloading << to act on
objects of type my_string. (See Section 5.5, Strings Using Reference Semantics, on
page 201.) Rewrite my_string by adding a conversion member function operator
char*(). This now allows << to output objects of type my_string. Discuss this
solution.
14. What goes wrong with the following client code when the overloaded definition of
operator=() is omitted from my_string? (See Section 5.5, Strings Using Reference
Semantics, on page 201.)
Ira Pohl’s C++ by Dissection Exercises 240
int main()
{
my_string b("do not try me "), c(" try me");
15. We can further develop our my_string class with a substring operation by overload-
ing the function call operator (). The notation is my_string(from, to), where
from is the beginning of the substring and to is the end. Use this to search a string for a
character sequence and return true if the subsequence is found.
16. Given this code for overloaded [] for my_string from Section 5.1.6, The Copy Con-
structor, on page 194, why would the following be buggy?
17. Rewrite the substring function, using a char* constructor. Is this better or worse? If
you have a profiler, run this example with both forms of substring creation on the
following client code:
Ira Pohl’s C++ by Dissection Exercises 241
int main()
{
my_string large("A verbose phrase to search");
18. To test your understanding, use the preceding substring operation to search a string
for a given character sequence and to return true if the subsequence is found. To
further test your understanding, recode this function to test that the positions are
within the actual string. This means that they cannot have negative values and they
cannot go outside the null character terminator of the string.
19. Code a class int_stack. Use this to write out integer subsequences in increasing
order by value. In the sequence (7, 9, 3, 2, 6, 8, 9, 2), the subsequences are (7, 9), (3),
(2, 6, 8, 9), (2). Use a stack to store increasing values. Pop the stack when a next
sequence value is no longer increasing. Keep in mind that the stack pops values in
reverse order. Redo this exercise using a queue, thus avoiding this reversal problem.
20. Redo the list ADT by using operator overloading. (See Section 5.4, Example: A Singly
Linked List, on page 196.) The member function prepend() should change to oper-
ator+(), and del() should change to operator--(). Also, overload operator[]()
to return the ith element in the list.
21. The postfix operators ++ and -- can be overloaded distinct from their prefix mean-
ings. Postfix can be distinguished by defining the postfix overloaded function as
having a single unused integer argument, as in
class T {
public:
// postfix invoked as t.operator++(0);
T operator++(int);
T operator--(int);
};
There is no implied semantic relationship between the postfix and prefix forms. Add
postfix decrement and increment to class my_clock in Section 5.12, Unary Operator
Overloading, on page 214. Have them subtract a second and add a second, respec-
tively. Write these operators to use an integer argument n that is subtracted or
added as an additional argument.
my_clock c(60);
23. (Project) You should start by writing code to implement a polynomial class with
overloaded operators + and * for polynomial addition and multiplication. You can
base the polynomial on a linked list representation. Then write a full-blown polyno-
mial package that is consistent with community expectations. You could include dif-
ferentiation and integration of polynomials as well.
24. (Project) Write code that fleshes out the rational type of Section 5.17, Overloading
<< and >>, on page 222. Have the code work appropriately for all major operators.
Allow it to properly mix with other number types, including integers, floats, and
complex numbers. There are several ways to improve the rational implementation.
You can try to improve the precision of going from double to rational. Also, many
algorithms are more convenient when the rational is in a canonical form in which
the quotient and divisor are relatively prime. This can be accomplished by adding a
greatest common division algorithm to reduce the representation to the canonical
form. (See exercise 4 on page 237.)
25. (Java) Rewrite in Java the class rational in Section 5.9, Overloading and Signature
Matching, on page 209. You must substitute ordinary methods for any operator
overloading.
CHAPTER 6
In file transferArray.cpp
// Simple array assignment function
In file voidTransferArray.cpp
// void* generic assignment function
C++ has template functions that can be used to create generic code. Template functions
are written using the keyword template followed by angle brackets. The angle brack-
ets contain an identifier that is used as a placeholder for an arbitrary type. Here, we
write the transfer() function using templates.
In file templateTransferArray.cpp
// Template generic assignment function
template<class T>
int transfer(T* from, T* to, int size)
{
for (int i = 0; i < size; i++)
to[i] = from[i];
return size;
}
The template function requires that the type be properly instantiated. It does not allow
two distinct types to be used in this form of array transfer. It continues to provide type-
safety, which is important to program correctness. Templates conveniently solve porta-
bility problems that void* techniques have difficulty with.
Ira Pohl’s C++ by Dissection 6.1 Template Class stack 246
C++ uses the keyword template to provide parametric polymorphism, which allows the
same code to be used with respect to various types, in which the type is a parameter of
the code body. This is a form of generic programming. Many of the classes used in the
text so far contained data of a particular type, although the data have been processed in
the same way regardless of type. Using templates to define classes and functions allows
us to reuse code in a simple, type-safe manner that lets the compiler automate the pro-
cess of type instantiation—that is, when a type replaces a type parameter that appeared
in the template code.
6.1
6.1 Template Class stack
Here, we modify the ch_stack type from Section 4.11, A Container Class Example:
ch_stack, on page 164, to have a parameterized type. This is a prototypical container
class. It is a class whose chief purpose is to hold values. Rather than write a version of
this class for each type, we can write generic code using the template syntax.
In file templateStack.cpp
// Template stack implementation
global or namespace scope, can be a member of a class, and can be declared within
another template class. An example of a stack declaration using this is
Which
form
do you
need,
master?
When a template class is used, the code must always use the angle brackets as part of
the declaration.
In file templateStack.cpp
// Reversing an array of char* represented strings
Member functions, when declared and defined inside the class, are, as usual, inline.
When defining them externally, you must use the full angle bracket declaration. So,
when defined outside the template class,
6.2
6.2 Function Templates
Many functions have the same code body, regardless of type; for example, initializing
the contents of one array from another of the same type uses the same code body. The
essential code is
#define COPY(A, B, N) \
{ int i; for (i=0; i < (N); ++i) (A)[i] = (B)[i]; }
Programming that works regardless of type is a form of generic programming. The use
of define macros is a form of generic programming. Its advantages are several, includ-
ing simplicity, familiarity, and efficiency. There is familiarity because of a long tradition
in C programming of using such macros. It is very efficient. There is no function call
overhead.
The disadvantages of using macros include type-safety, unanticipated evaluations, and
scoping problems. Using define macros can often work, but doing so is not type-safe.
Macro substitution is a preprocessor textual substitution that is not syntactically
checked until later. Another problem with define macros is that they can lead to
repeated evaluation of a single parameter. Definitions of macros are tied to their posi-
tion in a file and not to the C++ language rules for scope. The code
In file copy1.cpp
template<class TYPE>
void copy(TYPE a[], TYPE b[], int n)
{
for (int i = 0; i < n; ++i)
a[i] = b[i];
}
The invocation of copy() with specific arguments causes the compiler to generate the
function based on those arguments. If it cannot, a compile-time error results. What are
the effects of the following calls?
In file copy1.cpp
double f1[50], f2[50];
char c1[25], c2[50];
int i1[75], i2[75];
char* ptr1 = c1, *ptr2 = c2;
The last two invocations of copy() fail to compile because their types cannot be
matched to the template type. This is called a unification error. The types of the argu-
ments do not conform to the template. How the compiler generates this matching is dis-
cussed in the next section. If we were to cast f2 as
In file copy2.cpp
template<class T1, class T2>
void copy(T1 a[], T2 b[], int n)
{
for (int i = 0; i < n; ++i)
a[i] = b[i];
}
This form has an element-by-element conversion. This is usually the appropriate and
safer conversion.
In file swap.cpp
// Generic swap
temp = x;
x = y;
y = temp;
}
A function template is used to construct an appropriate function for any invocation
that matches its arguments unambiguously:
int i, j;
char str1[100], str2[100], ch;
complex c1, c2;
char *s1 = str1, *s2 = str2;
strcpy(temp, s1);
strcpy(s1, s2);
strcpy(s2, temp);
}
Ira Pohl’s C++ by Dissection 6.2 Function Templates 252
This specific version of swap() swaps the two strings represented by pointer values.
With this specialized case added, an exact match of this nontemplate version to the sig-
nature of a swap() invocation takes precedence over the exact match found by a tem-
plate substitution. This is a dangerous swap routine, as the longer string might
overflow the memory that had been allocated for the shorter string. When multiple
functions are available, the overloaded function-selection algorithm given below deter-
mines which to use.
// Macro square
// C++ template
6.3
6.3 Generic Code Development: Quicksort
In file quicksort.cpp
// Quicksort
inline void swap(int& x, int& y)
{
int t;
t = x;
x = y;
y = t;
}
quicksort(a, a + N - 1);
The first argument is a pointer to the first element of the array; the second argument is
a pointer to the last element of the array. In the function definition for quicksort(), it
is convenient to think of these pointers as being on the left and right side of the array,
respectively. The function find_pivot() chooses, if possible, one of the elements of
the array to be a pivot element. The function partition() is used to rearrange the
array so that the first part consists of elements all of whose values are less than the
pivot, and the remaining part consists of elements all of whose values are greater than
or equal to the pivot. In addition, partition() returns a pointer to an element in the
array. Elements to the left of the pointer all have value less than the pivot, and elements
to the right of the pointer, as well as the element pointed to, all have value greater than
or equal to the pivot. Once the array has been rearranged with respect to the pivot,
quicksort() is invoked on each subarray.
Ira Pohl’s C++ by Dissection 6.3 Generic Code Development: Quicksort 255
if (b < c) {
*pivot_ptr = c;
return true;
}
for (p = left + 1; p <= right; ++p)
if (*p != *left) {
*pivot_ptr = (*p < *left) ? *left : *p;
return true;
}
return false; // all elements have same value
}
Ideally, the pivot should be chosen so that at each step the array is partitioned into two
parts, each with an equal (or nearly equal) number of elements. This would minimize
the total amount of work performed by quicksort(). Because we do not know a priori
what this value should be, we try to select for the pivot the middle value from among
the first, middle, and last elements of the array. In order for there to be a partition,
there has to be at least one element that is less than the pivot. If all the elements have
the same value, a pivot does not exist and false is returned by the function.
The major work is done by partition(). We want to explain in detail how this function
works. Suppose we have an array a[] of 12 elements:
7 4 3 5 2 5 8 2 1 9 -6 -3
When find_pivot() is invoked, the first, middle, and last elements of the array are
compared. The middle value is 5, and because this is larger than the smallest of the
three values, this value is chosen for the pivot value. The following simulation shows
the values of the elements of the array after each pass of the outer while loop in the
partition() function. The elements that were swapped in that pass are underlined.
Unordered data: 7 4 3 5 2 5 8 2 1 9 -6 -3
First pass: -3 4 3 5 2 5 8 2 1 9 -6 7
Second pass: -3 4 3 -6 2 5 8 2 1 9 5 7
Third pass: -3 4 3 -6 2 1 8 2 5 9 5 7
Fourth pass: -3 4 3 -6 2 1 2 8 5 9 5 7
Notice that after the fourth pass, the elements with index 0 to 6 have value less than the
pivot and that the remaining elements have value greater than or equal to the pivot. The
address of a[7] is returned from partition() when it finishes the fourth pass and
exits.
In file genericQuicksort.cpp
template <class T>
inline void swap(T& x, T& y)
{
T t;
t = x;
x = y;
y = t;
}
Once the algorithm and its general coding scheme are understood, it is relatively easy to
find the specific type, in this case int, that needs to be parameterized and change it to
a parameterized type T. We show the remaining code parameterized without comment.
Ira Pohl’s C++ by Dissection 6.3 Generic Code Development: Quicksort 258
if (b < c) {
*pivot_ptr = c;
return true;
}
In file genericQuicksort.cpp
int main()
{
cout << "quicksort\n";
int a[12]={7, 4, 3, 5, 2, 5, 8, 2, 1, 9, -6, -3 };
6.4
6.4 Class Templates
In the stack<T> example given in Section 6.1, Template Class stack, on page 246, we
have an ordinary case of class parameterization. In this section, we wish to discuss var-
ious special features of parameterizing classes.
6.4.1 Friends
Template classes can contain friends. A friend function that does not use a template
specification is universally a friend of all instantiations of the template class. A friend
function that incorporates template arguments is specifically a friend of its instantiated
class.
·····
foo<int> a;
foo<double> b;
The static variables foo<int>::count and foo<double>::count are distinct.
Ira Pohl’s C++ by Dissection 6.4 Class Templates 261
In file coerce.cpp
template <class T1, class T2>
bool coerce(T1& x, T2 y)
{
if (sizeof(x) <= sizeof(y))
return false;
x = static_cast<T1>(y);
return true;
}
This template function has two possibly distinct types as template arguments.
Other template arguments include constant expressions, function names, and character
strings.
In file templateArray.cpp
template <class T, int n>
class assign_array {
public:
T a[n];
};
·····
assign_array<double,50> x, y;
·····
x = y; // should work efficiently
The benefits of this parameterization include allocation off the stack, as opposed to
allocation from free store. On many systems, the former is more efficient. The type is
bound to the particular integer constant; thus, operations involving compatible-length
arrays are type-safe and checked at compile time. (See exercise 1 on page 278.)
template<class T = double>
class point{
·····
private:
T x, y, z; // T is commonly double
}
Ira Pohl’s C++ by Dissection 6.5 Parameterizing the Class vector 262
This template can be used with an explicit parameter or with the parameter omitted as
follows:
foo<int>::fooprime<char> a;
There can also be function member templates. Check your local compiler documenta-
tion to see whether these constructs are available.
6.5
6.5 Parameterizing the Class vector
Let us improve on the native C++ array by creating a container class. A defect of the
array as found in C and C++ is that it is easy to have out-of-bounds errors resulting in
difficult-to-find runtime bugs. We parameterize the class, naming it vector in anticipa-
tion of discussing and understanding the Standard Template Library (STL) class
std::vector. The new class is used in conjunction with iterators and algorithms. An
iterator is a pointer or a pointerlike variable used for traversing and accessing container
elements.
Ira Pohl’s C++ by Dissection 6.5 Parameterizing the Class vector 263
In file vect_it.h
// Template-based vector type
This constructor converts an ordinary array to a vector. The copy constructor defines a
deep copy of the vector v.
In file vect_it.cpp
int main()
{
vector<double> v(5);
vector<double>::iterator p;
int i = 0;
do {
--p;
cout << *p << " , ";
} while (p != v.begin());
cout << endl;
}
The output from this program is
The values are in reverse order to how they are stored. This is a consequence of iterat-
ing back from the iterator value v.end(). (See exercise 6 on page 278.)
6.6
6.6 Using STL: string, vector, and complex
The C++ standard library makes heavy use of templates. It is not necessary to be able to
code templates, but it is vital to be able to use template code. This section discusses a
range of useful template types provided by the standard library and by STL. The full use
of STL is such an important and extensive topic that it is the subject of Chapter 7, Stan-
dard Template Library.
In file templateString.cpp
// String class to rewrite a sentence.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string sentence, words[10];
int pos = 0, old_pos = 0, nwords, i = 0;
nwords = i;
sentence = "C++ programmers ";
for (i = 1; i < nwords -1; ++i)
sentence += words[i] + ' ';
sentence += "windows";
cout << sentence << endl;
}
The string type is used to capture each word from an initial sentence in which the
words are separated by the space character. The position of the space characters is
computed by the find() member function. Then the assign() member function is
used to select a substring from sentence. Finally, a new sentence is constructed using
the overloaded operator=(), operator+=(), and operator+() functions to perform
assignments and concatenations.
Note that it is important to check the local system documentation, as different vendors
have employed their own specifications.
Ira Pohl’s C++ by Dissection 6.6 Using STL: string, vector, and complex 267
#include <vector>
using namespace std;
template<class T>
T* find(vector<T> data, T v)
{
int i;
for (i = 0; i < data.size(); i++)
if (data[i] == v);
return &data[i];
return 0; // indicates failure to find v
}
Notice that the code looks typical of an array. The extra feature is that size() is a
method that returns the length of the vector.
class complex{
// ·····methods
private:
double x, y;
};
This is replaced by
In file complex.cpp
#include <iostream>
#include <complex>
using namespace std;
int main()
{
complex<double> x(1,2.1), y;
cout << "x = " << x << endl;
y = x + 1.0;
cout << "\ny = " << y << endl;
// if (x < y) not allowed - no standard definition
// cout << "x less than y" << endl;
}
The complex type is important to scientists and engineers. It shows how easy it is to
extend C++ to new domains. For example, many scientists programmed in FORTRAN90,
which has a complex number type. Thus, C++ readily can be used to replace FORTRAN
programs needing the complex type.
Notice how the commented out lines involve using the less-than operator. It is not
defined in the standard library for this type, so the template compilation fails to instan-
tiate it. If you have your own definition for this operator, you could specifically over-
load it and then this code would work.
In file limits.cpp
#include <iostream>
#include <limits>
using namespace std;
int main()
{
cout << numeric_limits<char>::digits << " char\n ";
cout << numeric_limits<unsigned char>::digits << " u char\n";
cout << numeric_limits<wchar_t>::digits << " wchar_t\n";
cout << numeric_limits<int>::max() << " max int\n";
cout << numeric_limits<double>::max() << " max double " << endl;
}
Ira Pohl’s C++ by Dissection 6.7 Software Engineering: Reuse and Generics 269
The digits field yields the number of bits representing the magnitude of the type.
Two other template libraries that are not discussed here but that are very important for
certain specialized computations are valarray and memory. The valarray library pro-
vides templates for scientific vector computations that can be parallelized. Typically,
this is useful on certain vector-parallel supercomputers. The memory library provides
the auto_ptr<> template that aids management of data dynamically allocated by new
expressions.
6.7
6.7 Software Engineering: Reuse and Generics
Reuse is a key to holding down software costs. Writing and testing new code is always
expensive. Templates are a critical means of providing efficient reusable code. Unlike
the #define macro, templates are type-safe and properly scoped.
As indicated in this chapter, templates are frequently no more expensive to develop
than specialized code. Typically, the programmer writes the specialized case for a given
type. This type should have characteristics representative of the different types the
generic code would be instantiated with. The specialized code should be debugged and
carefully tested until satisfactory. Then the programmer rewrites the specialized code
in template form.
int main()
{
int i = 6;
struct s { int first, second; }x;
coerce(i, x);
cout << i;
}
Here is code that again has a syntax error, but this error is specific to the type instanti-
ation. We are asking a struct variable to be cast to an int variable. Different compilers
note this error with messages that can be difficult to decipher. You should try it on
whatever C++ compilers you are using.
int main()
{
int i = 6;
double x = 5.5;
coerce(i, x);
cout << i;
}
Here is code that works, but the Borland compiler properly indicates that there is
unreachable code. This is because the test
convert<double>(i + j);
Since it was previously illegal, the function instantiation may not work on some sys-
tems. The restriction exists because these compilers must use the arguments at func-
tion invocation to deduce which functions are created. A workaround is possible by
creating a class whose sole member is a parameterized static function, as follows:
int main()
{
convert_it<double> D;
6.8
6.8 Dr. P’s Prescriptions
6.9
6.9 C++ Compared with Java
Unlike C++, Java does not have templates. Java does not have void* or macro code
mechanisms. Instead, each class in Java can be viewed as an extension of the superclass
Object. This is done implicitly. The Object superclass provides for a type of generic
programming and achieves some of the ideas of polymorphism accomplished by the
use of templates in C++. The use of Object in writing generic code is based on inherit-
Ira Pohl’s C++ by Dissection 6.9 C++ Compared with Java 273
ance and is discussed in Java by Dissection (Addison Wesley, 1999), Pohl and McDowell,
pages 244 to 249.
Java does have string types. They are built-in and come in two important classes:
String and StringBuffer. We begin our discussion of the class String with an exam-
ple that uses two operations defined for String: length() and charAt(). A string can
be viewed as a sequence of characters. The method length() is used to find the num-
ber of characters in the string. The method charAt() is used to select individual char-
acters from a string. The first character in the string is at position zero, and the last is
at position length - 1, in which length is the number of characters in the string.
We use the String class to determine whether a string is a palindrome, a string that
reads the same backward or forward. A simple example is the word eye. Here is the Java
code from Java by Dissection (Addison Wesley, 1999), Pohl and McDowell, pages 190-
192, and its output:
In file Palindrome.java
// Check if a string is a palindrome
public class Palindrome {
public static void main(String[] args) {
String str1 = "eye", str2 = "bye";
System.out.println("Palindrome detection");
System.out.println(str1 + " " +
isPalindrome(str1));
System.out.println(str2 + " " +
isPalindrome(str2));
}
static boolean isPalindrome(String s) {
int left = 0;
int right = s.length() - 1;
while (left < right) {
if (s.charAt(left) != s.charAt(right))
return false;
left++;
right--;
}
return true;
}
}
Palindrome detection
eye true
bye false
Ira Pohl’s C++ by Dissection 6.9 C++ Compared with Java 274
The methods length() and charAt() are called instance methods because they operate
on a specified instance of the class String. This corresponds to C++’s nonstatic mem-
ber functions. Note that in the palindrome example, we preceded the method calls
length() and charAt() with a String variable separated by a dot. That’s how we
specify the object upon which the method is to operate.
Ira Pohl’s C++ by Dissection 6.9 C++ Compared with Java 275
We can modify a String variable to refer to different objects, but we can’t change an
actual String object to let it contain different characters. However, the standard Java
class StringBuffer does provide string objects that are modifiable. Like a String
object, a StringBuffer object represents a sequence of characters. In addition, the
class StringBuffer provides mutator methods that can be used to change the
sequence of characters represented by the StringBuffer. For example, the method
reverse() can reverse the character sequence contained in StringBuffer.
Summary
■ C++ has a void pointer type that can be used to create generic code. Generic code is
code that can work with different types.
■ C++ uses templates to provide parametric polymorphism. The same code is used
with different types, in which the type is a parameter of the code body.
■ Both classes and functions can have several class template arguments. In addition to
class template arguments, class template definitions can include constant expres-
sions, function names, and character strings as template arguments. A common case
is to have an int argument that parameterizes a size.
■ A nontemplate, specialized version of a function may be needed when the generic
routine does not work. When multiple functions are available, the overloaded func-
tion-selection algorithm determines which to use.
Review Questions
1. In C, one can use void* to write generic code, such as memcpy(). In C++, writing
generic code uses the keyword .
3. Using templates to define classes and functions allows us to reuse code in a simple,
type-safe manner that lets the compiler automate the process of type —that is,
when a type replaces a type parameter that appeared in the template code.
6. One downside is that for each use of a template function with different types,
is generated.
10. The keyword can be used inside templates instead of the keyword class to
declare template type parameters.
Ira Pohl’s C++ by Dissection Exercises 278
Exercises
1. Rewrite stack<T> in Section 6.1, Template Class stack, on page 246, to accept an
integer value for the default size of the stack. Now client code can use such declara-
tions as
3. The code
4. Write a generic exchange() function with the following definition, and test it:
template<class TYPE>
void exchange(TYPE& a, TYPE& b, TYPE& c)
{
// replace a's value by b's and b's by c's
// and c's by a's
}
5. Write a generic function that, given an arbitrary array and its size, rotates its values
with
6. For the vect_it program in Section 6.5, Parameterizing the Class vector, on page
265, write the member function template to print the entire vector range.
9. Using vector<T> and its associated iterator class, code a generic vector internal
sorting routine of your choice, but not quicksort (see Section 6.5, Parameterizing the
Class vector, on page 263). Compare its running time with the STL sort routine for
vectors of 100; 1,000; and 10,000 elements.
10. (Project) Create a parametric string type. The basic type is to act as a container class
that contains a class T object. In the prototype case, the object is a char. The nor-
mal end-of-string sentinel is 0. The standard behavior should model the functions
found in the string library. The class definition could parameterize the sentinel as
well. Such a type exists in the standard library string.
11. Sorting functions are natural candidates for parameterization. Rewrite the following
generic bubble sort using templates:
T he standard template library (STL) is the C++ library providing generic programming
for many standard data structures and algorithms. The STL provides three types of
components—containers, iterators, and algorithms—that support a standard for
generic programming.
The library is built using templates and is highly orthogonal in design. It is orthogonal
in that components can be used in combination with one another on native and user-
provided types through proper instantiation of the various elements of the STL. The fol-
lowing sections serve only as an overview and brief introduction to the STL, which is
large and complicated. Many newer systems have important further extensions to the
STL.
7.1
7.1 A Simple STL Example
We start with an example of using the container class vector. It was briefly discussed
in Section 6.6.2, vector<> in STL, on page 267. This class is a generalization of the
native array type in C++ and, as such, is easily understood and used. Indeed, one of the
most effective uses of the STL is to replace the use of ordinary C++ arrays with STL vec-
tors. The STL vector type has many important advantages over the array, such as
dynamic expansion, thus avoiding overflow. Further, it can be readily navigated with
both iterators and indices and has a rich interface of built-in operations.
In file stl_vector1.cpp
// Simple STL vector program
#include <iostream>
#include <vector>
using namespace std;
Ira Pohl’s C++ by Dissection 7.1 A Simple STL Example 281
int main ()
{
vector<int> v(100); // 100 is vector's size
#include <iostream>
#include <vector>
using namespace std;
The library vector contains the STL’s template for the component
vector<>.
■ vector<int> v(100); // 100 is vector's size
The STL container vector is used in place of an ordinary int array.
As with any other template, it is instantiated with an existing type.
Here, we use the native type int. The template class has a number of
constructors. The one used here generates an int vector of size
100.
■ for (int i = 0; i < 100; ++i)
v[i] = i;
The first for statement is written in exactly the same manner as a
C++ loop on ordinary data. In most instances, vectors can be used in
place of native arrays without changing working code besides the dec-
larations.
■ for (vector<int>::iterator p = v.begin();
p != v.end(); ++p)
cout << *p << '\t';
The second for statement is written using the iterator p. An iterator
behaves as a pointer. STL provides the member functions begin()
and end() as initial and terminal position values for the container.
Note that end() returns the iterator position (or address), one past
the last element of the container. Thus, end() is a guard location, or a
value signaling that you are finished traversing the container.
Ira Pohl’s C++ by Dissection 7.1 A Simple STL Example 282
The next example uses the list container, an iterator, and the generic algorithm accumu-
late(). The list and numeric libraries are required.
In file stl_container.cpp
#include <iostream>
#include <list> // list container
#include <numeric> // for accumulate
using namespace std;
int main()
{
double w[4] = { 0.9, 0.8, 88, -99.99 };
list<double> z;
7.2
7.2 Containers
Containers come in two major families: sequence and associative. Sequence containers
(vectors, lists, and deques) are ordered by having a sequence of elements. The vector is
the most useful. The deque is a double-ended queue container, conveniently added to at
both front and back. The list makes internal insertion and deletion efficient and conve-
nient. Associative containers (sets, multisets, maps, and multimaps) have keys for look-
ing up elements. The set is a container that stores a value according to an ordering
relationship. A set contains only unique values. The multiset allows multiple copies of
the same item to be stored. The map container is a basic associative array and requires
that a comparison operation on the stored elements be defined. The multimap is a gen-
eralization of a map that allows nonunique keys. So, one key value may be linked to
more than one value.
The two varieties of container share a similar interface.
In file stl_deque.cpp
// A typical container algorithm
Container classes, as shown in Table 7.1, are designated as CAN in the following descrip-
tion of their interface. The identifier CAN was chosen because a can is something that
holds items.
All container classes have these definitions available. For example, in using the vector
container class, vector<char>::value_type means a character value is stored in the
Ira Pohl’s C++ by Dissection 7.2 Containers 285
In file stl_vector_char.cpp
#include <iostream>
#include <typeinfo>
#include <vector>
using namespace std;
int main() {
char c;
vector<char>::value_type v;
if (typeid(c) == typeid(v))
cout << "vector<char>::value_type is just char"
<< endl;
else
cout << "vector<char>::value_type differs "
<< "from char" << endl;
}
Containers have an extensive list of standard member functions, as shown in Table 7.2.
Containers allow equality and comparison operators, as shown in Table 7.3.
In file stl_vector2.cpp
// Sequence Containers - insert a vector into a deque
// Simple STL vector program
#include <iostream>
#include <vector>
#include <deque>
using namespace std;
int main()
{
int data[5] = { 6, 8, 7, 6, 5 };
vector<int> v(5, 6); // 5 element vector
deque<int> d(data, data + 5);
deque<int>::iterator p;
cout << "\nDeque values" << endl;
for (p = d.begin(); p != d.end(); ++p)
cout << *p << '\t'; // print:6 8 7 6 5
cout << endl;
d.insert(d.begin(), v.begin(), v.end());
for (p = d.begin(); p != d.end(); ++p)
cout << *p << '\t';// print:6 6 6 6 6 6 8 7 6 5
cout << endl;
}
Ira Pohl’s C++ by Dissection 7.2 Containers 287
Some sequence container member functions are given in Table 7.4. Sequence classes are
designated as SEQ in the following description of their interface; these are in addition to
the already described CAN interface. End values designated in Table 7.4 below as e_it
are understood as guard values.
Ira Pohl’s C++ by Dissection 7.2 Containers 288
In file stl_age.cpp
// Associative Containers - looking up ages
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
map<string, int, less<string> > name_age;
name_age["Pohl,Laura"] = 12;
name_age["Dolsberry,Betty"] = 39;
name_age["Pohl,Tanya"] = 14;
cout << "Laura is " << name_age["Pohl,Laura"]
<< " years old." << endl;
}
Ira Pohl’s C++ by Dissection 7.2 Containers 289
The associative containers have several standard constructors for initialization. What
distinguishes these constructors from sequence container constructors is the use of a
comparison method. The insertions work when no element of the same key is already
present.
Associative classes are shown as ASSOC in Table 7.5. Keep in mind that these are in
addition to the already described CAN interface.
The associative containers are sets, multisets, maps, and multimaps. They have key-
based accessible elements. These containers have an ordering relation, Compare, which
is the comparison method for the associative container.
As a further associative container example, we use a multiset to count the number of
times each vegetable enters our diet in the course of 100 meals. We use a random num-
ber generator to select which vegetable we will have in a given meal. Besides printing
out the number of times each vegetable is in a meal, we will print out how the multiset
stores this information.
Ira Pohl’s C++ by Dissection 7.2 Containers 291
In file stl_multiset.cpp
// Associative Containers - checking up on your diet
#include <iostream>
#include <set> // used for both set and multiset
#include <vector>
using namespace std;
int main() {
vector<vegetables> my_diet(100);
vector<vegetables>::iterator pos;
vegetables veg;
multiset<vegetables, greater<vegetables> > v_food;
multiset<vegetables, greater<vegetables>>::iterator vpos;
Well, I did bring the heater, but I forgot the plug adapter!
In file stl_stack.cpp
// Adapt a stack from a vector
#include <iostream>
#include <stack>
#include <vector>
#include <string>
using namespace std;
Ira Pohl’s C++ by Dissection 7.2 Containers 294
int main()
{
stack<string, vector<string> > str_stack;
string quote[3] =
{ "The wheel that squeaks the loudest\n",
"Is the one that gets the grease\n",
"Josh Billings\n" };
Josh Billings
Is the one that gets the grease
The wheel that squeaks the loudest
■ while (!str_stack.empty()) {
cout << str_stack.top();
str_stack.pop();
}
The top of the stack is printed. Then the stack element is popped.
This continues until the stack is empty. This results in the adage
printed line-by-line in reverse order. Notice how push(), pop(),
empty() and top() are all standard methods for the stack container.
Special functions exist for adaptor classes, as is shown inTable 7.9 through Table 7.11.
In the minimal description in the table, the use of the equality operator or less-than
operator causes the entire contents of two stacks to be compared for equality or less-
than, respectively. The less-than is lexicographic, meaning the first elements are com-
pared, and that continues in sequence, element pair by element pair, until a less-than is
determined. Check your vendor’s product for specific system-dependent implementa-
tions. In general, it is best to stay with the standard. This avoids locking you into partic-
ular products.
7.3
7.3 Iterators
In file stl_iterator.cpp
// Compare iterator and pointer traversal
#include <iostream>
#include <set>
using namespace std;
int main()
{
int primes[4] ={ 2, 3, 5, 7 }, *ptr = primes;
set<int, greater<int> > s;
set<int, greater<int> > :: const_iterator c_it;
while (ptr != primes + 4)
s.insert(*ptr++);
In file stl_io.cpp
// Use of istream_iterator and ostream_iterator
#include <iterator>
#include <iostream>
#include <vector>
using namespace std;
Ira Pohl’s C++ by Dissection 7.3 Iterators 298
int main()
{
vector<int> d(5);
int i, sum;
The output stream iterator is isomorphic to the input stream iterator. When a value is
assigned to the iterator, it is written to the instantiated output stream, using operator
<<. As seen in the preceding example, the output stream iterator must specify the asso-
ciated output stream as a parameter to the constructor. An optional second parameter
to the constructor is a string that is used as a separator between values.
An ostream_iterator is derived from an output_iterator to work specifically with
writing to streams. The ostream_iterator can be constructed with a char* delimiter,
in this case \t. Thus, the tab character is issued to the stream cout after each int value
is written. In this program, the iterator out, when it is dereferenced, writes the assigned
int value to cout:
In file stl_o_iterator.cpp
// Use of ostream_iterator iterator
#include <iostream>
#include <iterator>
using namespace std;
int main()
{
int d[5] = { 2, 3, 5, 7, 11 }; // primes
ostream_iterator<int> out(cout, "\t");
Simple file manipulations can be coded by using input and output stream iterators and
various algorithms in the standard library. The following example reads a file of inte-
gers, removes all occurrences of the value 0, and copies the remaining values, separat-
ing each value with a comma:
In file stl_io_iterator.cpp
// Use istream_iterator & ostream_iterator iterator
#include <iostream>
#include <iterator>
#include <algorithm>
using namespace std;
void main()
{
istream_iterator<int> input (cin), eof;
ostream_iterator<int> output (cout, ",");
In file stl_adaptor.cpp
// Use of the reverse iterator
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int data[3] = { 9, 10, 11 };
vector<int> d(data, data + 3);
7.4
7.4 Algorithms
In file stl_sort1.cpp
// Using sort() from STL
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 5;
int main()
{
int d[N], i, *e = d + N;
These algorithms have a form that uses a Compare object replacing operator<(), as in
Ira Pohl’s C++ by Dissection 7.4 Algorithms 303
In file stl_sort2.cpp
#include <iostream>
#include <algorithm>
using namespace std;
template<class T>
class MyLess {
public:
bool operator()(const T& obj1, const T& obj2)
{ return obj1 < obj2; }
};
template<class T>
bool MyGreater (const T& obj1, const T& obj2)
{ return obj1 > obj2; }
const int N = 5;
Ira Pohl’s C++ by Dissection 7.4 Algorithms 305
int main()
{
int i, d[N], *e = d + N;
MyLess<int> MyLessObj;
In file stl_find.cpp
// Use of the find function
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
int main()
{
string words[5] = { "my", "hop", "mop", "hope",
"cope"};
string* where;
Ira Pohl’s C++ by Dissection 7.4 Algorithms 306
We present some of the library functions for algorithms in Table 7.18. We briefly list
other algorithms and their purpose as found in this library in Table 7.19.
Ira Pohl’s C++ by Dissection 7.4 Algorithms 307
In file stl_reverse.cpp
// Use of mutating copy and reverse
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
using namespace std;
Ira Pohl’s C++ by Dissection 7.4 Algorithms 308
int main()
{
string first_names[5] = {"laura", "ira",
"buzz", "debra", "twinkle"};
string last_names[5] = {"pohl", "pohl",
"dolsberry", "dolsberry", "star"};
vector<string> names(first_names, first_names+5);
vector<string> names2(10);
vector<string>::iterator p;
In file stl_numeric.cpp
// Vector accumulation and inner product
#include <iostream>
#include <numeric>
using namespace std;
int main()
{
double v1[3] = { 1.0, 2.5, 4.6 },
v2[3] = { 1.0, 2.0, -3.5 };
double sum, inner_p;
The library prototypes for numerical algorithms are shown in Table 7.22.
7.5
7.5 Numerical Integration Made Easy
STL provides the basic computations for many more sophisticated algorithms. By using
STL, programmers can easily implement those algorithms. We use numerical integration
as an example. The idea is to generate a series of points, using a generator. A generator
is a class that defines the function by overloading operator(), the function call opera-
tor. The STL algorithm
In file stl_integration1.cpp
// Simple integration routine for x * x over (0, 1)
#include <iostream>
#include <numeric>
#include <algorithm>
#include <vector>
using namespace std;
int main()
{
const int n = 10000;
■ int main()
{
const int n = 10000;
We can write a more accurate numerical integrator. We approximate the area under the
curve by a sequence of rectangles whose height is the value of the function and whose
width is the increment. An increment gives us two choices for a height, and in the previ-
ous example, we have chosen the right end point to compute it. We could improve the
numerical accuracy of integration by bounding the area between rectangles based on
the smaller heights and one based on the larger heights.
In file stl_integration2.cpp
// Simple integration routine for x * x over (0, 1)
#include <iostream>
#include <numeric>
#include <algorithm>
#include <vector>
using namespace std;
// Integrate on (0,1)
int main()
{
const int n = 10000;
7.6
7.6 STL: Function Objects
It is useful to have function objects to further leverage the STL library. For example,
many of the previous numerical functions had a built-in meaning using + or *, but also
had a form in which user-provided binary operators could be passed in as arguments.
Defined function objects can be found in function or built. Function objects are classes
that have operator() defined. These are inlined and are compiled to produce efficient
object code.
Ira Pohl’s C++ by Dissection 7.6 STL: Function Objects 316
In file stl_function.cpp
// Using a function object minus<int>
#include <iostream>
#include <numeric>
using namespace std;
int main()
{
double v1[3] = { 1.0, 2.5, 4.6 }, sum;
The comparison methods are frequently used with sorting algorithms, such as merge().
Ira Pohl’s C++ by Dissection 7.6 STL: Function Objects 317
Now this will work on arbitrary types that support the various arithmetic operations
found in the definition of operator(). It could be used as the binary operator for
accumulate() as follows:
In file stl_fadaptor.cpp
// Use of the function adaptor bind2nd
#include <iostream>
#include <algorithm>
#include <functional>
#include <string>
using namespace std;
int main()
{
int data[3] = { 9, 10, 11 };
Where the function object multiplies<int> is not available, use times<int>, as this
is an earlier implementation of this object. We use Table 7.26 to briefly list algorithms
and their purpose.
7.7
7.7 Allocators
Allocator objects manage memory for containers. They allow implementations to be tai-
lored to local system conditions while maintaining a portable interface for the container
class. Allocator definitions include value_type, reference, size_type, pointer, and
difference_type. We briefly list allocator member functions as found in this library in
Table 7.27.
7.8
7.8 Software Engineering: STL Use
template<class InIterator>
int count(InIterator b, InIterator e, char comp)
{
int count = 0;
for(; b != e; ++b)
count += (comp == *b);
return count;
}
Iterator ranges and template code is in the style of STL. This version is far more reus-
able. One last generalization is to not tie the algorithm to finding a character comp but
instead to allow it to be whatever the iterator value type is. This will be left as an exer-
cise.
In file stl_vec_is_best.cpp
// Use of vector is better than array
#include <iostream>
#include <vector>
using namespace std;
Ira Pohl’s C++ by Dissection 7.9 Dr. P’s Prescriptions 322
int main()
{
vector<int> d(5); //usually only 5 numbers
int i, sum = 0;
7.9
7.9 Dr. P’s Prescriptions
■ For sequence containers, think vector first, deque second, and list last.
■ Use the most efficient container for a computation.
■ When adapting, remember that the underlying structure determines efficiency.
■ Use iterator parameters rather than container variables.
■ Use the weakest iterator category compatible with the function.
■ Use the most efficient algorithm for a computation.
■ Modify or adapt existing STL algorithms.
■ Understand function composition.
■ Make sure your vendor implements the standard.
The vector is generally the easiest container to use. It is a simple generalization of the
array and as such is most familiar to programmers. It is also often the most efficient
over a large class of operations. It should be your default container choice. The deque is
the next most useful. Its ability to add to both ends of the data structure in linear time
is its greatest strength. It also supports random-access. The list in many ways is the
most expensive container class. Its chief benefit is to give you insertion and deletion of
internal elements in constant time without destroying existing iterator values. Again, be
guided by the most frequent operations required by your problem in making these
choices.
There is relative ease in switching among containers. One container can be constructed
by passing an iterator range from another container. Do not be afraid of using multiple
representations for some problems that dictate a combination of space-operation cost
trade-offs. The point of STL is to use a more efficient algorithm. Usually, this involves
selecting the appropriate container.
Container adaption results in a supported interface, such as a stack or priority queue,
that hides the underlying container implementation. Nevertheless, the different imple-
mentations dictate the efficiency of the resulting data structure. Your choice should be
sensitive to what operations and space constraints are important to your problem.
When in doubt, profile your program.
Ira Pohl’s C++ by Dissection 7.10 C++ Compared with Java 323
Iterator sequences are not tied to a particular type of container. Container types are a
narrower style of representation than iterator ranges. Ergo, using iterator sequences
leaves algorithms more general and hence more reusable.
Our modus operandi in generic programming is to make the program as general as pos-
sible without degrading efficiency. This leads to rule 2; namely, use the weakest iterator
type compatible with an efficient implementation of a computation.
The STL algorithms are expected to be efficient. The generalized sort is an efficient ver-
sion of quicksort and compares favorably in most cases to running qsort() as found in
the C standard library.
As in the preceding example of numerical integration, STL routines can be readily
employed and adapted to perform significant computations without resorting to special
codes. In many cases, a lack of understanding of the mathematical concept of function
composition prevents a programmer from fully mastering the notion and techniques of
adaptation. Many of these concepts are routinely used in functional languages or logic-
based languages such as Lisp, ML, Scheme, and Prolog. It can be useful to look at exam-
ples written in those languages to better understand how these ideas can apply to STL.
Many vendors have variations on the STL standard. There can also be problems with the
vendors’ support of template compilation. These algorithms have been tested on the
latest Sun Microsystem compiler and with Borland 5.1 and found to work. There were
some problems with Microsoft C++ version 6.0. Generally, these can be easily remedied
by looking at your vendor’s documentation.
7.10
7.10 C++ Compared with Java
Unlike C++, Java does not have templates. Instead, each class in Java can be viewed as
an extension of the superclass Object. This is done implicitly. The Object superclass
provides for a type of generic programming and achieves some of the ideas of polymor-
phism accomplished by the use of templates in C++.
There is a Java Generic Library (JGL) that corresponds roughly to STL for C++. The use
of Object in writing generic code is based on inheritance and is discussed in Chapter 8,
Inheritance and OOP.
The package java.util has several useful containers, including LinkedList and Stack.
The Java array type is safer than the C++ native array, so in some sense it more closely
approximates the vector. A Java array is allocated off the heap. It also has a length
member that dynamically tracks the array size. This is described in detail in Java by Dis-
section, by Ira Pohl and Charlie McDowell (Addison Wesley, 1999) page 147.
Ira Pohl’s C++ by Dissection Summary 324
Summary
■ The standard template library (STL) is the C++ library that provides generic pro-
gramming for standard data structures and algorithms.
■ Containers come in two major families: sequence and associative. Sequence contain-
ers (vectors, lists, and deques) are ordered by having a sequence of elements. Asso-
ciative containers (sets, multisets, maps, and multimaps) have keys for looking up
elements.
■ Container adaptor classes modify existing containers to produce different public
behaviors, based on an existing implementation. Three provided container adaptors
are stack, queue, and priority_queue.
■ Iterators can be thought of as an enhanced pointer type. The five iterator types are
input, output, forward, bidirectional, and random-access. Not all iterator types may
be available for a given container class. For example, random-access iterators are
available for vectors but not for maps.
■ The STL algorithm’s library contains the following four categories: sorting algo-
rithms, nonmutating sequence algorithms, mutating sequence algorithms, and
numerical algorithms. These algorithms generally use iterators to access containers
instantiated on a given type. The resulting code can be competitive in efficiency with
special-purpose codes.
■ It is useful to have function objects to further leverage the STL library. Defined func-
tion objects can be found in function or built. Function objects are classes that have
operator() defined. These are inlined and are compiled to produce efficient object
code.
■ When extending STL you should write in an STL style. This means that algorithms
should be coded to use the weakest iterator class that leaves the code efficient. This
also means that code should work on parameters that are iterator ranges.
Ira Pohl’s C++ by Dissection Review Questions 325
Review Questions
3. The member is used as a guard for determining the last position in a container.
Exercises
1. Using a random number generator, generate 10,000 integers between 0 and 9,999.
Place them in a list<int> container. (See Section 7.1, A Simple STL Example, on
page 282.) Compute and print the median value. What did you expect? Compute the
frequencies of each value; in other words, how many 0s were generated, how many
1s were generated, and so forth. Print the value with the greatest frequency. Use a
vector<int> to store the frequencies.
3. Write an algorithm for vector<> v that adds the values stored in the elements
v[2 * i], the even-valued indices of the vector. Test it on ints and doubles.
4. Write a program that inputs a string. It then separates the string into a list of words.
Finally, it should sort the list of words and print out this list. You need to use
list<string> and can use any of STL.
7. Write an algorithm to find the second largest element stored in an arbitrary con-
tainer class. Use STL containers vector<T>, list<T>, and set<T> to test that it
works regardless of the container. Write the algorithm, assuming that a forward iter-
ator is available and comparison is understood.
8. Write and test the template code for the STL library function count_if(b, e, p,
n), where p is a predicate and n is the summing variable.
9. Rewrite the flushing program of Section 4.7, An Example: Flushing, on page 153, to
use STL container classes.
10. Rewrite exercise 9 to use random_shuffle() instead of the special purpose routine
found in the original code. Why is this better methodology?
Ira Pohl’s C++ by Dissection Exercises 327
11. Improve the stl_multiset.cpp program by having the output appear as vegetable was
eaten k times instead of a simple unlabeled integer. One way to do this is to create
an array of vegetable names indexed by the vegetables enumeration value.
13. Use a map to create a table of foods and calories per portion. For example,carrots—
45, ice cream—250, and so on. Place at least 10 foods in your map. Use a random
number generator to pick 4 foods per meal. Print out the meal and its calorie total.
14. Write a comparison object that uses the square of an objects value for comparison.
Therefore, a large negative number is greater than a small positive number using
this comparison object. Generate in a vector the integers -100 to +100 and use an
STL sort with this comparison object. Print out the result.
15. Write an STL algorithm product(b1, e1, b2, c1) that multiplies the elements
starting at b1 by the elements starting at b2 and places the results starting at c1.
The parameter e1 is the guard value for the first sequence.
16. (Project) Design a data type class matrix that uses vectors to hold rectangular
arrays of elements. How should iterators be implemented for such a two-dimen-
sional container? You need to think about basic accessing operations and algorithms
such as matrix addition and multiplication.
CHAPTER 8
I nheritance is the powerful code-reuse mechanism of deriving a new class from an old
one. That is, the existing class can be added to or altered to create the derived class.
Through inheritance, a hierarchy of related types that share code and interfaces can be
created.
Many useful types are variants of one another, and it is frequently tedious to produce
the same code for each. A derived class inherits the description of the base class, which
can then be altered by adding members and modifying existing member functions and
access privileges. The usefulness of inheritance can be seen by examining how taxo-
nomic classification compactly summarizes large bodies of knowledge.
For example, knowing the concept mammal and knowing that an elephant and mouse
are both mammals allows our descriptions of them to be considerably more succinct
than they are otherwise. The root concept contains the information that mammals are
warm-blooded higher vertebrates and that they nourish their young through mammary
glands. This information is inherited by the concept of both mouse and elephant, but it
is expressed only once: in the root concept. In C++ terms, both elephant and mouse are
derived from the base class mammal.
C++ supports virtual member functions: functions declared in the base class and rede-
fined in a derived class. A class hierarchy that is defined by public inheritance creates a
related set of user types, all of whose objects may be pointed at by a base-class pointer.
By accessing the virtual function through this pointer, C++ selects the appropriate func-
tion definition at runtime. The object being pointed at must carry around type informa-
tion so that this distinction can be made dynamically, a feature typical of object-
oriented code. Each object knows how it is to be acted on. This is a form of polymor-
phism called pure polymorphism.
Inheritance should be designed into software to maximize reuse and to allow a natural
modeling of the problem domain. With inheritance, the key elements of the OOP design
methodology are as follows:
8.1
8.1 A Derived Class
In file person.h
class person {
public:
person(const string& nm, int a,
char g) : name(nm), age(a), gender(g) { }
void print() const { cout << *this << endl; }
friend ostream& operator<<(ostream& out, const person& p);
protected:
string name;
int age;
char gender; // male == 'M', female == 'F'
};
We can now create a new class for representing students that is derived from class per-
son. The idea is that a student is a type of person, and this idea is expressed by stu-
dent inheriting person’s code.
In file student.h
enum year { fresh, soph, junior, senior };
const string year_label[]= { "freshman", "sophomore",
"junior", "senior" };
Ira Pohl’s C++ by Dissection 8.1 A Derived Class 331
In this example, student is the derived class and person is the base class. The use of
the keyword public following the colon in the derived-class header means that the pro-
tected and public members of person are to be inherited as protected and public mem-
bers of student. Private members are inaccessible. Public inheritance also means that
the derived class student is a subtype of person. Thus, a student is a person, but a per-
son does not have to be a student. This subtyping relationship is called the ISA relation-
ship, or interface inheritance.
A derived class is a modification of the base class, inheriting the public and protected
members of the base class. Only constructors, destructors, and member function oper-
ator=() cannot be inherited. Thus, in the example of student, the person members
Ira Pohl’s C++ by Dissection 8.1 A Derived Class 333
name, age, gender, and print() are inherited. Frequently, a derived class adds new
members to the existing class members. This is the case with student, which has two
new data members and a redefined member function print(), which is overridden. The
function definitions of person::print() and student::print() are distinct. Imple-
mentation of the member function of the derived class is different from that of the base
class. This is different from overloading, in which the same function name can have dif-
ferent meanings for each unique signature.
person
name
age
gender
print()
student
gpa
y
print()
8.2
8.2 A Student ISA Person
The first thing to understand about C++ inheritance logic is that public inheritance is
used to generate subtypes, so in the first example, a student is a person. This implies
that wherever person is allowed, so is student. We extend our previous example:
In file person.h
class person {
public:
person(const string& nm, int a, char g):
name(nm), age(a), gender(g) { }
void print() const { cout << *this << endl; }
friend ostream& operator<<(ostream& out, const person& p);
int get_age() const { return age; }
protected:
string name;
int age;
char gender; // male == 'M', female == 'F'
};
In file student.cpp
int main()
{
// declare and initialize
person abe(string("Abe Pohl"), 92,'M');
person sam(string("Sam Pohl"), 66, 'M');
student phil(string("Philip Pohl"), 68, 'M', 3.8, junior);
student laura(string("Laura Pohl"), 12, 'F', 3.9, fresh);
cout << abe << endl; // info on abe is printed
cout << phil << endl;
person* ptr_person;
ptr_person = &abe;
ptr_person -> print();
ptr_person = &phil;
ptr_person -> print();
Ape
Person
Student
8.3
8.3 Virtual Functions: Dynamic Determination
In file virtual_sel.cpp
// Virtual function selection
class Base {
public:
virtual void print() const
{ cout << " inside Base" << endl; }
};
int main()
{
Base b;
Derived f;
Base* pb = &b; // points at a Base object
inside Base
inside Derived
Ira Pohl’s C++ by Dissection 8.3 Virtual Functions: Dynamic Determination 339
It is important to notice that this did not happen with the last section example of stu-
dent and person, because there the print() methods were not virtual. It is the normal
Ira Pohl’s C++ by Dissection 8.3 Virtual Functions: Dynamic Determination 340
case in inheritance that overridden methods be declared virtual. This allows them to
select appropriate behavior at runtime. As a simple exercise, redo person with virtual
functions. Should the get_age() method be virtual? Virtual functions require added
work at runtime and are less efficient than nonvirtual methods. C++ programmers use
them only where needed.
In file virtual_err.cpp
#include <iostream>
using namespace std;
class Base {
public:
virtual void foo(int i) { cout << "Base::i = " << i << endl; }
virtual void foo(double x){ cout << "Base::x = " << x << endl; }
};
int main()
{
Derived d;
Derived2 d2;
Base b, *pb = &d;
Only nonstatic member functions can be virtual. The virtual characteristic is inherited.
Thus, the derived-class function is automatically virtual, and the presence of the vir-
tual keyword is usually a matter of taste. Constructors cannot be virtual, but destruc-
tors can be. As a rule of thumb, any class having virtual functions should have a virtual
destructor. Some compilers, such as the latest version of g++, issues a warning if a class
has virtual members and a nonvirtual destructor.
Ira Pohl’s C++ by Dissection 8.3 Virtual Functions: Dynamic Determination 342
In file shape.cpp
class shape {
public:
virtual double area() const { return 0; }
// virtual double area is default behavior
protected:
double x, y;
};
const int N = 3;
int main()
{
shape* p[N];
p[0] = new rectangle(2, 3);
p[1] = new rectangle(2.5, 2.001);
p[2] = new circle(1.5);
double tot_area = 0.0;
for (int i = 0; i < N; ++i)
tot_area += p[i] -> area();
cout << tot_area << " is total area" << endl;
}
Ira Pohl’s C++ by Dissection 8.4 Abstract Base Classes 343
A major advantage here is that the client code does not need to change if new shapes
are added to the system. Change is managed locally and propagated automatically by
the polymorphic character of the client code.
8.4
8.4 Abstract Base Classes
A type hierarchy begins with a base class that contains a number of virtual functions.
They provide for dynamic typing. In the base class, virtual functions are often dummy
functions and have an empty body. In the derived classes, however, virtual functions
are given specific meanings. In C++, the pure virtual function is introduced for this pur-
pose. A pure virtual function is one whose body is normally undefined. Notationally,
such a function is declared inside the class, as follows:
In file predator.cpp
// Predator-Prey simulation using class living
sm[EMPTY] = sm[GRASS] = 0;
sm[RABBIT] = sm[FOX] = 0;
for (i = -1; i <= 1; ++i)
for (j = -1; j <= 1; ++j)
sm[w[row + i][column + j] -> who()]++;
}
sm[EMPTY] = sm[GRASS] = 0;
sm[RABBIT] = sm[FOX] = 0;
for (i = -1; i <= 1; ++i)
for ( j = -1; j <= 1; ++j)
sm[w[row + i][column + j] -> who()]++;
}
This function collects the values of the different life-forms in the
region immediately surrounding the life-form’s position in the world,
namely (row, column). Each life-form has rules that use this sum to
see if they propagate, die off, or stay alive.
living* grass::next(world w)
{
int sum[STATES];
sums(w, sum);
if (sum[GRASS] > sum[RABBIT]) // eat grass
return (new grass(row, column));
else
return (new empty(row, column));
}
Rabbits die of old age if they exceed a defined limit DRAB; they are eaten if there are an
appropriate number of foxes nearby.
living* rabbit::next(world w)
{
int sum[STATES];
sums(w, sum);
if (sum[FOX] >= sum[RABBIT]) // eat rabbits
return (new empty(row, column));
else if (age > DRAB) // rabbit is too old
return (new empty(row, column));
else
return (new rabbit(row, column, age + 1));
}
Ira Pohl’s C++ by Dissection 8.4 Abstract Base Classes 347
living* fox::next(world w)
{
int sum[STATES];
sums(w, sum);
if (sum[FOX] > TMFOX) // too many foxes
return (new empty(row, column));
else if (age > DFOX) // fox is too old
return (new empty(row, column));
else
return (new fox(row, column, age + 1));
}
Empty squares are competed for by the various life-forms.
sums(w, sum);
if (sum[FOX] > 1)
return (new fox(row, column));
else if (sum[RABBIT] > 1)
return (new rabbit(row, column));
else if (sum[GRASS] > 0)
return (new grass(row, column));
else
return (new empty(row, column));
}
The rules in the various versions of next() determine a possibly complex set of interac-
tions. Of course, to make the simulation more interesting, other behaviors, such as sex-
ual reproduction, whereby the animals have gender and can mate, could be simulated.
The array type world is a container for the life-forms. The container has the responsi-
bility of creating its current pattern. The container needs to have ownership of the liv-
ing objects so as to allocate new ones and delete old ones.
void init(world w)
{
int i, j;
This routine creates an empty world. Each square is initialized by the empty::empty()
constructor.
// Clean world up
void dele(world w)
{
int i, j;
void eden(world w)
{
int i, j;
We need a first state of the world. This version of an eden() routine should be replaced
by a routine that allows the user to establish the Garden of Eden pattern.
void pr_state(world w)
{
int i, j;
int main()
{
world odd, even;
int i;
init(odd); init(even);
eden(even); // generate initial world
pr_state(even); // print Garden of Eden state
for (i = 0; i < CYCLES; ++i) { // simulation
if (i % 2) {
update(even, odd);
pr_state(even);
dele(odd);
}
else {
update(odd, even);
pr_state(odd);
dele(even);
}
}
}
Ira Pohl’s C++ by Dissection 8.5 Templates and Inheritance 350
The code runs the simulation for a number of iterations specified by the constant
CYCLES. The reader should experiment with modifications of this code. The structure of
the program lets you easily modify the rules and the initial configuration. More
advanced modifications would improve the user interface and add other life-forms.
8.5
8.5 Templates and Inheritance
Templates and inheritance are jointly an extremely powerful reuse technique. Parame-
terized types can be reused through inheritance. Such use parallels that of inheritance
in deriving ordinary classes. Templates and inheritance are both mechanisms for code
reuse, and both can involve polymorphism. They are distinct features of C++ and, as
such, combine in various forms. A template class can derive from an ordinary class, an
ordinary class can derive from an instantiated template class, and a template class can
derive from a template class. Each of these possibilities leads to different relationships.
In some situations, templates lead to unacceptable cost in the size of the object module.
Each instantiated template class requires its own compiled object module. This can be
remedied by using a template to inherit the base class.
The derivation of a class from an instantiated template class is basically no different
from ordinary inheritance. In the following example, we assume that we already have a
template class stack<class T>. For example, it can be obtained from STL. It is used as
a base class for a safe character stack.
It is important to notice the linkage between the base class and the derived class. Both
require the same instantiated type. Each pair of base and derived classes is independent
of all other pairs.
8.6
8.6 Multiple Inheritance
The examples in the text thus far require only single inheritance; that is, they require
that a class be derived from a single base class. This feature can lead to a chain of deri-
vations wherein class B is derived from class A, class C is derived from class B, . . . , and
class N is derived from class M. In effect, N ends up being based on A, B, . . . , M. This
chain must not be circular, however; a class cannot have itself as an ancestor.
Multiple inheritance allows a derived class to be derived from more than one base class.
The syntax of class headers is extended to allow a list of base classes and their privacy
designations. For example:
class student {
·····
};
class worker {
·····
};
class worker {
public:
const int soc_sec;
const char* name;
·····
};
Ira Pohl’s C++ by Dissection 8.6 Multiple Inheritance 352
class student {
public:
const char* name;
·····
};
person
student worker
student_worker
Without the use of virtual in this example, class student_worker would have
objects of class student::person and class worker::person. The order of execu-
tion for initializing constructors in base and member constructors is given in the fol-
lowing list:
Ira Pohl’s C++ by Dissection 8.7 RTTI and Other Fine Points 353
8.7
8.7 RTTI and Other Fine Points
Runtime type identification (RTTI) provides a mechanism for safely determining the
type pointed at by a base-class pointer at runtime and involves dynamic_cast, an oper-
ator on a base-class pointer; typeid, an operator for determining the type of an object;
and type_info, a structure providing runtime information for the associated type. It is
used with classes having virtual functions. The dynamic_cast operator has the form
Base* bptr;
·····// print typename of what current bptr points at
cout << typeid(*bptr).name() << endl;
·····
if (typeid(*bptr) == typeid(Derived)) {
····· // appropriate for Derived
}
Bad dynamic casts and typeid operations can be made to throw the exceptions
bad_cast and bad_typeid, so the user can choose between dealing with the NULL
pointer or catching an exception. (See Section 10.8, terminate() and unexpected(),
on page 409.)
In file access_mod.cpp
// Access modification
class Base {
public:
int k;
protected:
int j, n;
private:
int i;
};
8.8
8.8 Software Engineering: Inheritance and Design
class Abstract_Base {
public:
// interface - largely virtual
Abstract_Base(); // default ctor
Abstract_Base(const Abstract_Base&); // copy ctor
virtual ~Abstract_Base() = 0; // pure virtual
·····
protected: // used instead of private for inheritance
·····
private: // often empty-no future design constraint
·····
};
Ira Pohl’s C++ by Dissection 8.8 Software Engineering: Inheritance and Design 357
8.9
8.9 Dr. P’s Prescriptions
8.10
8.10 C++ Compared with Java
Like C++, Java has the inheritance mechanism, which extends a new class from an exist-
ing one. Java uses different terminology with respect to inheritance. The Java base class
is called the superclass. The extended class adds to or alters the inherited superclass
methods. This is used to share an interface and to create a hierarchy of related types. In
Java, ordinary class inheritance is single inheritance. Java also has interface inheritance,
which can be used as a restricted form of multiple inheritance.
Consider designing a database for a college. The registrar must track various types of
students. We start with the superclass Person1. This class is identical to Person in Sec-
tion 4.14, C++ Compared with Java, on page 171 except that the private instance vari-
ables are changed to have access protected. This access allows their use in the
subclass but otherwise acts like private.
Ira Pohl’s C++ by Dissection 8.10 C++ Compared with Java 359
In file Person1.java
// An elementary Java implementation of type Person
class Person1 {
public void setName(String nm) { name = nm; }
public void setAge(int a) { age = a; }
public void setGender(char b) { gender = b; }
public String toString() { return(name + " age is "
+ age + " gender is " + gender); }
protected String name;
protected int age;
protected char gender; // male 'M', female 'F'
};
Now we derive Student from Person1.
class Person1 {
protected String name;
·····
public String toString() { return (name
+ " age is " + age + " gender is " + gender); }
·····
};
Summary
■ Inheritance provides the ability to create new derived classes by adding to or alter-
ing existing classes. Through inheritance, a hierarchy of related, code-sharing ADTs
can be created.
■ A class can be derived from an existing class using the form
functions. Facilities that allow the implementation of inheritance and the ability to
process objects dynamically are the essentials of OOP.
■ A pure virtual function is a virtual member function whose body is normally unde-
fined. Notationally, a pure virtual function is declared inside the class, as follows:
Review Questions
10. True or false: Constructors, destructors, overloaded operator=, and friends are not
inherited.
Ira Pohl’s C++ by Dissection Exercises 363
Exercises
1. For student and grad_student code, input member functions that read input for
each data member in their classes. (See Section 8.1, A Derived Class, on page 330.)
Use student::read to implement grad_student::read.
2. Pointer conversions, scope resolution, and explicit casting create a wide selection of
possibilities. Using main(), discussed in Section 8.2, A Student ISA Person, on page
335, which of the following work, and what is printed?
3. Create an abstract class Counter. It should have only pure virtual functions. It
should have a method click() that would advance the counter. It should have
methods get() and set() for accessing and mutating the counter’s value.
4. Create a concrete class Timer derived from the abstract class Counter. This class
should simulate a timer that has seconds and minutes as readout. Write a program
that tests this implementation.
5. Develop a class Clock based on Timer. It should have the same functionality as an
ordinary house clock or watch. Write a program that tests this implementation.
6. Write a class stack that stores values that are void*. This is a form of generic
stack. Implement operations that are available for the STL stack. Now derive pri-
vately a form of this stack that stores ints. Write some code testing this. One such
standard test would be to use the stack for reversing a set of values. Compare this
approach to one using STL. Why should STL be preferred? Are there any advantages
to the use of void* and inheritance instead of templates?
7. Derive an integer vector class from the STL class vector<int> that has 1 as its
first index value and n as its last index value.
8. Generalize the previous exercise by deriving a template class that creates the index
range 1 to n.
9. For the following program, explain when both overriding and overloading take place.
Ira Pohl’s C++ by Dissection Exercises 364
class B {
public:
B(int j = 0) : i(j) {}
virtual void print() const
{ cout << " i = " << i << endl; }
void print(char *s) const
{ cout << s << i << endl; }
private:
int i;
};
class D : public B {
public:
D(int j = 0) : B(5), i(j) {}
void print() const
{ cout << " i = " << i << endl; }
int print(char *s) const
{ cout << s << i << endl; return i; }
private:
int i;
};
int main()
{
B b1, b2(10), *pb;
D d1, d2(10), *pd = &d2;
class D2 : private B {
public:
B::i; // access modification
void print_i()
{
cout << i << " inside D2 and B::i is "
<< B::i << endl;
}
};
What is changed in the output from that program?
Ira Pohl’s C++ by Dissection Exercises 365
11. Define a base class person that contains universal information, including name,
address, birth date, and gender. Derive from this class the following classes:
12. Add a new life-form to the predator-prey simulation found in Section 8.4, Abstract
Base Classes, on page 343.
13. (Project) Design and implement a graphical user interface (GUI) for the predator-prey
simulation of Section 8.4, Abstract Base Classes, on page 343. It is beyond the scope
of this book to describe various available GUI toolkits. The program should draw
each iteration of the simulation on the screen. You should be able to directly input a
Garden of Eden starting position. (See Section 8.4, Abstract Base Classes, on page
348, for the game-of-life simulation.) You should also be able to provide other set-
tings for the simulation, such as the size of the simulation. Can you allow the user to
define other life-forms and their rules for existing, eating, and reproducing? Make
the graphical interface as elegant as possible. The user should be able to position it
on the screen, resize it, and select icons for the various available life-forms.
14. (Java) Add GraduateStudent to the Java class hierarchy in Section 8.10, C++ Com-
pared with Java, on page 360. Note how Java uses capitalization instead of an under-
score to separate words in an identifier. This is stylistic. C++ derives its heritage
directly from C and adopted C style. Java has a SmallTalk influence and has styles
adopted from that culture.
15. (Java) Develop the Java version of the shape hierarchy in Section 8.3.2, A Canonical
Example: Class shape, on page 342.
16. (Java) Develop the predator-prey simulation in Java, using the SWING library to pro-
vide a graphical interface. (See Section 8.4, Abstract Base Classes, on page 343, for
the predator-prey C++ simulation.) This is one area that Java excels in.
CHAPTER 9
Input/Output
T his chapter describes input/output in C++, using iostream and its associated librar-
ies. The standard input/output library for C, described by the header cstdio, is still
available in C++. However, C++ introduces iostream, which implements its own collec-
tion of input/output functions.
Stream I/O is described as a set of classes in iostream. These classes overload the put to
and get from operators << and >>. Streams can be associated with files, and examples
of file processing using streams are discussed in this chapter. A lot of file processing
requires character-handling macros, which are found in ctype. These are also discussed
here.
In OOP, objects should know how to print themselves, and in this text we have fre-
quently made print() a member function of a class. Notationally, it is also useful to
overload << for user-defined ADTs. In this section, we develop output functions for the
types card and deck to illustrate these techniques.
9.1
9.1 The Output Class ostream
Output is inserted into an object of type ostream, declared in the header file iostream.
An operator << is overloaded in this class to perform output for the standard types.
The overloaded left-shift operator is called the insertion, or put to, operator. The opera-
tor is left-associative and returns a value of type ostream&. The standard output
ostream corresponding to stdout is cout, and the standard output ostream corre-
sponding to stderr is cerr.
The effect of executing a simple output statement, such as
cout.put('A'); // output A
9.2
9.2 Formatted Output and iomanip
The put to operator << produces by default the minimum number of characters needed
to represent the output. As a consequence, output can be confusing, as seen in the fol-
lowing example:
int i = 8, j = 9;
x = 1;
cout << "x = " << x << endl;
This immediately prints the line
x = 1
Another manipulator, flush, flushes the ostream, as in
Ira Pohl’s C++ by Dissection 9.2 Formatted Output and iomanip 368
In file manip.cpp
// Using different bases in integer I/O
int main()
{
int i = 10, j = 16, k = 24;
cout << i << '\t' << j << '\t' << k << endl;
cout << oct << i << '\t' << j << '\t' << k << endl;
cout << hex << i << '\t' << j << '\t' << k << endl;
cout << "Enter 3 integers, e.g. 11 11 12a" << endl;
10 16 24
12 20 30
a 10 18
Enter 3 integers, e.g. 11 11 12a
11 17 298
■ cout << oct << i << '\t' << j << '\t' << k << endl;
The manipulator oct changes to an octal representation, so the
value of i, which is decimal 10, is printed as the octal 12.
Ira Pohl’s C++ by Dissection 9.2 Formatted Output and iomanip 369
■ cout << hex << i << '\t' << j << '\t' << k << endl;
The manipulator hex changes to a hexadecimal representation, so
the value of i, which is decimal 10, is printed as the hexadecimal a.
■ cout << "Enter 3 integers, e.g. 11 11 12a" << endl;
cin >> i >> hex >> j >> k;
cout << dec << i << '\t' << j << '\t' << k << endl;
Input streams can also set the base using manipulators. Thus, for cin
the variable of i is read in as decimal, but the manipulator hex
changes the base for the next values. The base manipulator is persis-
tent. If we had not reset cout to dec, the values would have printed
as hexadecimal because the last setting for cout was hexadecimal.
Thus, the value of j input as hexadecimal 11 prints as decimal 17.
The value of k input as hexadecimal 12a prints as decimal 298.
The preceding manipulators are found in iostream. Other manipulators are found in
iomanip. For example, setw(int width) is a manipulator that changes the default field
width for the next formatted I/O operation to the value of its argument. This value
reverts to the default. Table 9.1 briefly lists the standard manipulators, the function of
each, and the location where each is defined.
A further example demonstrates the use of setw, setfill, and setprecision manipu-
lators.
In file format.cpp
// Display use of formatting manipulators
#include <iostream>
#include <iomanip>
using namespace std;
// pi to 21 places
const long double pi = 3.14159265358979323846L;
int main()
{
long double r;
Enter radius:
Area is 3.14159
Area is 3.141592654
Area is 3.141592654
Area is 3.141592653589793238
*******************1
Area is 3.14159
*******************1
Ira Pohl’s C++ by Dissection 9.3 User-Defined Types: Output 372
9.3
9.3 User-Defined Types: Output
User-defined types have typically been printed by creating a member function print().
Let us use the types card and deck as an example of a simple user-defined type. We
write out a set of output routines for displaying cards:
In file print_deck.cpp
// Card output
class pips {
public:
void assign(int n) { p = n % 13 + 1; }
void print() { cout << pips_symbol[p]; }
private:
int p;
};
Ira Pohl’s C++ by Dissection 9.3 User-Defined Types: Output 373
class card {
public:
suit s;
pips p;
void assign(int n)
{ cd = n; s = suit(n / 13); p.assign(n); }
void pr_card()
{ p.print(); cout << suit_symbol[s] << " "; }
suit get_suit() { return s; }
pips get_pips() { return p; }
private:
int cd; // a cd is from 0 to 51
};
class deck {
public:
void init_deck();
void shuffle();
void deal(int, int, card*);
void pr_deck();
private:
card d[52];
};
void deck::pr_deck()
{
for (int i = 0; i < 52; ++i) {
if (i % 13 == 0) // 13 cards to a line
cout << endl;
d[i].pr_card();
}
cout << endl;
}
Each card is printed out in two characters. If d is a variable of type deck, then
d.pr_deck() prints out the entire deck, 13 cards to a line.
In keeping with the spirit of OOP, it would also be nice to overload << to accomplish the
same aim. The operator << has two arguments—an ostream& and the ADT—and it must
produce an ostream&. You want to use a reference to a stream and to return a reference
to a stream, whenever overloading << or >>, because you do not want to copy a stream
object. Let us write these functions for the types card and deck:
In file print_deck.cpp
ostream& operator<<(ostream& out, pips& x)
{
return (out << pips_symbol[x.p]);
}
Ira Pohl’s C++ by Dissection 9.4 The Input Class istream 374
9.4
9.4 The Input Class istream
An operator >> is overloaded in istream to perform input for the standard types. The
overloaded right-shift operator is called the extraction, or get from, operator. The stan-
dard input istream corresponding to stdin is cin.
The effect of executing a simple input statement, such as
9.5
9.5 Files
C systems have stdin, stdout, and stderr as standard files. In addition, systems may
define other standard files, such as stdprn and stdaux. Abstractly, a file may be
thought of as a stream of characters that are processed sequentially. The standard C++
files are shown in Table 9.2.
The C++ stream input/output ties the first three of these standard files to cin, cout,
and cerr, respectively. Typically, C++ ties cprn and caux to their corresponding stan-
dard files, stdprn and stdaux. There is also clog, which is a buffered version of cerr.
Other files can be opened or created by the programmer. We show how to do this in the
context of writing a program that double-spaces an existing file into an existing or new
file. The file names are specified on the command line and passed into argv.
File I/O is handled by including fstream, which contains the classes ofstream and
ifstream for output and input file-stream creation and manipulation. To properly open
and manage an ifstream or ofstream related to a system file, you must first declare it
with an appropriate constructor.
ifstream();
ifstream(const char*, int = ios::in, int prot = filebuf::openprot);
ofstream();
ofstream(const char*, int = ios::out, int prot = filebuf::openprot);
The constructor of no arguments creates a variable that is later associated with an input
file. The constructor of three arguments takes as its first argument the named file. The
second argument specifies the file mode. The third argument is for file protection. The
arguments for file mode are defined as enumerators in class ios, as shown Table 9.3.
Thus, the default for an ifstream is input mode, and the default for an ofstream is
output mode. If file opening fails, the stream is put into a bad state. The mode can be
tested with the !operator. In libraries built with exceptions, the failure exception can
be thrown.
Other important member functions found in fstream include
void close();
Ira Pohl’s C++ by Dissection 9.5 Files 377
These functions can be used to open and close appropriate files. If you create a file
stream with the default constructor, you would normally use open() to associate it
with a file. You could then use close() to close the file and to open another file, using
the same stream. Typically, a file stream will be closed automatically when it goes out of
scope. Additional member functions in other I/O classes allow for a full range of file
manipulation. The following program uses both the fstream and the cstdlib libraries:
In file double_space.cpp
// A program to double space a file.
// Usage: executable f1 f2
// f1 must be present and readable
// f2 must be writable if it exists
#include <fstream>
#include <cstdlib>
using namespace std;
while (f.get(c)) {
t.put(c);
if (c == '\n')
t.put(c);
}
}
ifstream f_in(argv[1]);
ofstream f_out(argv[2]);
if (!f_in) {
cerr << "cannot open " << argv[1] << endl;
exit(1);
}
if (!f_out) {
cerr << "cannot open " << argv[2] << endl;
exit(1);
}
double_space(f_in, f_out);
}
Ira Pohl’s C++ by Dissection 9.5 Files 378
while (f.get(c)) {
Much of file processing is handled one character at a time. The
expression f.get(c) returns as 0 when the stream can no longer be
read. Otherwise, it returns with a nonzero value and reads into c the
character value, including white space characters.
■ t.put(c);
if (c == '\n')
t.put(c);
The loop places each character into the output file. It tests each char-
acter for being a newline. Where a newline is found, it outputs a sec-
ond newline, thus double spacing the file.
■ int main(int argc, char** argv)
{
if (argc != 3) {
cout << "\nUsage: " << argv[0]
<< " infile outfile" << endl;
exit(1);
}
This is idiomatic for generating an executable that utilizes command
line arguments. The resulting code would be something like
double_space my_input my_output
Here, the expectation is that there are three strings on the command
line. The name of the executable, followed by the input and output
file names. This correct usage is tested by main().
■ ifstream f_in(argv[1]);
ofstream f_out(argv[2]);
The declarations of the two streams cause constructor invocation to
properly open these files.
Ira Pohl’s C++ by Dissection 9.6 Using Strings as Streams 379
■ if (!f_in) {
cerr << "cannot open " << argv[1] << endl;
exit(1);
}
if (!f_out) {
cerr << "cannot open " << argv[2] << endl;
exit(1);
}
double_space(f_in, f_out);
We test that the constructors properly opened the two files. If there is
no error exit, the double_space() function is invoked.
9.6
9.6 Using Strings as Streams
In file str_stream.cpp
#include <iostream>
#include <sstream> // replaces strstream
#include <cstdlib>
using namespace std;
int main()
{
string name;
int total;
string scores[5] = { "Vicki 2", "Nicole 5", "Boston 8",
"Chris 7", "Don 3" };
istringstream ist(scores[4]);// ist uses scores[4]
ist >> name >> total; // name: Don, total: 3
cout << "\nname: " << name << " total: "
<< total << endl; }
Ira Pohl’s C++ by Dissection 9.7 The Functions and Macros in ctype 380
Here, we have a series of five strings stored in an array. We take scores[4] as the ini-
tializer for ist. Then the overloaded extraction operator >> can be used to assign
"Don" to name and 3 to total.
The ostringstream declarations have the following forms:
ostringstream name();
ostringstream name(char* s, int n,
int mode = ios::out);
where s is pointer to buf to receive string, n is the optional size of buffer, and mode
specifies whether the data are to be put into an empty buffer (ios::out) or appended
to the existing null-terminated string in the buffer (ios::app or ios::ate). If no size is
specified, the buffer is dynamically allocated. The ostringstream variable may use the
overloaded put to operator << to build the string. The use of ostringstream is particu-
larly useful when you want to construct a single string from information kept in a vari-
ety of variables. In the following example, note that ost2 must contain an existing null-
terminated string in order for the append to work correctly:
ostringstream ost1;
ostringstream ost2 (charbuf, 1000, ios::app);
ost1 << name << " " << score << endl;
ost2 << address << city << endl << ends;
9.7
9.7 The Functions and Macros in ctype
The system provides a standard header file, ctype.h, or ctype, which contains a set of
functions used to test characters and a set of functions used to convert characters, as
shown in Table 9.4. These functions may be implemented as macros or as inline func-
tions. This is mentioned here because of its usefulness in C++ input/output. Those
functions that only test a character return an int value. The argument is type int.
Other functions, as shown in Table 9.5, provide for the appropriate conversion of a
character value. Note that these functions do not change the value of c stored in mem-
ory.
The ASCII code functions are usual on ASCII systems.
9.8
9.8 Using Stream States
Each stream has an associated state that can be tested. The states on existing systems
are
Testing for a stream’s being in a nongood state can protect a program from hanging up.
A stream state of good means that the previous input/output operation worked and
that the next operation should also. A stream state of EOF means that the previous
input operation returned an end-of-file condition. A stream state of fail means that
the previous input/output operation failed but that the stream is usable once the error
bit is cleared. A stream state of bad means that the previous input/output operation is
invalid but that the stream may be usable once the error condition is corrected.
It is also possible to directly test a stream. It is nonzero if it is in either a good or an
EOF state.
In file word_count.cpp
// The word_count program for counting words
// Usage: executable < file
int found_next_word();
int main()
{
int word_cnt = 0;
while (found_next_word())
++word_cnt;
cout << "word count is " << word_cnt << endl;
}
int found_next_word()
{
char c;
int word_sz = 0;
cin >> c;
while (!cin.eof() && !isspace(c)) {
++word_sz;
cin.get(c);
}
return word_sz;
}
A non-white space character is received from the input stream and is assigned to c. The
while loop calls the isspace() function in the ctype library to test that adjacent char-
acters are not white space. The loop terminates when either an end-of-file character or a
Ira Pohl’s C++ by Dissection 9.9 Mixing I/O Libraries 383
white space character is found. The word size is returned as 0 when the only non-white
space character found is the end-of-file. One last point: The loop cannot be rewritten as
9.9
9.9 Mixing I/O Libraries
Throughout this text, iostream has been used. It is perfectly reasonable to want to con-
tinue using stdio. This is the standard in the C community, and it is well understood. Its
disadvantage is that it is not type-safe.
Functions such as printf() use unchecked variable-length argument lists. Stream I/O
requires, as arguments to its functions and overloaded operators, assignment-compati-
ble types. You might also want to mix both forms of I/O. Synchronization problems can
occur because the two libraries use different buffering strategies. This can be avoided
by calling
ios::sync_with_stdio();
The following program coordinates the two libraries:
Ira Pohl’s C++ by Dissection 9.10 Software Engineering: I/O 384
In file mix_io.cpp
// The mix_io program with synchronized I/O
int main()
{
int n;
ios::sync_with_stdio();
do {
cout << "\nEnter n positive or 0 to halt: ";
scanf("%d", &n);
printf("\n fact(%d) = %ld", n, fact(n));
} while (n > 0);
cout << "\nend of session" << endl;
}
Note that for integer values greater than 12, the results overflow. It is safe to mix stdio
and iostream, provided they are not mixed on the same file.
9.10
9.10 Software Engineering: I/O
STL containers and iterators are a natural pattern to use when writing code for input/
output streams. Both model the sequence abstraction. STL provides special input and
output iterators for handling I/O. These again demonstrate how sequences are a very
powerful software design tool. Let us write a routine that reads a file into a vector,
outputs its contents, and sums the vector:
In file io_iterators.cpp
// Use of istream_iterator and ostream_iterator
#include <iterator>
#include <iostream>
#include <fstream>
#include <vector>
#include <numeric>
using namespace std;
Ira Pohl’s C++ by Dissection 9.10 Software Engineering: I/O 385
int main()
{
int sum;
istream_iterator<int> in(*new ifstream("data"));
istream_iterator<int> eos;
ostream_iterator<int> out(cout, "\t");
vector<int> v(in, eos);
istream_iterator<int, ptrdiff_t>
in(*new ifstream("data"));
■ istream_iterator<int> eos;
This establishes the end-of-stream iterator. This allows us to use in
as the beginning of the stream and eos as the end-of-stream guard.
Note that some older compilers require the use of ptrdiff_t param-
eter as follows:
9.11
9.11 Dr. P’s Prescriptions
9.12
9.12 C++ Compared with Java
Java has type-safe I/O but does not have operator overloading. In Java, most output to
the terminal is done using println(), as we discussed in Section 2.11, C++ Compared
with Java, on page 67. Java also has the GUI library Swing, which is discussed exten-
sively in Java by Dissection by Ira Pohl and Charile McDowell (Addison-Wesley 1999)
Chapters 7 and 8. In this section, we present an example of Java writing to a file. This is
taken from Java by Dissection, Section 10.2, pages 347-348.
The simplest way to write text to a file requires the use of two different classes—
PrintWriter and FileWriter—both from the standard package java.io. The class
PrintWriter has the familiar methods print() and println() that we’ve been using
to write to the console. To create a PrintWriter object that is associated with a partic-
ular file, we must first create a FileWriter object for that file. This object is then
passed to the constructor for the PrintWriter, as shown in the following example:
Ira Pohl’s C++ by Dissection 9.12 C++ Compared with Java 387
In file HelloFile.java
//Writing to a Java file
import java.io.*;
class HelloFile {
public static void main(String[] args)
throws java.io.IOException
{
PrintWriter out =
new PrintWriter(new FileWriter("hello.txt"));
out.println("Hello, file system!");
out.close();
}
}
If you run this program, it creates a file, hello.txt, which you can view with any text edi-
tor. The contents of the file are the one line:
Hello, file system!
Why do we need the two classes PrintWriter and FileWriter? The reason is that the
Java I/O package is designed to support many different types of input/output process-
ing. Think of the classes in the package as building blocks. By assembling the correct
set of building blocks, you can meet many different I/O processing needs. You can use
the class FileWriter to write a stream of text characters into a file, but the methods in
FileWriter are fairly primitive and support only the writing of text from String,
char, and char[] values. The class PrintWriter from the same package can generate
a stream of text characters from any value. The primary methods in class PrintWriter
are the familiar print() and println() used for writing to the console. By passing a
FileWriter object to the constructor of a PrintWriter, you are logically creating a
sequence or pipeline of processing steps.
You can use the class PrintWriter to create text streams that go somewhere other
than to a file. For example, you can also use a PrintWriter to write over a network or
to write to a character array. The output of the PrintWriter is sent to the stream spec-
ified in the constructor. In this case, it is a FileWriter.
Ira Pohl’s C++ by Dissection Summary 389
Summary
■ Output is inserted into an object of type ostream, declared in the header file
iostream. An operator << is overloaded in this class to perform output for the stan-
dard types. The overloaded left_shift operator is called the insertion, or put to, oper-
ator. The standard output ostream is cout.
■ The operator >> is overloaded in istream to perform input for standard types. The
overloaded right-shift operator is called the extraction, or get from, operator. The
standard input istream is cin.
■ A manipulator is a value or a function that has a special effect on the stream on
which it operates. Common ones include endl and setw().
■ Code for overloading operator<< often looks like
ios::sync_with_stdio();
Ira Pohl’s C++ by Dissection Review Questions 390
Review Questions
6. The class allows strings to be treated as iostreams, and the library must
be included.
7. In OOP, objects should know how to print themselves, and it is best to do this by
for user-defined ADTs.
8. Synchronization problems can occur when using stdio and iostream in the same
program because the two libraries use different buffering strategies, which can be
avoided by calling .
9. Fill in the C++ stream names and their default physical connection devices in Table
9.1.
10. File I/O is handled by including , which contains the classes and for
output and input file-stream creation and manipulation.
Ira Pohl’s C++ by Dissection Exercises 391
Exercises
1. Write an array of strings to a file named strings.txt. Initialize the array with the four
strings "I am", "a text", "file written", and "to strings.txt".
2. Create an array of strings that receive their input from the file save.txt. Specify the
number of strings by asking the user to enter the number of lines to be read. Echo
the strings read to cout.
3. Redo the preceding exercise to end when the input is a special sentinel string. For
example, you may use an empty string as the sentinel.
5. Write a program to read 1,000 random numbers in the range 0 to 1 from a file (see
exercise 4) and plot their distribution. That is, divide the interval 0-1 into tenths and
count the numbers that fall into each tenth. This gives you some confidence in their
randomness.
6. Modify the preceding two exercises to allow the user to specify the number of ran-
dom numbers and the name of the file on the command line. Store the number of
generated numbers as the first entry in the file.
7. Read a text file and write it to a target text file, changing all lowercase to uppercase
and double spacing the output text.
8. Modify the program in the previous exercise to number each nonblank line.
9. Write a class dollar. Have its overloaded I/O operators print a number such as
12345.67 as $12,345.67. You should decide whether this class should internally
store a dollar amount as two ints or a simple double.
10. Write a program that reads a text file and computes the relative frequency of each of
the letters of the alphabet. You can use an array of length 26 to store the number of
occurrences of each letter. You can use tolower() to convert uppercase letters.
Subtracting 'a' then gives you a value in the range 0 to 25, inclusive, which you can
use to index into the array of counts.
11. Run the program from the previous exercise on several large text files and compare
the results. How can you use this information to break a simple substitution code?
12. Compile the following program and put the executable code into the file try_me:
Ira Pohl’s C++ by Dissection Exercises 392
#include <iostream>
int main( )
{
cout << "A is for apple" << endl;
cerr << "and alphabet pie!" << endl;
}
Execute the program so you understand its effects. What happens when you redirect
the output? Try the command
try_me > temp
Make sure you read the file temp after you do this. If UNIX is available to you, try the
command
try_me >& temp
This causes the output that is written to cerr to be redirected, too. Make sure that
you look at what is in temp. You may be surprised!
13. Write a program to number the lines in a file. The input file name should be passed
to the program as a command line argument. The program should write to cout.
Each line in the input file should be written to the output file with the line number
and a space prepended.
14. Modify the program you wrote in the previous exercise so that the line numbers are
right-adjusted. The following output is not acceptable:
·····
9 This is line nine.
10 This is line ten.
15. Our program that double-spaces a file can be invoked with the command
dbl_space infile outfile
But if outfile exists, it is overwritten; this is potentially dangerous. Rewrite the pro-
gram so that it writes to stdout instead. Then the program can be invoked with the
command
dbl_space infile > outfile
This program design is safer. Of all the system commands, only a few are designed
to overwrite a file. After all, nobody likes to lose a file by accident.
16. Write the function getwords(in, k, words) so that it reads k words from a file
using the input stream in and places them in the string words, separated by new-
lines. The function should return the number of words successfully read and stored
in words. Write a program to test your function.
17. Write a program that displays a file on the screen 20 lines at a time. The input file
should be given as a command line argument. The program should display the next
20 lines after a carriage return has been typed. (This is an elementary version of the
more utility in UNIX.)
Ira Pohl’s C++ by Dissection Exercises 393
18. Modify the program you wrote in the previous exercise. Your program should dis-
play one or more files given as command line arguments. Also, allow for a command
line option of the form -n, where n is a positive integer specifying the number of
lines that are to be displayed at one time.
19. Write a program called search that searches for patterns. If the command
search hello my_file
is given, then the string pattern hello is searched for in the file my_file. Any line that
contains the pattern is printed. (This program is an elementary version of grep.)
Hint: Use STL functions.
20. (Java) In the following Java example, we demonstrate how to detect an EOF with the
standard Java class BufferedReader. The program opens the file specified on the
command line and echoes its contents to the console. Rewrite this code as C++.
import java.io.*;
class Echo {
public static void main(String[] args)
throws IOException {
if (args.length < 1) {
System.out.println("Usage: " +
"java Echo filename");
System.exit(0);
}
BufferedReader input =
new BufferedReader(new FileReader(args[0]));
String line = input.readLine();
while (line != null) {
System.out.println(line);
line = input.readLine();
}
}
}
CHAPTER 10
T his chapter describes exception handling in C++. Exceptions are generally unex-
pected error conditions. Normally, these conditions terminate the user program with a
system-provided error message. An example is floating-point divide-by-zero. Usually,
the system aborts the running program. C++ allows the programmer to attempt to
recover from these conditions and continue program execution.
Assertions are program checks that force error exits when correctness is violated. One
point of view is that an exception is based on a breakdown of a contractual guarantee
among the provider of a code, the code’s manufacturer, and the code’s client. (See Sec-
tion 11.1.1, ADTs: Encapsulation and Data Hiding, on page 423.) In this model, the client
needs to guarantee that the conditions for applying the code exist, and the manufac-
turer needs to guarantee that the code works correctly under these conditions. In this
methodology, assertions enforce the various guarantees.
10.1
10.1 Using the assert Library
Program correctness can be viewed in part as a proof that the computation terminated
with correct output, dependent on correct input. The user of the computation had the
responsibility of providing correct input. This was a precondition. The computation, if
successful, satisfied a postcondition. Providing a fully formal proof of correctness is an
ideal but is not usually done. Nevertheless, such assertions can be monitored at runt-
ime to provide very useful diagnostics. Indeed, the discipline of thinking out appropri-
ate assertions frequently causes the programmer to avoid bugs and pitfalls.
The C and C++ communities are increasingly emphasizing the use of assertions. The
standard library assert provides a macro, assert, which is invoked as
assert(expression);
If the expression evaluates as false, execution is aborted with diagnostic output. The
assertions are discarded if the macro NDEBUG is defined.
Ira Pohl’s C++ by Dissection 10.1 Using the assert Library 395
In file templateStack.cpp
// Template stack implementation
private:
enum { EMPTY = -1 };
TYPE* s;
int max_len;
int top;
};
The use of assertions replaces the ad hoc use of conditional tests with a more uniform
methodology. This is better practice. The downside is that the assertion methodology
does not allow a retry or other repair strategy to continue program execution. Also,
assertions do not allow a customized error message, although it would be easy to add
this capability.
It is possible to make this scheme slightly more sophisticated by providing various test-
ing levels, as are found in the Borland C++ checks library. Under this package, the flag
_DEBUG can be set to
_DEBUG 0 no testing
_DEBUG 1 PRECONDITION tests only
_DEBUG 2 CHECK tests also
The idea is that once the library functions are thought to be correct, the level of check-
ing is reduced to testing preconditions only. Once the client code is debugged, all test-
ing can be suspended.
The following bubble sort does not work correctly:
In file bad_bubble1.cpp
// Incorrect bubble sort
a = b;
b = temp;
}
int main()
{
int t[10] = { 9, 4, 6, 4, 5, 9, -3, 1, 0, 12};
bubble(t, 10);
for (int i = 0; i < 10; ++i)
cout << t[i] << '\t';
cout << "\nsorted? " << endl;
}
As an exercise, place assertions in this code to test that it is working properly. (See exer-
cise 1 on page 419.)
10.2
10.2 C++ Exceptions
In file simple_throw.cpp
int main()
{
cout << "\nEnter positive integer " <<
"(negative will cause exception)" << endl;
try {
double x;
cin >> x;
if (x < 0)
throw(x);
else sqrt(x);
·····
}
catch(double x)
{ cerr << "x = " << x << endl; abort(); }
}
The throw(x) has a double argument and matches the catch(double x) signature.
The catch(double x) is called an exception handler. It is expected to perform an
appropriate action where an incorrect value has been passed as an argument to sqrt().
For example, an error message and abort are normal.
Ira Pohl’s C++ by Dissection 10.3 Throwing Exceptions 398
10.3
10.3 Throwing Exceptions
throw expression
throw
The throw expression raises an exception. The innermost try block in which an excep-
tion is raised is used to select the catch statement that processes the exception. The
throw with no argument can be used inside a catch to rethrow the current exception.
This throw is typically used when you want a second handler called from the first han-
dler to further process the exception.
The expression thrown is a temporary object that persists until exception handling is
completed. The expression is caught by a handler that may use this value, as follows:
In file throw1.cpp
int foo()
{
int i = 0; // illustrates an exception thrown
// ····· code that affects i
if (i < 0)
throw i;
return i;
}
int main()
{
try {
foo();
}
catch(int n)
{ cerr << "exception caught\n" << n << endl; }
}
The integer value thrown by throw i persists until the handler with the integer signa-
ture catch(int n) exits and is available for use within the handler as its argument.
Ira Pohl’s C++ by Dissection 10.3 Throwing Exceptions 399
When a nested function throws an exception, the process stack is unwound until an
exception handler is found. This means that block exit from each terminated local pro-
cess causes automatic objects to be destroyed.
Ira Pohl’s C++ by Dissection 10.3 Throwing Exceptions 400
In file throw2.cpp
void foo()
{
int i, j;
·····
throw i; // foo() terminates with i persisting
// as the exception object
// i and j are destroyed
····· // this code won't be reached
}
void call_foo()
{
int k;
·····
foo(); // when foo() throws i call_foo() exits
// exception object from foo() persists
// k is destroyed
·····
}
int main()
{
try {
call_foo(); // exception object persists
}
catch(int n) { ····· } // catch(i) is executed
}
void foo()
{
try {
·····
throw i;
}
catch(int n)
{
if (n > 0) // handle for positive values here
·····
return;
}
else { // handle n <= 0 partially
·····
throw; // rethrown
}
}
Assuming that the thrown expression was of integer type, the rethrown exception is the
same persistent integer object that is handled by the nearest handler suitable for that
type.
class stack_error {
public:
stack_error(stack& s, string message);
};
Now, throwing an expression using an object of type stack_error can be more infor-
mative to a handler than just throwing expressions of simple types.
·····
throw stack_error(stk, "out of bounds");
·····
Let us use these ideas to write a complete example:
In file stack_error1.cpp
// Example of using an stack_error object
// Version 1. Uwe F. Mayer
#include <iostream>
#include <string>
using namespace std;
class stack_error {
public:
stack_error(stack& s, const string message) :
st(s), msg(message) { }
void* get_stack() const { return &st; }
const string& get_msg() const { return msg; }
private:
stack& st;
string msg;
};
Ira Pohl’s C++ by Dissection 10.3 Throwing Exceptions 403
int main()
{
stack stk;
try {
throw stack_error(stk,"out of bounds");
}
catch(stack_error& se)
{
cerr << se.get_msg() << " for stack stored at "
<< se.get_stack() << endl;
abort();
}
}
10.4
10.4 try Blocks
try
compound statement
handler list
The try block is the context for deciding which handlers are invoked on a raised excep-
tion. The order in which handlers are defined determines the order in which a handler
for a raised exception of matching type is tried.
try {
·····
throw ("SOS");
·····
io_condition eof(argv[i]);
throw (eof);
·····
}
catch(const char* s) {·····}
catch(io_condition& x) {·····}
Conditions Under Which Throw Expression Matches the Catch Handler Type
■ An exact match
■ A derived type of the public base-class handler type
■ A thrown object type that is convertible to a pointer type that is the catch
argument
It is an error to list handlers in an order that prevents them from being called. For
example:
10.5
10.5 Handlers
In file catch.cpp
catch(string& message)
{
cerr << message << endl;
exit(1);
}
10.6
10.6 Converting Assertions to Exceptions
We revisit our template class stack and use exceptions instead of assertions. Here,
we can see that the exception logic is more dynamic because the handlers can be more
informed than with asserts. The asserts print an assertion failure message and abort
the program. Exception handlers can print arbitrary information and either abort the
program or attempt to continue the program.
In file stack_error2.cpp
// Template stack with preconditions instead
// of assertions
#include <iostream>
using namespace std;
Ira Pohl’s C++ by Dissection 10.6 Converting Assertions to Exceptions 406
private:
enum { EMPTY = -1 };
TYPE* s;
int max_len;
int top;
};
Let us write a main() that tests these assertions:
Ira Pohl’s C++ by Dissection 10.6 Converting Assertions to Exceptions 407
int main() {
try{
cout << "allocates for -1 " << endl;
stack<char> d(-1);
} catch(int n) { cerr << "stack error n=" << n << endl; }
try{
stack<int> f(2);
f.push(1);
f.push(2);
f.push(3);
} catch(int n) { cerr << "stack error n= " << n << endl;}
}
The output from this program is
allocates for -1
Incorrect allocation
stack error n = 0
Stack OverFlow
stack error n = 2
■ } catch(int n)
{ cerr << "stack error n= " << n << endl; }
Upon failure, the catch() prints out that a stack error has occurred
with n = max_len.
10.7
10.7 Exception Specification
For all our listeners out there who may be unfamiliar with the
new expansion team, the Silicon Valley Exceptions, we have to
say that they don’t have a running game at all. But they sure
can catch and throw exceptionally well!
10.8
10.8 terminate() and unexpected()
The system-provided function terminate() is called when no handler has been pro-
vided to deal with an exception. The abort() function, called by default, immediately
terminates the program, returning control to the operating system. Another action can
be specified by using set_terminate() to provide a handler. These declarations are
found in the except library.
The system-provided handler unexpected() is called when a function throws an excep-
tion that was not in its exception-specification list. By default, the terminate() func-
tion is called; otherwise, a set_unexpected() can be used to provide a handler.
10.9
10.9 Standard Exceptions and Their Uses
C++ compilers and library vendors provide standard exceptions. For example, the
exception type bad_alloc is thrown by the ANSI compiler if the new operator fails to
return with storage from free store. The bad_alloc exception is in the exception library.
Here is a program that lets you test this behavior:
Ira Pohl’s C++ by Dissection 10.9 Standard Exceptions and Their Uses 410
In file except.cpp
#include <iostream>
#include <exception> // standard exceptions here
using namespace std;
int main()
{
int *p, n;
try {
while (true) {
cout << "enter allocation request:" << endl;
cin >> n;
p = new int[n];
}
}
catch(bad_alloc) { cerr << "bad_alloc" << endl; }
catch(...) { cerr << "default catch" << endl; }
}
This program loops until it is interrupted by an exception. On our system, a request for
1 billion integers invokes the bad_alloc handler.
A frequent use of standard exceptions is in testing casts. The standard exception
bad_cast is declared in file exception.
In file bad_cast.cpp
#include <iostream>
#include <exception>
using namespace std;
class A {
public:
virtual void foo() { cout << "in A" << endl; }
};
class B: public A {
public:
void foo() { cout << "in B" << endl; }
};
// Example by Ira Pohl and corrected by Uwe F. Mayer
int main()
{
try {
A a, *pa; B b;
A& ar1= b; // legal
// B& br1 = a; // illegal
Ira Pohl’s C++ by Dissection 10.10 Software Engineering: Exception Objects 411
ar1.foo();
pa = &b;
A& ar2 = dynamic_cast<A&>(*pa); // succeeds
ar2.foo();
pa = &a;
B& br2 = dynamic_cast<B&>(*pa); // fails, throws bad_cast
br2.foo();
}
catch(bad_cast) { cerr << "dynamic_cast failed" << endl; }
}
The standard library exceptions are derived from the base-class exception. Two
derived classes are logic_error and runtime_error. Logic-error types include
bad_cast, out_of_range, and bad_typeid, which are intended to be thrown, as indi-
cated by their names. The runtime error types include range_error, overflow_error,
and bad_alloc.
The base class defines a virtual function.
Paradoxically, error recovery is concerned chiefly with writing correct programs. Excep-
tion handling is about error recovery. Exception handling is also a transfer-of-control
mechanism. The client/manufacturer model gives the manufacturer the responsibility
of making software that produces correct output, given acceptable input. The question
for the manufacturer is how much error detection and, conceivably, correction should
be built in. The client is often better served by fault-detecting libraries, which can be
used in deciding whether to attempt to continue the computation.
Object::Object(arguments)
{
if (illegal argument1)
throw expression1;
if (illegal argument2)
throw expression2;
····· // attempt to construct
}
The Object constructor now provides a set of thrown expressions for an illegal state.
The try block can now use the information to repair or abort the incorrect operation.
try {
When many distinct error conditions are useful for the state of a given object, a class
hierarchy can be used to create a selection of related types to be used as throw expres-
sions.
Object_Error {
public:
Object_Error(arguments); // capture useful info
members that contain thrown expression state
virtual void repair()
{ cerr << "Repair failed in Object" << endl;
abort(); }
};
Exceptions are often misused when they are used as a patch to fix code, much in the
way the goto was used to hack changes to poorly designed programs. Exceptions are
meant to detect errors; therefore, they should mostly be used to provide informed ter-
mination and soft failure.
Programming by contract is the ability of one part of the code to rely on guarantees
from another part of the code. For example, to properly merge two lists, the merge code
must rely on the input lists already being ordered. This is often done with assertions.
The assertion methodology can be mimicked by exceptions that abort when guarantees
are not met. An example of this is a dynamic_cast throwing a bad_cast exception
when it is not able to provide the indicated conversion.
Exceptions should be thrown when requested resources are unavailable. The
std::bad_alloc exception thrown by new when it fails is an example of this
approach. In such cases, there may be ways to add to the system resources, allowing the
program to continue.
Unless program termination is unacceptable, as in mission-critical real-time systems, ad
hoc error correction and program resumption should be avoided. Such unexpected con-
ditions should be diagnosed and the code redone. Special techniques exist for mission-
critical code.
The last four tips were suggested by George Belotsky as further professional advice.
Exception handling adds significant runtime expense. An assert methodology tied to a
debug flag does not. In production code, you may want to eliminate exception handling.
Exception specifications may cause the system-provided handler unexpected() to be
called unnecessarily and is undesirable in production code. Pointers use can lead to
dangling references and memory leaks. Be careful about these problems when using
them as catch signatures. Finally, copying complex objects has significant expense. As
with ordinary function call signatures, catch signatures can use call-by-reference to
suppress this copying.
Java’s exception-handling mechanism is integral to the language and heavily used for
error detection at runtime. The mechanism is similar to the one found in C++. A Java
exception is itself an object, which must be derived from the superclass Throwable. An
exception is thrown by a method when it detects an error condition. The exception is
handled by invoking an appropriate handler picked from a list of handlers, or catches.
These explicit catches occur at the end of an enclosing try block. An uncaught excep-
tion is handled by a default Java handler that issues a message and terminates the pro-
gram.
The following code robustly reads one integer from the console. If the user doesn’t type
an integer, he or she is prompted to try again. It is taken from Java by Dissection by Ira
Pohl and Charlie McDowell (Addison Wesley 1999) pages 374-376, and uses the spe-
cially developed tio package. The source code is presented in Appendix D, , and is
available on the Web at ftp:// ftp.awl.com/cseng/authors/pohl-mcdowell/.
Ira Pohl’s C++ by Dissection 10.12 C++ Compared with Java 415
In file ExceptionExample.java
import tio.*;
while (!success) {
try {
aNumber = Console.in.readInt();
success = true;
}
catch (NumberFormatException e) {
inputString = Console.in.readWord();
System.out.println(inputString +
" is not an integer. Try again!");
}
}
System.out.println("You typed " + aNumber);
// continue with code to process aNumber
}
}
■ catch (NumberFormatException e) {
inputString = Console.in.readWord();
System.out.println(inputString +
" is not an integer. Try again!");
}
Here, we ignore the parameter. Arriving at this catch() tells us all we
need to know—a NumberFormatException occurred in the try block.
Because readInt() is defined not to consume any nonwhite input
characters if it fails, we use readWord() to read the offending white
space delimited string of characters. We print an appropriate message
and then continue with the statement following the try-catch state-
ment. The exception has been handled, and normal execution
resumes.
■ while (!success) {
·····
success = true;
·····
}
System.out.println("You typed " + aNumber);
// continue with code to process a Number
Eventually, the user will type a legal integer, the assignment to suc-
cess is reached, the end of the try block is reached, the catch block
is skipped, and the loop exits.
Summary
assert(expression);
If the expression evaluates as false, then execution is aborted with diagnostic out-
put. The assertions are discarded if the macro NDEBUG is defined.
■ C++ code can raise an exception by using the throw expression. The exception is
handled by invoking an appropriate handler selected from a list of handlers found
at the end of the handler’s try block.
■ The throw expression raises an exception in a try block. The throw with no argu-
ment may be used in a catch to rethrow the current exception. Syntactically, throw
comes in two forms:
throw expression
throw
■ The try block is the context for deciding which handlers are invoked on a raised
exception. The order in which handlers are defined determines the order in which a
handler for a raised exception of matching type is tried. Syntactically, a try block
has the form
try
compound statement
handler list
■ The catch looks like a function declaration of one argument without a return type.
Syntactically, a handler has the form
Review Questions
6. The system-provided handler is called when no other handler has been pro-
vided to deal with an exception.
7. Handlers are declared at the end of a try block, using the keyword .
Exercises
1. The following bubble sort does not work correctly. Place assertions in this code to
test that it is working properly. Besides detecting errors, the placing of assertions in
code as a discipline aids you in writing a correct program. Correct the program.
a = b;
b = temp;
}
int main()
{
int t[10] = { 9, 4, 6, 4, 5, 9, -3, 1, 0, 12};
bubble(t, 10);
for (int i = 0; i < 10; ++i)
cout << t[i] << '\t';
cout << "\nsorted?" << endl;
}
2. Use templates to write a generic version of the correct bubble sort, complete with
assertions. Use a random number generator to generate test data. On what types can
this be made to work generically?
4. Write a program that asks the user to enter a positive integer. Have it throw an
exception when the user fails to enter a correct value. Have the handler write out the
incorrect value and abort.
Ira Pohl’s C++ by Dissection Exercises 420
5. Rewrite the previous program to require the handler to ask the user for a correct
value. The program should terminate printing the correct value. Many programs try
to ensure that input is failure proof. This is an aspect of good software engineering.
6. Recode the ch_stack class to throw exceptions for as many conditions as you think
are reasonable. (See Section 5.1.5, Constructing a Stack, on page 190.) Use an enu-
merated type to list the conditions.
7. Write a stack_error class that replaces the enumerated type in the previous exer-
cise. Make this a base class for a series of derived classes that encapsulates each
specific exception condition. The catches should be able to use overridden virtual
functions to process the various thrown exceptions.
8. (Java) Recode in Java the ch_stack class, complete with exceptions. Java already
throws exceptions if new fails to allocate storage, and Java automatically throws a
range-error exception when an index is out of range.
CHAPTER 11
11.1
11.1 OOP Language Requirements
We present four major OOP language characteristics below. These features cannot sub-
stitute for programmer discipline and community-observed convention, but they can be
used to promote such behavior.
11.1.3 Polymorphism
Polymorphism is the genie in OOP, taking instruction from a client and properly inter-
preting its wishes. A polymorphic function has many forms. Following the categoriza-
tion developed by the programming theorists L. Cardelli and P. Wegner of Brown
University, we make the following distinctions:
Types of Polymorphism
1. Coercion (ad hoc polymorphism): A function or operator works on several
types by converting their values to the expected type. An example is conver-
sion of arithmetic types in expressions.
11.2
11.2 OOP: The Dominant Programming Methodology
OOP using C++ gained dazzling acceptance in industry from 1986 on, despite acknowl-
edged flaws and unfamiliarity with OOP strategies. The reason for this is that C++
brought OOP technology to industry in an acceptable way. C++ is based on an existing,
widely used, successful language. C++ allows tight, efficient, portable code to be writ-
ten. Type-safety is retained, and type extensibility is general. C++ coexists with stan-
dard languages and does not require special system resources.
C was designed as a system-implementation language and as such allows coding that is
readily translated to efficiently use machine resources. Software products gain compet-
itive advantage from such efficiency. Hence, despite complaints that traditional C was
not a safe or robust language to code in, C grew in its range of application. The C com-
munity, by convention and discipline, used structured programming and ADT exten-
sions. OOP made inroads into this professional community only when it was wed to C
within a conceptual framework that maintained its traditional point of view and advan-
tages. Key to the bandwagon move to C++ has been the understanding that inheritance
and polymorphism gain additional important advantages over traditional coding prac-
tice.
Polymorphism in C++ allows a client to use an ADT as a black box. Success in OOP is
characterized by the extent to which a user-defined type can be made indistinguishable
from a native type. Polymorphism allows coercions to be specified that integrate the
ADT with the native types. Objects from subtype hierarchies respond dynamically to
function invocation, the messaging principle in OOP. Polymorphism also simplifies cli-
ent protocols, and name proliferation is controlled by function and operator overload-
ing. The availability of all four forms of polymorphism encourages the programmer to
design with encapsulation and data hiding in mind. OOP is many things to many people.
Ira Pohl’s C++ by Dissection 11.2 OOP: The Dominant Programming Methodology 426
Attempts to define it are like blind men’s attempts to describe an elephant. Recall the
equation describing object orientation: OOP = type-extensibility + polymorphism.
In many languages and systems, the cost of detail suppression was runtime inefficiency
or undue rigidity in the interface. C++ has a range of choices that allow both efficiency
and flexibility. Also, the success of C++ was a precondition for the introduction of Java
in 1995. Together, C++ and Java have established OOP as the dominant contemporary
programming methodology.
The following example is amended from code developed by Andrew Koenig, the most
important figure in the C++ community besides the inventor of C++, Bjarne Strous-
troup. It is a demonstration of the power of type hierarchies and polymorphism. It is
used to do expression evaluation.
An expression such as 2 * x + y can be represented as a tree, with each subexpres-
sion being a node. The following code uses an expression tree to evaluate an expression
and print out its fully parenthesized form:
In file tree.cpp
// OOP = type-extensibility + polymorphism
// See also Andrew Koenig JOOP August 1988
#include <iostream>
using namespace std;
class Node {
friend class Tree;
friend ostream& operator<<(ostream&, const Tree&);
protected:
Node() { use = 1; }
virtual ~Node() { }
virtual void print(ostream&) const = 0;
virtual int eval() const = 0;
private:
int use; // reference count
};
In file tree.cpp
class IntNode : public Node {
public:
friend class Tree;
int eval() const { return n; }
private:
const int n;
void print(ostream& o) const { o << n; }
IntNode(int k): n(k) { }
};
The next major piece is the expression tree. By adding further node types, such as a
unary Node type and tree constructors, we can readily extend this code to more expres-
sion types.
In file tree.cpp
class Tree {
public:
Tree(int); // constant
Tree(char); // variable
Tree(char, Tree, Tree); // binary operator
Tree(const Tree& t) { p = t.p; ++p -> use; }
~Tree() { if (--p -> use == 0) delete p; }
void operator=(const Tree& t);
int eval() const { return p -> eval(); }
private:
friend ostream& operator<<(ostream&, const Tree&);
Node* p; // polymorphic hierarchy
};
Ira Pohl’s C++ by Dissection 11.2 OOP: The Dominant Programming Methodology 431
In file tree.cpp
int main()
{
11.3
11.3 Designing with OOP in Mind
Most programming should involve the use of existing designs. For example, the mathe-
matical and scientific communities have standard definitions of complex numbers,
rationals, matrices, and polynomials. Each of these can be readily coded as an ADT. The
expected public behavior of these types is widely agreed on.
Ira Pohl’s C++ by Dissection 11.3 Designing with OOP in Mind 433
Invertibility means that the program should have member functions that are inverses. In
the mathematical types, addition and subtraction are inverses. In a text editor, add and
Ira Pohl’s C++ by Dissection 11.4 Class-Responsibility-Collaborator 434
delete are inverses. Some commands, such as negation, are their own inverses. The
importance of invertibility in a nonmathematical context can be seen by the brilliant
success of the undo command in text editing and the recover commands in file mainte-
nance.
Completeness is best seen in Boolean algebra, in which the nand operation suffices to
generate all possible Boolean expressions. But Boolean algebra is usually taught with
negation, conjunction, and disjunction as the basic operations. Completeness by itself
is not enough to judge a design by. A large set of operators is frequently more expres-
sive.
Orthogonality means that each element of a design should integrate and work with all
other elements without overlapping or being redundant. For example, on a system that
manipulates shapes, one should have a horizontal move, a vertical move, and a rotate
operation. In effect, these operations would be adequate to position the shape at any
point on the screen.
Hierarchy is captured through inheritance. Designs should be hierarchical. It is a reflec-
tion of two principles—decomposition and localization. Both principles are methods of
suppressing detail, a key idea in coping with complexity. However, there is a scale prob-
lem in such a design. How much detail is enough to make a concept useful as its own
class? It is important to avoid a proliferation of specialized concepts. Too much detail
renders the class design difficult to master.
11.4
11.4 Class-Responsibility-Collaborator
card front
card back
state/description
top
base_pointer
CRC Card
As the design process proceeds, the cards are rewritten and refined. They become more
detailed and closer to a set of member function headers. The back of the card can be
used to show implementation details, including ISA, LIKEA, and HASA relationships.
The attractiveness of this scheme is its flexibility. In effect, it represents a pseudocode
refinement process that can reflect local tastes. The number of revisions and the level
of detail and rigor are a matter of taste. (See the site at http://c2.com/doc/oopsla89/
paper.html for a good description.)
A more formal system for documenting class architectures is Unified Modeling Lan-
guage (UML), which we already discussed in Section 4.12.2, Unified Modeling Language
(UML) and Design, on page 169. (See the UML site at http://www.rational.com/uml/ for a
full description.) A class diagram describes the types and relationships in the system. It
is very useful documentation, and a number of systems, such as Rational Rose, now
provide automated tools to develop such documentation along with coding. A key rela-
Ira Pohl’s C++ by Dissection 11.5 Design Patterns 436
tionship is the ISA or subtype relationship. In the following basic UML diagram, we
show the Node-IdNode class diagram:
Node
use
eval()
print()
IdNode
eval()
print()
Other relationships that can be depicted by UML include the part-whole or aggregation
relationship (HASA), and the uses or collaborates relationship. For example, a Tree type
uses a Node type as part of its representation. This is also called delegation.
Tree Node
name
val
print()
eval()
11.5
11.5 Design Patterns
Reuse is a primary theme in modern programming. In early times, reuse was limited to
simple libraries of functions, such as the math functions found in math.h or the char*
functions in cstring. In OOP, the class or template becomes a key construct for reuse.
Classes and templates encapsulate code that conforms to certain designs. Thus, the
iterator classes of STL are a design pattern. Recently, the concept of design pattern has
proved very popular in defining medium-scale reuse. A design pattern has four ele-
ments.
Ira Pohl’s C++ by Dissection 11.6 A Further Assessment of C++ 437
Design Patterns
1. Iterator, such as vector::iterator; organizes visitation on a container
2. Composite, such as class grad_student; composes complex objects out of
simpler ones
OOP has stimulated reuse through design patterns. A design pattern is a software solu-
tion in search of a problem. Consider how the iterator logic of STL decouples visitation
of container elements from specific details of the container. This idea is independent of
computer language and is useful in C++, Java, and SmallTalk coding projects. This idea
can be summarized as the iterator pattern.
The name is of great importance, as it increases the programmer’s technical vocabulary.
A name should be memorable and illuminate a key characteristic of the method. The
problem identifies circumstances under which the pattern provides a solution. The
solution shows how the pattern solves the problem. The consequences are a discussion
of the cost-benefit trade-off in using the pattern.
When the pattern is discussed in a specific language context, it is often called a pro-
gramming idiom. This is also sometimes used for smaller coding ideas. For example, in
C++ or C, EOF is frequently used as a guard value to terminate file processing.
When the pattern is used in a wider context to provide a library of routines and compo-
nents, it is called a framework. The STL can be considered a framework that makes
heavy use of the iterator and template patterns, among others. In Java, the Java Founda-
tion Classes, also known as Swing, support window development. They are imple-
mented with the model-view-controller pattern.
11.6
11.6 A Further Assessment of C++
C++ is better than C in several ways. First, it can be used in place of C without change
for most applications. Second, it extends C by including additional critical features that
support object-oriented and generic programming. Third, it remedies some of C’s
defects.
Ira Pohl’s C++ by Dissection 11.6 A Further Assessment of C++ 438
C++ is largely a superset of C. There are some minor differences, such as the difference
in semantics between void* in both languages and the fact that C++ treats control
expressions as bool. Nevertheless, most running C code compiles and runs as C++
code.
C++ was designed, at first, to allow for classes and inheritance, the core functionality
needed for object-oriented programming. This was done without sacrificing C’s object
code efficiency. The key idea was to extend struct’s functionality to include access
restrictions and member function declarations. The result is that the C++ language is
philosophically consistent with C at its core. C++ also embraced generic programming
using templates.
C++ remedies some of C’s defects. C was a language of the 1970s. It relied on the pre-
processor to provide macros and to define constants. C++ uses inline, template, and
const to provide these facilities directly in the language. C is heavily criticized for its
lack of type-safety. Here, C++ provided iostream, a type-safe I/O library, as a replace-
ment for stdio.h in C. Also, C++ replaced unrestricted casting with four named casts.
C++ added the bool type, which means that code that needs logical values, such as con-
trol expressions in conditional statements, no longer uses the traditional C interpreta-
tion of 0 as false and nonzero as true. C++ also provides more scoping constructs. In
general, having scope enables you to manage complexity better. C++ allows namespace
scope and nested class scope, both unavailable in C.
gramming that is type-safe. The implementation and use of STL, including important
extensions to it, demonstrate the power of these techniques.
11.7
11.7 Software Engineering: Last Thoughts
Let us revisit our tree.cpp and examine software engineering lessons learned from this
example. Imagine writing this code in a procedural programming style such as needed
by C or Pascal. Invariably, it would be designed around a big switch statement, such as
switch (nodetype) {
case 1: ····· break; // unary operator
case 2: ····· break; // binary operator
·····
default: cerr << “ Case not Found. “ << endl; abort();
break;
}
What is required when we write a new version, possibly incorporating the evaluation of
additional operators? We have to search out each such switch and update with the new
case. In the case of our object-oriented code using a related hierarchy of Node types, we
only need to add a new subclass. We do not have to do any global searches.
Notice that we did use a switch for evaluating a BinaryNode. This can be avoided by
having further subclasses. In exercise 6 on page 450, you are asked to provide this
switchless alternative and discuss its design trade-offs.
Ira Pohl’s C++ by Dissection 11.8 Dr. P’s Prescriptions 440
11.8
11.8 Dr. P’s Prescriptions
11.9
11.9 C++ Compared with Java
Java shares with C++ the use of classes and inheritance to build software in an object-
oriented manner. Also, both languages use data hiding and have methods that are bun-
dled within the class.
Unlike C++, Java does not allow for conventional programming. Everything is encapsu-
lated in a class. This forces the programmer to think and to design everything as an
object. The downside is that conventional C code is not as readily adapted to Java as it
is to C++. Java avoids most of the memory-pointer errors that are common to C and
C++. Address arithmetic and manipulation are done by the compiler and the system,
not the programmer. Therefore, the Java programmer writes safer code. Also, memory
reclamation is automatically done by the Java garbage collector.
Another important concept in OOP is the promotion of code reuse through the inherit-
ance mechanism. In Java, this is the mechanism of extending a new class, called a sub-
class, from an existing one, called the superclass. Methods in the extended class
override the superclass methods. The method selection occurs at runtime and is a
highly flexible polymorphic style of coding.
Java, in a strict sense, is completely portable across all platforms that support it. Java is
compiled to byte code that is run on the Java virtual machine. This is typically an inter-
preter—code that understands the Java byte code instructions. Such code is much
slower than native code on most systems. The trade-off here is universally consistent
behavior versus loss of efficiency.
Java has extensively developed libraries for performing Web-based programming. Java
also has the ability to write graphical user interfaces that are used interactively. Its
thread package has secure Web communication features that let the coder write distrib-
uted applications.
Java is far simpler than C++ in the core language and its features. In some ways, this is
deceptive in that much of the complexity is in its libraries. Java is far safer because of
very strict typing, avoidance of pointer arithmetic, and well-integrated exception han-
dling. It is system independent in its behavior, so one size fits all. This combination of
object orientation, simplicity, universality, and Web-sensitive libraries makes it the lan-
guage of the moment.
Java programs are classes. A class has syntactic form that is derived from the C
struct, which does not exist in Java. Data and functions are placed within classes.
When a class is executed as a program, it starts by calling the member function main().
Java is known for providing applets on Web pages. A browser is used to display and
execute the applet. Typically, the applet provides a graphical user interface (GUI) to the
code. The following discussion of an applet is taken from Chapter 8 of Java by Dissec-
tion (Addison Wesley 1999), by Pohl and McDowell. It uses the standard Java library
swing for drawing components and the Java applet library awt.
Most GUIs have more than a single button to click or a single text field. As a result, we
face two new questions: How do we arrange the GUI components whenwe have more
than one? How do we respond to events from several different components?
Ira Pohl’s C++ by Dissection 11.9 C++ Compared with Java 442
To control the arrangement of GUI components, Java uses layout managers. A layout
manager is an object that determines the location of components. One type of layout
manager is implemented by the class java.awt.GridLayout. As the name implies,
GridLayout arranges the components in a two-dimensional grid. We specify the num-
ber of rows and columns in the grid and then add the components, one at a time. Each
new component is added into the next available cell in the grid.
In the following program, we use a GridLayout to arrange the components of a minical-
culator, which is capable of adding and subtracting two numbers. The program includes
two buttons: one for adding and one for subtracting. The ActionListener determines
which button is clicked.
In file MiniCalc.java
// Demo GridLayout
import java.awt.*;
import javax.swing.*;
class MiniCalc {
public static void main(String[] args) {
JFrame frame = new JFrame("MiniCalc");
Container pane = frame.getContentPane();
pane.add(result);
pane.add(addButton);
pane.add(subButton);
Ira Pohl’s C++ by Dissection 11.9 C++ Compared with Java 443
We have now shown one way to control the arrangement of multiple components—with
a GridLayout. We use the class DoMath to show how an object can listen to multiple
buttons and determine which button was clicked.
In file DoMath.java
// Respond to two different buttons
import javax.swing.*;
import java.awt.event.*;
Ira Pohl’s C++ by Dissection 11.9 C++ Compared with Java 445
Summary
back of the card is used to describe implementation detail. The front of the card cor-
responds to public behavior.
■ OOP has stimulated reuse of design patterns. A design pattern is a software solution
in search of a problem. Consider how the iterator logic of STL decouples visitation of
container elements from specific details of the container. This idea can be summa-
rized as the iterator pattern. The name is of great importance, as it increases the
programmer’s technical vocabulary. The name should be clever and should illumi-
nate a key characteristic of the method. The problem identifies circumstances under
which the pattern provides a solution. The solution shows how the pattern solves
the problem. The consequences are a discussion of the cost-benefit trade-off in
using the pattern.
Review Questions
2. True or false: Conventional academic wisdom is that excessive concern with effi-
ciency is detrimental to good coding practice.
3. Through , a hierarchy of related ADTs can be created that share code and a
common interface.
7. is the genie in OOP, taking instruction from a client and properly interpreting
its wishes.
9. Describe at least two separate concepts for the keyword virtual as used in C++.
Does this cause conceptual confusion?
Ira Pohl’s C++ by Dissection Exercises 449
Exercises
#define TRUE 1
#define FALSE 0
#define Boolean int
// C++ as a class
class Boolean {
·····
public:
// various member functions
// including overloading ! && || == !=
};
What would be the advantages and disadvantages of each style? Keep in mind scope,
naming and conversion problems. In what ways is it desirable for C++ to now have a
native type bool?
Discuss the advantages and disadvantages of each style. Keep in mind scope, nam-
ing, and conversion problems. In what ways is it desirable for C++ to now have a
native type bool?
2. C++ originally allowed the this pointer to be modifiable. One use was to have user-
controlled storage management by assigning directly to the this pointer. The
assignment of 0 meant that the associated memory could be returned to free store.
Discuss why this is a bad idea. Write a program with an assignment of this = 0.
What error message does your compiler give you? Can you get around this with a
cast? Would this be a good idea?
3. The rules for deciding which definition of an overloaded function to invoke have
changed since the first version of C++. One reason for this is to reduce the number
of ambiguities. A criticism is that the rules allow matching through conversions that
may be unintended by the programmer causing difficult-to-detect runtime bugs.
One strategy is to have the compiler issue a diagnostic warning in such cases;
another is to use casting defensively to inform the compiler of the intended choice.
Discuss these alternatives after investigating how the rules have changed.
4. Add a UnaryNode class to our tree.cpp code from Section 11.2, OOP: The Dominant
Programming Methodology, on page 426. It should deal with unary minus and unary
Ira Pohl’s C++ by Dissection Exercises 450
plus. Unary plus is a no-op, meaning nothing is done. Test using the expressions +a -
3 * b and -a + b * -3.
5. Redo the previous exercise by including the increment and decrement operators.
Write and run appropriate test cases.
6. (Uwe F. Mayer) Redo the tree.cpp from Section 11.2, OOP: The Dominant Program-
ming Methodology, on page 427, to avoid the switch statements. Do this by writing
further subclasses for the different binary operators. Discuss the benefits and draw-
backs of this approach.
7. (Java) Java and C++ have different casting rules. Investigate the differences. C++
allows a wider range of casting opportunities. Is this desirable?
8. List three things that you would drop from the C++ language. Argue why each would
not be missed. For example, it is possible to have protected inheritance, although it
was never discussed in this text? Should it be in the language for completeness’
sake? Can you write code that uses protected inheritance that demonstrates that it
is a critical feature of language, as opposed to an extravagance?
9. (Java) Using awt, write a Java program that is a basic desktop calculator. Have but-
tons that indicate a series of operations, such as addition, multiplication, square
root, and reciprocal; data fields to enter arguments; and a result field. If you have
access to JFC (Swing), use it. Document your design with CRC cards.
APPENDIX A
Some Observations
■ Character codes 0 through 31 and 127 are nonprinting.
■ Character code 32 prints a single space.
■ Character codes for digits 0 through 9e are contiguous.
■ Character codes for letters A through Z are contiguous.
■ Character codes for letters a through z are contiguous.
■ The difference between a capital letter and the corresponding lowercase let-
ter is 32.
Ira Pohl’s C++ by Dissection 452
Operators Associativity
:: (global scope) :: (class scope) Left to right
func() [] -> . (postfix) ++ (postfix) --typeid(e) Left to right
type(e) dynamic_cast<type>(e) static_cast<type>(e)
reinterpret_cast<type>(e) const_cast<type>(e)
++ (prefix) --(prefix) ! ~ & (address) Right to left
sizeof(e) + (unary) - (unary) *(indirection)
delete new (type)e
.* ->* Left to right
* / % Left to right
+ - Left to right
<< >> Left to right
< <= > >= Left to right
== != Left to right
& Left to right
^ Left to right
| Left to right
&& Left to right
|| Left to right
?: Right to left
= += -= *= /= %= >>= <<= &= ^= |= Right to left
throw(e) Left to right
, (comma operator) Left to right
Ira Pohl’s C++ by Dissection 454
All operators in a given table entry, such as ++, new, and &, have equal precedence with
respect to one another but have higher precedence than all the operators in the entries
below them.
The associativity rule for all the operators in a given entry appears on the right side of
the table.
APPENDIX C
String Library
A string type library is supplied by the C++ system including the standard header file
string. It is the instantiation of a template class basic_string<T> with char. The
string type provides member functions and operators that perform string manipula-
tions, such as concatenation, assignment, or replacement. An example of a program
using the string type for simple string manipulation follows:
In file poem.cpp
// String class to write a poem
#include <string>
#include <iostream>
#include <cstdlib>
#include <vector>
using namespace std;
int main()
{
int i, seed;
string poem;
vector<string> nouns(6), verbs(6), rhymes(6);
This implementation provides an explicit variable to track the string length; thus, string
length can be looked up in constant time, which is efficient for many string computa-
tions.
C.1
C.1 Constructors
Table C.2 presents the six public string constructors. These constructors make it easy
to declare and initialize strings from a wide range of parameters.
Ira Pohl’s C++ by Dissection C.2 Member Functions 457
These constructors make it quite easy to use the string type initialized from char*
pointers, which is the traditional C method for working with strings. Many computa-
tions are readily handled as a vector of characters. This is also facilitated by the string
interface.
C.2
C.2 Member Functions
Strings have some members that overload operators, as described inTable C.3.
The extensive set of public member functions lets you manipulate strings. In many
cases, these functions are overloaded to work with string, char*, and char.
For the append() and assign() functions in Table C.4, all functions throw a
length_error exception if the resulting lengths exceed max_size() and all return a
reference to the implicit string argument.
In the following code examples, it is assumed that each statement starts with s1 con-
taining "I am " and s2 containing "7 years old":
Ira Pohl’s C++ by Dissection C.2 Member Functions 458
The insert() functions presented inTable C.6 work with iterators. These versions of
insert() put additional elements in this string immediately before the character referred
to by p. All of these versions require that p is a valid iterator on this string. The first two
functions return the iterator p, the third returns void.
The following code illustrates the use of insert() and again assumes that s1 contains
"I am " and s2 contains "7 years old".
You can lexicographically compare two strings by using compare(), a family of over-
loaded member function shown in Table C.8. The return int value is 0 if the strings
have the same value, a negative number if the implied string is lexicographically before
the string argument, and a positive number otherwise.
.
The find() functions we present in Table C.9 perform a find operation. One group is
discussed here; Table C.10 summarizes more variations of this group of member func-
tions. In all cases, if the substring is found, its position is returned. If it is not found,
npos is returned.
Further functions for finding strings and characters are briefly described in Table C.10.
C.3
C.3 Global Operators
The string package contains operator overloadings that provide input/output, concate-
nation, and comparison operators. These are intuitively understandable and are briefly
described in Table C.11.
The comparison operators and the concatenation operator+() are also overloaded
with the four signatures as shown in Table C.12.
Ira Pohl’s C++ by Dissection C.3 Global Operators 461
In effect, a comparison or concatenation of any kind can occur between string and a
second argument that is a string, a character, or a character pointer.
APPENDIX D
T he Java tio package was developed by Charlie McDowell as an aid in writing simple
I/O in Java programs. Originally developed for the book Java by Dissection, by Ira Pohl
and Charlie McDowell (Addison-Wesley, 1999) the downloadable source code may be
found at www.cse.ucsc.edu/~charlie/java/tio/.
D.1
D.1 Console
package tio;
import java.io.*;
/**
* The class Console is a convenience class.
* It contains a static variable in that is
* initialized to refer to a ReadInput object,
* reading from the standard input stream
* System.in.
* It also contains a static variable out
* that is initialized to refer to a
* FormattedWriter, writing to the output
* stream System.out.
*/
D.2
D.2 FormattedWriter
package tio;
import java.io.*;
import java.text.*;
/**
* The class FormattedWriter contains
* methods that allow for formatted printing.
* It includes support for setting the width of the
* output field, using left or right justification in
* the output field, using an arbitrary fill
* character, and setting the number of digits to the
* right of the decimal point in floating point
* values.
*
* @author C. E. McDowell
*/
/**
* Constructs a FormattedWriter object for an
* OutputStream.
*
* @param os the OutputStream to write to
*/
/**
* Constructs a FormattedWriter object for a
* FileWriter.
*
* @param writer the FileWriter to write to
*/
/**
* Constructs a FormattedWriter object for writing
* to a file.
*
* @param filename the name of the file to write to
*/
/**
* Set the output field width. If the value being
* printed is less than the width of the field,
* then the field is padded with the pad character
* (see setPadChar()). The field can be either left
* or right justified (see setJustify()).
*
* @param width the width of the output field
*/
/**
* Set the number of digits to be printed to the
* right of the decimal point in floating point
* values.
*
* @param places the number of places to the right
* of the decimal point
*/
/**
* Set the justification to be LEFT or RIGHT.
*
* @param leftOrRight use FormattedWriter.LEFT
* or FormattedWriter.RIGHT
* @exception IllegalArgumentException if not LEFT
* or RIGHT
*/
/**
* Set the character to be used in padding.
* The default padding character is a blank.
*
* @param pad the character to use in padding
*/
/**
* Print a String in a field of the current
* width using the current padding character
* and justification.
*
* @param s the String to print
*/
Ira Pohl’s C++ by Dissection D.2 FormattedWriter 466
/**
* Print a boolean in a field of the current
* width, using the current padding character and
* justification.
*
* @param value the value to print
*/
/**
* Print a char in a field of the current
* width, using the current padding character and *
justification.
*
* @param value the value to print
*/
/**
* Print an array of characters in a field of the
* current width, using the current padding
* character and justification.
*
* @param value the value to print
*/
/**
* Print an int in a field of the current
* width, using the current padding character and
* justification.
*
* @param value the value to print
*/
/**
* Print a long in a field of the current
* width, using the current padding character and *
justification.
*
* @param value the value to print
*/
/**
* Print any Object in a field of the current
* width, using the current padding character and *
justification.
*
* @param value the value to print
*/
/**
* Print a double in a field of the current
* width, with the current number of digits to the
* right of the decimal point and using the current
* padding character and justification.
*
* @param value the value to print
*/
/**
* Print a float in a field of the current
* width, with the current number of digits to the
* right of the decimal point and using the current
* padding character and justification.
*
* @param value the value to print
*/
/**
* Same as printf() with a newline added at the
* end.
*/
/**
* Same as printf() with a newline added at the
* end.
*/
/**
* Same as printf() with a newline added at the
* end.
*/
/**
* Same as printf() with a newline added at the
* end.
*/
Ira Pohl’s C++ by Dissection D.2 FormattedWriter 469
/**
* Same as printf() with a newline added at the
* end.
*/
/**
* Same as printf() with a newline added at the
* end.
*/
/**
* Same as printf() with a newline added at the
* end.
*/
/**
* Same as printf() with a newline added at the
* end.
*/
/**
* Same as printf() with a newline added at the
* end.
*/
Ira Pohl’s C++ by Dissection D.2 FormattedWriter 470
/*
* Trim the number of digits to the right of the
* decimal point if there is one.
*/
if (decimalPlaces == -1)
return value;
int pos = value.indexOf(".");
int exp = value.indexOf("E");
if (exp == -1)
places = value.length() - pos - 1;
else
places = exp - pos - 1;
if (places <= decimalPlaces)
return value;
if (exp == -1)
return round(value);
else {
String needsRounding=value.substring(0, exp);
return round(needsRounding) +
value.substring(exp);
}
}
/*
* Round the last digit of s. E.g. 1.2345 would be
* returned as 1.235 and 1.234 would be returned as
* 1.23.
* This is done using a java.text.NumberFormat
* object that had its decimal places set in
* setDigits() above.
*/
Ira Pohl’s C++ by Dissection D.2 FormattedWriter 471
/*
* Create an array of pad characters used for
* quickly building strings of pad characters
* by a call to substring (see printfln(String s))
*/
D.3
D.3 PrintFileWriter
package tio;
import java.io.*;
/**
* The class PrintFileWriter is a
* convenience class. It adds one constructor to its
* parent class, PrintWriter. This new constructor
* takes the name of a file.
*
* new PrintFileWriter(fileName) is the
* same as
* new PrintWriter(new FileWriter(fileName))
*
*/
D.4
D.4 ReadException
package tio;
import java.io.*;
/**
* The class ReadException is used to convert
* java.io.IOExceptions into a subtype of
* RuntimeException. By doing this, users of
* ReadInput methods do not need to use throws
* declarations,simplifying beginning programs.
* Subtypes of RuntimeException do not need
* to be declared using a throws clause.
*
* @author C. E. McDowell
* @version 1.1, Released for Java By Dissection
*
*/
Ira Pohl’s C++ by Dissection D.5 ReadInput 473
/**
* Constructs a ReadException object with no
* specific message.
*/
public ReadException() {
super();
}
/**
* Constructs a ReadException object with the
* specified message.
*
* @param message the error message
*/
D.5
D.5 ReadInput
package tio;
import java.io.*;
/**
* The class ReadInput contains methods that
* allow for simple input of numbers, strings and
* characters from a text stream.
*
* @author C. E. McDowell
* @version 1.1, release for Java By Dissection
*/
/**
* Constructs a ReadInput object for reading from
* any * Reader object.
* @param input the Reader text stream to read * from.
*/
Ira Pohl’s C++ by Dissection D.5 ReadInput 474
/**
* Constructs a ReadInput object for reading from a
* file.
*
* @param filename the name of the file from which
* to read.
* @exception FileNotFoundException if the file
* can’t be opened.
*/
/**
* Constructs a ReadInput object for reading from
* any InputStream.
*
* @param input the InputStream to read from.
*/
/**
* Check to see if there are any non-white space
* characters left in the input. If used to
* terminate reading with readLine(), any trailing
* blank lines will be ignored. To read trailing
* blank lines, do not use hasMoreElements() and
* instead read with readLine() until an
* EOFException is thrown.
*
* @return true if the input contains more
* non-white space characters and false otherwise.
*/
Ira Pohl’s C++ by Dissection D.5 ReadInput 475
/**
* Read next character. White space is not skipped.
* readChar() cannot be used to reread input
* characters that resulted in a
* NumberFormatException trying
* to read a number. readLine() will return the
* characters of a failed number read.
*
* @return the int value of the next character.
*/
/**
* Attempt to interpret next white space delimited
* input characters as a double.
*
* @return double value of the next, white-space
* delimited input string.
* @exception NumberFormatException if A3next input
* string does not contain a parsable double.
*/
Ira Pohl’s C++ by Dissection D.5 ReadInput 477
/**
* Attempt to interpret next white space delimited
* input characters as a float.
*
* @return the float value of the next, white-space
* delimited input string.
* @exception NumberFormatException if next input
* string does not contain a parsable float.
*/
/**
* Attempt to interpret next white space delimited
* input characters as an int.
*
* @return the int value of the next, white-space
* delimited input string.
* @exception NumberFormatException if next input
* string does not contain a parsable int.
*/
Ira Pohl’s C++ by Dissection D.5 ReadInput 478
/**
* Read the next complete input line up to newline
* character. The terminating newline character is
* read and discarded. It is not part of the return
* string. If the previous read was an attempt to * read a
number that generated
* a NumberFormatException, readLine()
* will return the input line including the input
* characters that caused the exception. This can
* be used to try and recover from failure to read
* numeric input.
*
* @return the next input line as a String.
*/
/**
* Attempt to interpret next white space delimited
* input characters as a long.
*
* @return the long value of the next, white-space
* delimited input string.
* @exception NumberFormatException if next input
* string does not contain a parsable long.
*/
/**
* Read the next white space delimited string.
*
* @return the next, white-space delimited input
* string.
*/
/**
* Do the work of reading a line of text.
* White space may have been buffered up from a
* call to hasMoreElements(). If so, unread the
* buffered white space then read one line.
*/
Ira Pohl’s C++ by Dissection D.5 ReadInput 480
/**
* Read the next white space delimited string.
* This will then be parsed by the appropriate
* routine to return one of the desired types.
*/
Ira Pohl’s C++ by Dissection D.5 ReadInput 481
Mascitti, R. 139 N
math library 436 \n newline 30, 367
max() 304 namespace
max_element() 304 anonymous 99
max_size() 286, 320 scope 98, 247
McDowell, C. viii, 17 namespace 4, 94, 98
member 140, 167, 195 narrowing 41
data 143, 148, 159, 170, 183 native type 25-44, 207
function 140, 143, 161, 183, 195, 207, 211, need to know style 148
217, 263, 328-329, 337, 433, 435 negation ! 47
hidden 211 nested function 399
object selector .* 172, 214 nested program 152
operator . 214 new 120, 206, 224
union 174 new library 225
member function 143 newline \n 30, 367
memberwise copy 205 next_permutation() 303, 309
memberwise initialization 193 noboolalpha 369
memcpy() 136 nonmutating sequence 305
memory location 99 normal form 228
memory management noskipws 369
delete 120, 195, 226 not equal != 47
new 120, 206, 224 not1() 319
memory register 93 not2() 319
merge() 303 nth_element() 303
message 339 null character \0 29-30
method 143, 339 null pointer 0 197, 353
instance 274 null statement 51
mutator 275 NumberFormatException 415
MI 355 numeric library 282, 310-311
min() 304 numerical algorithm 310
min_element() 304
minimum() 112 O
mismatch() 307
.obj file 97
mix_io program 384
object 146, 167, 183, 315
mixed expression 40
object file 3, 14
mixing libraries 383
object-oriented programming 422
Modula–2 422
Occam’s Razor 433
modulus operator % 44
oct 369
Monte Carlo calculation 108, 153
ofstream library 376
MS-DOS
OOP 422
control-d 16
open() 376-377
end-of-file signal 16
operating system 2, 14
multi_main program 96
operator 31, 65
multidimensional array 117, 120
associativity 45, 47, 214, 453
multimap 283, 288, 290
binary overloading 217
multiple assignment 220
bit manipulation 50
multiple inheritance 351, 355
bit shift 31
multiplication operator * 44
bitwise 176
multiset 283, 288, 290
equality 47
mutable 163
logical 47-48
mutating sequence 307
overloading 213, 228-230
mutator function 166-167, 191
precedence 45, 47, 214, 453
mutator method 275
relational 47
mv command 14
ternary 49
my_clock program 214, 216-217, 227
unary overloading 214
my_string program 201, 213, 219, 221, 229
Operator Precedence and Associativity 453
Ira Pohl’s C++ by Dissection 494
V
\v vertical tab 30
variable
global 92
reference 99
vect_it program 263, 265
vector library 280, 283, 285, 293, 297, 300, 307,
311
vertical tab \v 30
vi command 14
virtual
base class 353
inheritance 352
member functions 329
virtual 337
virtual_err program 340
virtual_sel program 338